iT邦幫忙

2026 iThome 鐵人賽

DAY 1
0
Software Development

TW-OCR:高準確率繁體中文公文辨識與版面重建系統系列 第 3

Day 3|一張公文進來後,到底經過了哪些模型?

  • 分享至 

  • xImage
  •  

前兩天我們先處理了兩件事情。

Day 1 講的是:

為什麼政府公文不能只當成一般 OCR 問題。

Day 2 則先建立了一把尺:

到底怎樣才算 OCR 準。

今天終於可以正式把整套系統攤開來看。

如果用一句話形容我最初對 OCR 的想像,大概就是:

圖片 → OCR Model → 文字

但實際做到最後,我的架構完全不是這樣。

比較接近:

PDF / Image
    ↓
文字偵測
    ↓
版面分析
    ↓
表格結構分析
    ↓
文字辨識
    ↓
後處理
    ↓
文字 + 座標 + 表格結構

也就是說:

真正「讀字」的模型,其實到了很後面才出場。


為什麼不直接把整頁丟給 VLM?

現在的 Vision Language Model 已經很強了。

拿一張文件圖片丟進去,再問:

「請把這張圖片的文字完整轉出來。」

確實常常可以得到看起來相當不錯的結果。

所以一開始很自然會問:

既然 VLM 已經看得懂圖片,為什麼還要弄這麼多層?

答案其實跟 Day 1 講的問題有關。

我真正需要的不只是:

「大概知道這頁寫什麼。」

而是希望最後可以拿到:

  • 完整文字

  • 每段文字的位置

  • 正確閱讀順序

  • 表格 cell

  • 橫排與直排內容

  • 關鍵欄位

如果只是拿去做圖片問答,整頁 VLM 很合理。

但如果後面還要進:

RAG、文件檢索、AI 審查、欄位擷取或結構化資料庫

那我就不能接受:

「意思差不多。」

我需要的是:

這個字在哪裡、屬於哪一段、哪一格,以及到底寫了什麼。

所以最後我把問題拆成四層。


第一層:CRAFT —— 先回答「字在哪裡?」

第一個出場的是:

CRAFT。

它在我的系統裡不是負責辨識文字。

它只負責一件事情:

找出文字的位置。

例如一張 200 dpi 的公文影像進來。

CRAFT 不需要知道上面寫的是:

「環境部」

還是:

「行政院」。

它只要告訴我:

這裡有一行字
這裡也有一行字
下面還有一塊文字

然後回傳一個個 Bounding Box。

概念上就像:

┌──────────────────────────┐
│        ○○○○○○○           │
│                          │
│  ┌───────────────────┐   │
│  │ 這裡是一段正文      │   │
│  └───────────────────┘   │
│                          │
│  ┌─────────┐ ┌────────┐  │
│  │ 表格文字 │ │ 表格文字│  │
│  └─────────┘ └────────┘  │
└──────────────────────────┘

CRAFT 做的就是把這些文字區域找出來。

這個階段很重要。

因為後面的辨識模型並不是每次直接看整張 A4。

而是:

先找到文字,再把小區域裁下來。

這種裁下來的小圖,我後面都會叫它:

crop。

例如一頁公文最後可能被切成數十甚至數百個 crop。

辨識模型真正看到的是這些小塊圖片。


為什麼我要先 Crop?

原因很簡單。

假設一張公文有:

30 行正文、10 個表格欄位、3 個標題。

如果直接把整張 200 dpi 文件縮進模型固定的輸入尺寸,

原本很清楚的一個中文字,很可能只剩下非常少的 pixel。

尤其如果是一整條很長的橫排文字:

本案依政府採購法第○○條規定辦理後續相關事宜......

壓縮之後,每一個字都會變得非常小。

但如果我先把這一行裁下來:

┌────────────────────────────────────┐
│ 本案依政府採購法第○○條規定辦理...  │
└────────────────────────────────────┘

再送進辨識模型,

模型就能把大部分解析度用在真正需要辨識的文字上。

所以第一層解決的是:

Where?字在哪裡?

還沒有開始回答:

What?它到底寫了什麼?


第二層:Heron —— 這一塊到底是什麼?

CRAFT 找出文字之後,新的問題馬上出現。

假設我現在拿到 100 個文字框。

那它們是什麼?

有些可能是:

正文。

有些是:

表格。

有些是:

頁眉。

有些是:

頁尾。

旁邊甚至可能還有圖片。

所以第二層我用了:

docling-layout-heron

來做版面分析。

它的工作可以理解成:

判斷頁面上的區域屬於什麼類型。

例如:

┌──────────────────────────────┐
│          HEADER              │
├──────────────────────────────┤
│                              │
│        TEXT                  │
│                              │
├──────────────┬───────────────┤
│              │               │
│    TABLE     │    PICTURE    │
│              │               │
├──────────────┴───────────────┤
│          FOOTER              │
└──────────────────────────────┘

這一步不是為了認字。

而是為了決定:

這個區域後面該走哪條路。


到這裡,Pipeline 第一次開始分岔

如果 Heron 判斷這是一塊:

一般正文

那就可以直接準備送去文字辨識。

但如果判斷是:

表格

事情就不能這麼簡單。

因為表格最重要的並不只是「裡面有哪些字」。

還有:

這些字屬於哪一格。

所以表格會進入第三層。


第三層:表格網格建構

假設我們有這樣一張表:

項目 數量 金額
電腦 10 300,000
螢幕 20 160,000

如果 OCR 最後只吐出:

項目
數量
金額
電腦
10
300000
螢幕
20
160000

雖然每個字可能全部認對,

但對程式來說:

結構其實已經不見了。

它不知道:

300000

到底是電腦的金額,

還是螢幕的金額。

所以表格區域會另外進行:

格線偵測 → 網格建構 → cell 還原

包括跨欄的儲存格也需要考慮。

最後的目標不是:

「這裡有九段文字。」

而是:

row 1
  col 1 = 項目
  col 2 = 數量
  col 3 = 金額

row 2
  col 1 = 電腦
  col 2 = 10
  col 3 = 300000

這就是為什麼:

OCR 與 Table Recognition 其實不能完全當成同一件事。


接著才輪到真正的文字辨識

到目前為止:

CRAFT 找到了文字。

Heron 知道它屬於正文、表格還是其他區域。

表格也已經把 cell 建好。

現在終於可以開始問:

「這張小圖到底寫了什麼?」

這一層我使用的是:

PaliGemma2-3B

搭配 LoRA。

但有趣的是:

我最後也不是只有一組 LoRA。

而是三組:

PaliGemma2-3B Base
        │
        ├── 橫排 LoRA
        │
        ├── 直排 LoRA
        │
        └── 關鍵欄位 LoRA

三組 LoRA 共用同一份 Base Model。

這件事情後面還會非常重要。

因為一開始如果每組 LoRA 都各自載一份 Base,

GPU 記憶體會直接被吃掉非常大一塊。

後面做到單卡最佳化的時候,

我就是在這裡找到了一個很大的記憶體浪費來源。

但這個坑我們留到後面再談。


為什麼要分橫排跟直排?

這其實是政府文件很常見的一個問題。

例如正文:

本案經本會審查通過

很明顯是橫排。

但表格裡可能出現:

預
算
科
目

同一套文件裡兩種方向同時存在。

因此在 crop 準備送進 PaliGemma2 之前,

系統還要先判斷:

這塊是橫排還是直排?

再決定交給哪一組 LoRA。

另外還有一些我特別在意的欄位,

例如特定的重要資訊,

會有另外的關鍵欄位辨識策略。

所以實際辨識階段並不是:

crop
 ↓
OCR

而比較像:

             ┌→ 橫排 LoRA
crop → 分流 ├→ 直排 LoRA
             └→ 關鍵欄位 LoRA


為什麼不是訓練三個完整模型?

這裡有一個工程上的考量。

PaliGemma2-3B 本身是一個數十億參數的模型。

如果今天我要:

橫排一份模型。

直排一份模型。

關鍵欄位再一份模型。

那 GPU 就得放三份完整權重。

代價非常高。

所以比較合理的做法是:

一份 Base Model
+
三組很小的 LoRA Adapter

需要哪個任務,

就使用對應的 Adapter。

因此:

任務專門化,不代表一定要複製整個模型。

這也是後面我把常駐權重記憶體降下來的重要基礎。


辨識完還不能直接輸出

到這裡看起來好像結束了。

其實還沒有。

模型輸出的結果還要經過一整串後處理。

例如:

重複消除。

有時候不同偵測框可能覆蓋到同一段文字。

如果全部保留:

環境部
環境部

最後文件就會出現重複內容。

另外還有:

前導點處理。

像目錄或預算文件裡常見:

第一章....................10

以及:

標點還原。

最後還有一件非常重要的事情:

閱讀順序。

因為模型實際上一塊一塊辨識。

最後必須重新知道:

哪一塊是第一段?
哪一塊接第二段?
表格應該插在哪裡?

否則就會出現 Day 2 講過的情況:

每一塊都認對,但整份文件讀起來是亂的。


所以完整流程其實長這樣

如果簡化成一張圖:

PDF / Image
    │
    ▼
200 dpi 頁面影像
    │
    ▼
CRAFT
文字偵測
「字在哪裡?」
    │
    ▼
Heron
版面分析
「這是正文、表格還是圖片?」
    │
    ├───────────────┐
    │ 正文          │ 表格
    ▼               ▼
文字框          格線偵測
                網格建構
                    │
    └──────────┬────┘
               ▼
        crop 指派與切分
               │
               ▼
        判斷辨識策略
        ┌──────┼──────┐
        ▼      ▼      ▼
      橫排    直排   關鍵欄位
      LoRA    LoRA    LoRA
        └──────┼──────┘
               ▼
         PaliGemma2
          文字辨識
               │
               ▼
           後處理
    重複 → 標點 → 閱讀順序
               │
               ▼
文字 + 座標 + 表格結構

看到這裡,應該就比較能理解:

為什麼我最後做的不是「一個 OCR 模型」。

而是一條 Document AI Pipeline。


有趣的是,大部分時間其實花在最後一層

我後來實際量測整套系統。

以目前使用的設定來看,

前面的:

文字偵測 + 版面分析 + 表格結構

加起來中位時間大約:

0.74 秒。

而真正的辨識階段大約:

4.15 秒。

也就是說:

整條 Pipeline 裡,

真正最重的部分依然是:

PaliGemma2 的文字生成。

這件事情後來直接影響了我很多最佳化方向。

例如:

如果我要提升整體吞吐量,

花很多時間把一個 0.1 秒的步驟再砍一半,

實際上幫助很有限。

反而應該優先處理:

辨識模型、GPU 記憶體與 Concurrent。

這也是後面單卡最佳化會一直圍繞 PaliGemma2 的原因。


那 CRAFT 為什麼還值得特別研究?

雖然它不是最慢的部分,

但 CRAFT 曾經是:

GPU 記憶體的大戶。

而記憶體在這個專案裡非常重要。

因為我不是只想做到:

「一張文件可以成功跑完。」

我要的是:

同一張 24GB GPU 能不能讓多個使用者一起用?

只要前面的 CRAFT 多吃幾 GB,

後面就可能少一個 PaliGemma2 的並行名額。

所以後來我做了一件很有趣的實驗:

把 CRAFT 的 Canvas 從比較大的設定一路往下調,

觀察:

到底可以少吃多少 VRAM,又不會開始漏字?

結果峰值記憶體最後降了非常多。

這部分後面會單獨寫一篇。


Day 3 小結

今天最重要的其實不是記住:

CRAFT、Heron、PaliGemma2。

而是理解這個拆法:

找得到、分得對、讀得準,是三個不同問題。

CRAFT 解決:

字在哪裡?

Heron 解決:

這是什麼區域?

表格層解決:

文字彼此是什麼結構?

PaliGemma2 解決:

這張小圖到底寫了什麼?

後處理再負責:

怎麼把所有結果重新組成一份可以使用的文件。

也因為我把問題拆開,

後面才有辦法分別去量:

哪一層不準?

哪一層太慢?

哪一層太吃 GPU?

而不是看到最後 OCR 錯了,只知道一句:

「模型好像不夠好。」

下一篇 Day 4,我想再往下問一個很自然的問題:

既然 VLM 已經這麼強,為什麼不能直接整頁辨識?

我會實際從解析度、長寬比與文字尺寸去看:

當一整頁公文被塞進模型固定的輸入尺寸後,到底發生了什麼事。

也會開始碰到後面非常關鍵的一個問題:

同一個模型,為什麼你怎麼切圖片,可能比你換模型還重要?


上一篇
Day 2|OCR 到底怎樣才算「準」?99% 準確率可能什麼都沒告訴你
系列文
TW-OCR:高準確率繁體中文公文辨識與版面重建系統3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言