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

スキャンから、人が読む順序どおりのテキストを取り出す

POST /ocr/text で読み順を整えた本文を抽出し、必要に応じて内容だけの blocks、path キーの元画像座標、明示的な確認一覧を受け取る方法。

7 分で読了· 2026-08-31

「テキストだけ欲しい」は簡単そうに見えますが、検索インデックスを静かに壊しやすい OCR 要件です。ビジョン OCR は正しい単語を見つけても、検出順で返すことがあります。左段の 1 行目、右段の 1 行目、また左段へ、と混ざれば、エラーは出ないのに文書として読めません。

POST /ocr/text はこの問題を、フィールド抽出や Markdown 変換から切り分けます。既定の useLlm: true ではブロックを人の読み順に並べ、折り返し行をつなぎ直します。一方、文字と読み取り元の位置は Vision の観測と照合されるため、言語モデルの転写を無条件に正解とは扱いません。

リクエストの二つのスイッチ

useLlm の既定値は true です。多段組み、サイドバー、傾いたスキャン、人が読む本文ではオンのままにします。false にすると LLM 処理を使わない Vision 専用転写となり、raw OCR 順で返ります。この経路でも文書単位の検証オブジェクトが返ります。

includeBlocks の既定値は false です。全文だけなら data.values.text を使います。独自チャンク、元画像ハイライト、確認 UI が必要ならオンにします。すると内容だけの data.values.blocksdata.cells["blocks[0]"] のようなメタデータ、data.review.flagged の block path が加わります。

1 回のリクエスト

以下は確認元までたどれるよう includeBlocks を有効にした例です。認証や全パラメータは API ドキュメントで確認できます。

1
2
3
4
5
6
7
8
9
curl -X POST https://api.space-ocr.com/ocr/text \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://example.com/page.jpg",
    "imageType": "url",
    "useLlm": true,
    "includeBlocks": true
  }'
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
{
  "status": "success",
  "data": {
    "values": {
      "text": "株式会社サクラ商事\n請求書\n合計 1,451円",
      "blocks": [
        {
          "text": "株式会社サクラ商事"
        },
        {
          "text": "請求書\n合計 1,451円"
        }
      ]
    },
    "cells": {
      "blocks[0]": {
        "box": {
          "xmin": 60,
          "ymin": 48,
          "xmax": 470,
          "ymax": 92
        },
        "quad": [
          {
            "x": 60,
            "y": 48
          },
          {
            "x": 470,
            "y": 48
          },
          {
            "x": 470,
            "y": 92
          },
          {
            "x": 60,
            "y": 92
          }
        ],
        "verified": true,
        "review": null,
        "evidence": {
          "text_match": true,
          "source": "token_id"
        }
      },
      "blocks[1]": {
        "box": {
          "xmin": 58,
          "ymin": 190,
          "xmax": 510,
          "ymax": 274
        },
        "quad": [
          {
            "x": 58,
            "y": 190
          },
          {
            "x": 510,
            "y": 190
          },
          {
            "x": 510,
            "y": 274
          },
          {
            "x": 58,
            "y": 274
          }
        ],
        "verified": false,
        "review": {
          "reasons": [
            "text_mismatch"
          ]
        },
        "evidence": {
          "text_match": false,
          "source": "char_matcher_fallback"
        }
      }
    },
    "review": {
      "unit": "block",
      "total": 2,
      "boxed": 2,
      "verified": 1,
      "flagged": [
        {
          "path": "blocks[1]",
          "reasons": [
            "text_mismatch"
          ]
        }
      ],
      "by_reason": {
        "text_mismatch": 1
      },
      "coverage": {
        "recovered_blocks": 0,
        "vision_tokens": 8,
        "tokens_claimed": 8,
        "token_coverage": 1
      }
    },
    "image": {
      "width": 1654,
      "height": 2339
    },
    "source": "llm"
  }
}

役割ごとにレスポンスを読む

  • data.values.text は全文です。blocks を有効にすると同じ内容が { text } 単位の data.values.blocks にも入り、内容に座標は混ざりません。
  • data.cells[path] は元画像との対応情報です。boxquad は 0〜1000 のページ座標で、data.image.widthheight を使って処理後画像のピクセルへ戻します。verified はその位置の Vision 文字列と正規化後に一致したかという判定で、精度のパーセンテージではありません。
  • data.review.flagged は確認作業の一覧です。pathdata.cells[path] を直接開き、review.reasons で理由を確認します。
  • data.source は読み順パスなら llm、Vision パスなら vision です。

索引処理は values.text、確認画面は reviewcells を使う、と責務を分けられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { data } = await response.json();

indexDocument(data.values.text);

for (const flag of data.review.flagged) {
  const cell = data.cells?.[flag.path];
  queueForReview({
    path: flag.path,
    reasons: flag.reasons,
    box: cell?.box,
    quad: cell?.quad,
    image: data.image,
  });
}
✓ Verified

フォールバックは隠されません。 LLM の読み順処理が失敗すると、OCR 全体をエラーにせず Vision 転写を返します。その場合は data.source"vision" となり、理由は data.warning に入ります。どのブロックにも取り込まれなかった Vision token は、cells[path].evidence.source: "unclaimed_tokens" の回収ブロックとして追加されることがあり、欠落を静かに捨てません。

プレーンテキストか Markdown か

全文検索、埋め込み、差分、アクセシビリティ用フィードなど、見出しや表を型として残す必要がなければプレーンテキストが適します。構造が必要なら POST /ocr/markdown を使い、画像から Markdown へのガイドを参照してください。座標と確認情報は OCR の元画像座標でも説明しています。

  1. 読み順の経路を選ぶ
    画像を POST /ocr/text に送ります。読み順が重要なら既定の useLlm:true を保ち、raw Vision 順でよい場合だけ false にします。
  2. 必要なときだけ blocks を要求する
    元画像との対応が必要なら includeBlocks:true にし、values.blocks、path キーの cells、ブロック単位の review を受け取ります。
  3. 確認一覧を処理する
    data.review.flagged を巡回し、data.cells[flag.path] の box または quad を data.image のフレーム上に描画します。
  4. 実行経路を保存する
    本文と一緒に data.source を保存し、自動フォールバックで vision になった場合は data.warning を表示します。
/ocr/text は既定で読み順を直しますか?
はい。useLlm の既定は true で、ブロックの並べ替えと折り返し行の再結合を行います。raw OCR 順がよい場合だけ false にします。
blocks は必ず必要ですか?
いいえ。includeBlocks の既定は false です。単純な索引なら data.values.text だけで十分で、座標やブロック単位の確認が必要なときに有効にします。
verified:true なら必ず正しいですか?
いいえ。リンクされた座標の Vision 文字列と正規化後に一致したという意味で、精度スコアではありません。同じ誤読で一致する可能性も残ります。
読み順モデルが失敗するとどうなりますか?
Vision 転写へフォールバックし、data.source が vision、理由が data.warning になります。文書単位の data.review も返ります。
関連記事