space ocr
ガイド記事料金ドキュメント
developer

スキャンした 1 ページを、確かめられる Markdown にする

スキャンを Markdown にする処理は、雑にやると見出しが潰れ、表が崩れ、どの行がどこから来たのか分からなくなります。要素ごとの座標と text_verified を返す、レイアウト保存 Markdown OCR の実装ガイド。

7 分で読了· 2026-07-28

スキャンした書類を Markdown にしたい理由は、だいたい二つあります。そしてこの二つは求めるものが少し違います。

一つは公開。Wiki やドキュメントサイト、リポジトリに載せたい。そのとき見出しは見出しのままであってほしい。もう一つはモデルに読ませるため。多くの LLM パイプラインが Markdown を前提にしているのは、記法が構造を運ぶからです。## は「ここが節の切れ目だ」と伝え、パイプ表は「このセルたちは同じ行だ」と伝えます。素のテキストではその情報が消えます。

失敗の仕方も共通です。見出しが段落に潰れれば、ドキュメントサイトは一枚の壁になり、検索用のチャンクは変なところで切れます。そしてどちらの用途でも、返ってくるのはたいてい 1 本の Markdown 文字列だけで、この行はページのどこから来たのかを聞く手段がありません。

「レイアウト保存」が実際に守るべきもの

使える Markdown 変換は、独立した 4 つの判断を正しく行う必要があります。

  1. 読み順 — 多段組みで文章が入り混じらないこと。素の OCR 出力が最初に崩れるのがここで、エンジンは人が読む順ではなく検出順に段落を並べます。
  2. ブロック種別 — この行は見出しか、リスト項目か、引用か、ただの段落か。スキャンでは文字サイズだけでは判断できません。
  3. 表の構造 — どのセルが同じ行か、どの行がヘッダーか、セルが 2 行に折り返したときにどうなるか。
  4. 落とさないこと — 誰も気づかない失敗です。段落が静かに消えても、Markdown は「ちゃんとして見える」。

4 番目がいちばん厄介です。ページの 5% を落とした変換結果は、読む分には完璧に読めてしまいます。

1 回の呼び出しと、返ってくるもの

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 の方です。

すべての要素、そして表の 1 セルずつに bboxvertices が 0〜1000 の正規化座標で付きます。出力中の見出しが、元画像のどの矩形から来たのかをそのまま辿れます。正規化座標である点は実務で効きます — リサイズしても位置がずれないので、サムネイルを作った後でも枠が合います。

要素の種別は素直です。headinglevel 付き)、paragraphlist_itemblockquotecode_blockthematic_breaktable。表は rows / colscells[] を持ち、各セルが自分の row / col / header / text と座標を持つので、パイプ記法を再パースせずに独自のグリッドを描けます。

text_verified — 書き換えを捕まえるフラグ

ページを読む言語モデルは、静かに「直して」しまうことがあります。日付を正規化し、誤字と思ったものを修正し、略語を展開する。要約用途なら構いません。しかし保存する文書では、それは有能そうに見えるデータ破損です。

そこで各要素に text_verified が付きます。エンジンはその要素が主張する単語トークンを取り、その座標で Vision OCR が実際に読んだ文字を引き、正規化した上で突き合わせます。true は独立した 2 つの読みが一致したという意味。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 が付きます。

関連記事