space ocr
指南文章價格文件
developer

把一頁掃描件變成可以核對的 Markdown

把掃描件轉成 Markdown 很容易做壞:標題被壓平、表格變糊,也說不清哪一行來自哪裡。這是一份關於保留版式的 Markdown OCR 的開發者指南——每個元素都帶回座標與 text_verified。

7 分鐘閱讀· 2026-07-28

把掃描件轉成 Markdown,通常出於兩個理由,而這兩者要的東西並不完全一樣。

一是發布:你想把文件放進 Wiki、文件站或儲存庫,並且希望標題還是標題。二是餵給模型:多數 LLM 流程以 Markdown 為輸入,因為語法本身承載結構——## 告訴模型這裡是章節邊界,管線表格告訴它這些儲存格屬於同一列。純文字會把這些資訊丟掉。

失敗的方式也一樣。標題被壓成段落,文件站就成了一堵牆,檢索用的分塊也會在錯誤的位置斷開。而且在這兩種用途裡,你拿到的通常只是一整串 Markdown,沒有辦法追問這一行來自頁面的哪裡

「保留版式」真正要保留的東西

一個可用的 Markdown 轉換,需要正確完成四個彼此獨立的判斷:

  1. 閱讀順序 —— 多欄排版不能交錯。這是樸素 OCR 輸出最先崩掉的地方:引擎按偵測順序輸出段落,而不是人閱讀的順序。
  2. 區塊型別 —— 這一行是標題、清單項、引用,還是普通段落。在掃描件上,僅憑字級並不可靠。
  3. 表格結構 —— 哪些儲存格屬於同一列,哪一列是表頭,儲存格折成兩行時怎麼處理。
  4. 不掉東西 —— 沒人會注意到的失敗。段落悄悄消失了,Markdown 看上去依然完好。

第四點最棘手,因為它在產物裡是隱形的:掉了 5% 的轉換結果,讀起來完全通順。

一次呼叫,以及回傳什麼

POST /ocr/markdown 接收一張影像,同時回傳組裝好的 Markdown 字串,以及組裝它所用的元素。

1
2
3
4
5
6
7
curl -X POST https://api.space-ocr.com/ocr/markdown \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://example.com/report.jpg",
    "imageType": "url"
  }'
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
{
  "status": "success",
  "data": {
    "markdown": "# 季度報告\n\n營收年增。\n\n| 項目 | 金額 |\n| --- | --- |\n| 營收 | 12,000 |",
    "review_summary": {
      "unit": "element",
      "total": 8,
      "boxed": 8,
      "text_verified": 7,
      "text_mismatch": 1,
      "needs_review": 1,
      "flagged": [{ "path": "elements[3].cells[1]", "reason": "text_mismatch" }],
      "recovered_blocks": 0,
      "token_coverage": 1.0
    },
    "elements": [
      {
        "type": "heading",
        "level": 1,
        "text": "季度報告",
        "bbox": { "xmin": 60, "ymin": 48, "xmax": 520, "ymax": 92 },
        "vertices": [{ "x": 60, "y": 48 }, "…"],
        "bbox_source": "token_id",
        "text_verified": true
      }
    ]
  }
}

markdown 是你直接貼進文件站的字串。讓結果可核對的,是 elements

每個元素,以及表格裡的每個儲存格,都帶有 0–1000 正規化網格上的 bboxvertices,所以輸出裡的一個標題可以對映回原圖上的一個矩形。正規化在工程上很重要:縮放之後位置依然對得上,產生縮圖後框還是準的。

元素型別如你所料:heading(帶 level)、paragraphlist_itemblockquotecode_blockthematic_breaktable。表格帶有 rowscolscells[],每個儲存格有自己的 rowcolheadertext 和座標——你可以直接繪製自己的表格,而不必把管線語法再解析回來。

text_verified:抓住「擅自改寫」的那面旗

讀頁面的語言模型會悄悄地「改好」內容:把日期正規化、修掉它認為的錯字、展開縮寫。做摘要沒問題;但對於要入庫的文件,這是看起來很稱職的資料損壞

所以每個元素都帶 text_verified。引擎取出該元素所主張的詞元,查出在這些座標上視覺 OCR 實際讀到的文字,正規化後進行比對。true 表示兩次獨立判讀一致;false 表示轉寫文字與頁面上的文字不同——值照樣回傳,但被標記出來,而不是悄悄出錯null 表示沒有可比對的依據。

它不是準確率分數,也不該被當成準確率。它只回答一個很窄的問題:這條有沒有和像素對過?

✓ Verified

悄悄的失敗是有儀表的。 review_summary.token_coverage 是回收步驟之前模型所主張的頁面詞元占比。任何沒有被任何元素主張的詞元區間,都會以 bbox_source: "unclaimed_tokens" 的段落追加回來,review_summary.recovered_blocks 記錄發生了幾次。掉了段落的轉換,會顯示在數字裡,而不是只體現在消失的內文裡。

放在流程的哪一環

做 RAG 匯入時,elements 比字串更有用。按 heading 邊界而不是字元數切塊,得到的分塊會與文件自身的章節對齊。給每個分塊保留 bbox,引用就能指向原頁面上的矩形,而不是一個頁碼。

如果是文件站或 Wiki,直接用 markdown,把元素作為審閱用的旁路資料保留。當有人對某個數字提出異議時,你可以在掃描件上把框打出來,而不是爭論。

如果你根本不需要結構——建索引、檢索、比對——Markdown 語法反而是雜訊。那正是 POST /ocr/text 的用途:同一頁以還原了閱讀順序的純文字回傳,每個區塊同樣帶 text_verified

相關文章