space ocr
指南文章價格文件

具備稽核軌跡的文件 OCR

大多數 OCR 只丟給你一堆得照單全收的文字。space-ocr 會為每個數值附上出處:data.cells[path] 中的 box 與 quad 座標、支撐這次比對的 evidence,而需要人工過目的路徑則彙整在 data.review.flagged 裡。

從文件中擷取資料,做個展示很容易,要讓人信得過卻很難。一個模型讀了一張發票,回傳 total: 2,045,於是你面對一個任何信心分數都無法真正回答的問題:這到底是頁面上實際印著的數字,還是模型自己生出來的? 如果只是臨時查一筆資料,這倒無所謂。但換成會計、理賠處理、法遵合規,或任何你日後得接受稽核的場景,「相信模型就對了」根本稱不上是一種控管。

稽核軌跡正好補上這一塊。每個欄位回傳的不再只是一個光禿禿的數值,而是附帶一個已驗證的頁面位置——這樣一來,人(或另一套系統)就能直接跳到該數值被讀取出來的那組像素加以確認。這正是「一個答案」與「一個你能站得住腳的答案」之間的差別。

親眼看看:每個數值都回溯得到源頭

把游標移到下方任一欄位上。收據上那個框,就是該數值被讀取出來的位置——每個欄位也都帶著與這處位置對應的查核狀態。

Receipts with extracted-field bounding boxes
Verified fields
KINSHO · 合計 2,045
ライフ · 合計 4,286

Each value with a box carries a verified on-page location — in data.cells[path], that is box + 4-point quad + evidence.match_ratio — on a 0–1000 normalized grid (0,0 top-left → 1000,1000 bottom-right), the same shape the live API returns. Hover a field to trace it back to the pixels it came from.

一個「已驗證的數值」實際帶著什麼

站得住腳的結果,不是一個數值外加一個分數。POST /ocr/fields 會把答案拆成幾個層次,每一層都能分別儲存、查詢與引用:

  1. data.values — 讀到了什麼。就是你請求的那套結構,不摻任何保留鍵,可以直接寫進資料庫。
  2. data.cells[path].box 與 .quad — 從哪裡讀到的。box 是與座標軸對齊的矩形 { xmin, ymin, xmax, ymax },落在 0–1000 正規化的網格上(0,0 = 左上角,1000,1000 = 右下角);quad 是四個有序頂點,由於系統從不做傾斜校正,它會一路跟著頁面的傾斜角度。路徑語法全程一致:total、items[0].price。
  3. data.cells[path].evidence — 憑什麼這樣認定。text_match 就是字元比對本身,source 說明座標是怎麼定出來的,match_ratio 是該數值的字元中在頁面上定位到的比例(≥ 0.85 視為高信心比對成功),printed_text 則是 OCR 在那組座標上讀到的字元,可用來與 values 做精確字串比對。
  4. data.cells[path].verified 與 .review — 能不能不經人工就直接採用。verified 是一個判定,而不是字元分數:只要 review 帶著任何理由就是 false;跑過查核且沒有任何標記時是 true;沒有任何標記、但根本沒有可比對的對象時則是 null — 例如整列的合併框只有幾何資訊。review.reasons 一律是陣列,依排序給出,第 0 項是主要理由。
  5. data.review.flagged — 留給人看的待辦清單。每一項都是一組 { path, reasons },要覆核的筆數就是 flagged.length。
  6. data.normalized — 把頁面上的寫法和拿來計算的值分開。只有在某個欄位宣告了純量型別(或帶 pattern/enum 的 string)時才會出現,它把同一份讀值解析成該型別,而完全不動到 values。

因為位置與證據是跟著數值一起回傳的,結果就不再是個黑盒子。你可以把框畫出來、引用路徑與座標,或重新查核某個被標記的欄位,完全不必再跑一次 OCR。

✓ Verified

這些座標並不是直接採信模型講的。 語言模型會回傳每個數值的文字——以及它用到了哪些 word token 的提示——但從不回傳框本身。接著引擎會拿那段文字,去和視覺 OCR 在頁面上實際偵測到的符號做字元比對,讓框落在那些字元真正被找到的像素上,並為每個數值算出一個比對比例:它的字元中實際被定位到的占比。模型給的 token 提示可能有雜訊——在重複的列之間有時還會張冠李戴——所以引擎會用欄與列的一致性檢查去驗證它們,而不是盲目採信。重點不在於 AI 不會出錯,而在於與頁面對不上的數值會被擺到待覆核清單上,而不是悄悄放行。遇到根本沒有比對對象的項目——整列的合併框只有幾何資訊——該格會如實回報 verified: null,而不是謊稱通過。

點一下數值,直接落在像素上

在 App 裡,這變成了一種互動:點任一格,原始圖片就會把該數值所來自的那個框精準地標亮出來,還附上放大裁切與一條連接線。要抽查一整批資料,這是最快的方式——你的視線直接落到那個點上,而不必把整份文件從頭掃一遍。

點任一格 → 對應的區域就會在原始圖片上亮起來。

修改紀錄同樣可稽核

稽核軌跡不只關乎機器的輸出——也關乎人到底改了什麼。當你編輯一格時,space-ocr 會把你的修改與原始 OCR 數值分開保存。Original 提示框永遠會顯示引擎一開始讀到的內容,讓審核者能把機器的數值與人工覆寫的值並排對照。

編輯某一格後,原始的 OCR 數值會被保留在 Original 提示框底下。

它就在 API 裡,附在每個數值上

這並不是只有 UI 才有的功能。POST /ocr/fields 回傳的 data.cells 是一份以路徑為鍵的扁平對照表(total、items[0].price),每一項都帶著 box、quad、verified、review 與 evidence。data.review.flagged[].path 用的是同一套路徑語法,所以從一筆待覆核的項目可以直接查到它自己的座標。當你用 GET /view 查詢已儲存的試算表時,這份對照表預設就會一起帶回來——加上 boxes=0 只會拿掉列裡的 cells 對照表,values、review 與 image 照樣回傳。

POST /ocr/fields → 回應(節錄)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{
  "status": "success",
  "data": {
    "values": {
      "total": "2,045",
      "items": [
        { "qty": "2", "price": "780" }
      ]
    },
    "cells": {
      "total": {
        "box": { "xmin": 595, "ymin": 974, "xmax": 781, "ymax": 1000 },
        "quad": [
          { "x": 594, "y": 975 }, { "x": 781, "y": 972 },
          { "x": 781, "y": 998 }, { "x": 595, "y": 1000 }
        ],
        "verified": true,
        "review": null,
        "evidence": {
          "text_match": true,
          "source": "vision_symbol_match",
          "match_ratio": 1.0,
          "printed_text": "2,045"
        }
      },
      "items[0].price": {
        "box": { "xmin": 693, "ymin": 640, "xmax": 781, "ymax": 668 },
        "quad": [
          { "x": 693, "y": 641 }, { "x": 781, "y": 640 },
          { "x": 781, "y": 667 }, { "x": 693, "y": 668 }
        ],
        "verified": false,
        "review": { "reasons": ["text_mismatch"] },
        "evidence": {
          "text_match": false,
          "source": "vision_symbol_match",
          "match_ratio": 0.67,
          "printed_text": "180"
        }
      }
    },
    "review": {
      "unit": "field",
      "flagged": [
        { "path": "items[0].price", "reasons": ["text_mismatch"] }
      ],
      "by_reason": { "text_mismatch": 1 }
    },
    "normalized": { "total": 2045 },
    "image": { "width": 1654, "height": 2339 }
  }
}

evidence.source 會告訴你每個座標是怎麼定出來的——vision_symbol_match 是常見的字元比對路徑(會帶著它真正的 match_ratio),token_id 表示用到了 word-token 提示。這是一份你可以記錄、過濾,或呈現給審核者看的中繼資料(metadata)。比對薄弱的情況並不會藏在這個鍵裡:它會以 low_ratio、weak_source、low_ocr_confidence 這類代碼出現在 review.reasons 中,同一條路徑也會列在 data.review.flagged 裡。這些理由代碼屬於 API 合約詞彙——請依代碼分支處理,並為目前還不認得的代碼留一則通用訊息。

實務上如何驗證一個數值

  1. 打開擷取結果
    打開試算表,或呼叫 GET /view——每個數值都由一條路徑定址,data.cells[path] 帶著它的 box、quad、review 與 evidence。
  2. 點一下該數值
    點該格,就能在原始圖片上標亮它被讀取出來的確切區域。
  3. 讀取證據與待覆核清單
    match_ratio 為 1.0 表示每個字元都被定位到了,≥ 0.85 即視為高信心比對成功。引擎無法定論的數值,會連同 low_ratio、text_mismatch 之類的理由一起列在 data.review.flagged 裡。
  4. 必要時加以修正
    編輯該格即可覆寫它——為了稽核軌跡,原始的 OCR 數值會被保留在 Original 提示框底下。
什麼是 OCR 稽核軌跡?
稽核軌跡意味著每個擷取出來的數值,都能回溯到它在原始文件上的確切位置。在 space-ocr 中,每個數值都能用路徑在 data.cells 裡查到,那裡有它的 box、跟隨頁面傾斜的四點 quad,以及支撐這次比對的 evidence,因此結果可以被引用、被重新查核,而不是只能照單全收。
AI 會不會乾脆把邊界框給編出來?
模型從不回傳座標——它只回傳數值的文字,外加它用到了哪些字詞的提示。接著引擎會拿那段文字,去和視覺 OCR 在頁面上實際偵測到的符號做字元比對,並回報一個 match_ratio,說明其中有多少被找到了。模型給的 token 提示同樣不會被盲目採信——它們會與欄、列的一致性交叉查核——因此一個框反映的是某個數值的字元真正被找到的位置,而不是模型「以為」它們所在的位置。這套比對抓的是不一致:與頁面對不上的數值會進入 data.review.flagged,而不是悄悄放行。但它並不能證明這個數值是對的——兩套引擎有可能在同一處誤讀上取得一致——所以請把 required、enum、pattern 這類你自己宣告的規則一併跑起來。
回傳的座標是以像素為單位嗎?
API 回傳的是一個 0–1000 正規化的網格(0,0 為左上角,1000,1000 為右下角),與圖片的解析度無關。換算成像素的公式為 pixel_x = box.xmin / 1000 × data.image.width。data.image 是實際讀取時的頁面尺寸——已依 EXIF 擺正,必要時還做過縮放——所以請以它為準,而不是你上傳的那個檔案。
驗證會額外收費,或是會重跑一次 OCR 嗎?
不會。座標是標準回應的一部分,而用 GET /view 查詢已儲存的試算表,絕不會重跑 OCR,也不會產生任何費用。加上 boxes=0 只會拿掉列裡的 cells 對照表,values、review 與 image 仍會照常回傳。

拿你自己的文件來試試看

免費方案——每月 100 點數,免綁信用卡。每個數值回傳時都附帶它在頁面上的位置。

相關