検証できるバウンディングボックスを返すOCR API
OCR APIの多くはバウンディングボックスを返します。ただし座標系はまちまちで、ボックスが教えてくれるのは「どこか」だけ。「どれだけ確かか」までは分かりません。ソース座標を扱う開発者向けのガイドです。各値がページ上で実際にどれだけ見つかったかを示すマッチ率についても解説します。
バウンディングボックスは、OCRを検証するための手がかりです。素の文字列は、モデルが何を読んだつもりかを示すだけ。一方ボックスは、それをページ上のどこで読んだのかまで示してくれるので、あなた(あるいはレビュー担当者やコード)は、その値を鵜呑みにせず原本と突き合わせて確認できます。請求書、経費、KYC、記録管理といった監査の対象になる場面にOCRを組み込むなら、「モデルが total: 2,045 を返した」だけでは足りません。その 2,045 がどのピクセルから来たのかを指し示せる必要があります。
幸い、主要なOCR APIのほとんどはバウンディングボックスを返してくれます。やっかいなのは、いざ実装を始めると効いてくる3つの点で各社が違うことです。すなわち座標系、(生テキストだけでなく)構造化フィールドまで得られるか、そして値ごとの信頼度が実際に何を測っているか、の3つです。本ガイドではこの3点を順に解説し、ソース座標に文字カバレッジに基づくマッチ率まで加えたOCR APIが、実際にどんなものかをお見せします。
ほとんどのOCR APIはボックスを返す——違いはここにある
Google Cloud Vision、Tesseract、Amazon Textract、Azure AI Document Intelligence は、いずれもテキストと一緒にジオメトリ(形状情報)を返します。違いが出るのは、座標系、構造化フィールドまで得られるのか生テキスト+レイアウトだけなのか、そして信頼度の数値が何を意味するのか、という点です。これは宣伝文句ではなく検証済みの事実なので、自分のスタックでどれだけの統合作業が必要かを見積もる材料に使ってください。
| API | 座標系 | 構造化フィールド | 値ごとの信頼度 |
|---|---|---|---|
| Google Cloud Vision | ソース画像のピクセル単位の boundingPoly 頂点 | テキスト+ジオメトリのみ(構造化されたキー・バリューは別製品の Document AI) | 単語/シンボルごとの認識信頼度(0〜1) |
| Tesseract | ピクセル単位の hOCR / TSV ボックス(セルフホスト、APIなし) | なし——生テキスト+レイアウト | 単語ごとの認識信頼度(0〜100) |
| Amazon Textract | ページの幅・高さに対して0〜1で正規化された BoundingBox(+ Polygon) | AnalyzeDocument によるフォーム/テーブル、AnalyzeExpense による領収書 | ブロックごとの認識信頼度(%) |
| Azure Document Intelligence | ピクセル(画像)またはインチ(PDF)単位のバウンディングポリゴン | 事前構築/カスタムモデル | 単語ごとの認識信頼度 |
| space-ocr | 0〜1000で正規化された bbox(+回転対応の vertices) | 組み込みテンプレート+カスタムフィールド、明細行対応 | match_ratio——値の文字のうちページ上で見つかった割合——+ bbox_source |
注目したい点が2つあります。1つめは、座標の単位は使い回せないこと。Vision/Tesseract/Azure のピクセルボックスは送信した画像そのものに紐づきますが、正規化されたボックス(Textract、space-ocr)はリサイズしても有効なままです。2つめは、信頼度の列が指すものが各社で違うこと。ほとんどのAPIは認識信頼度(モデルがどれだけ自信を持っているか)を返しますが、これは、返された値がページ上で実際にどれだけ見つかったかを測ることとは別物です。
ボックスがどう導き出されるかは、その形式と同じくらい大切です。 space-ocr では、言語モデルが返すのは各フィールドのテキストと、どの単語トークンを使ったかのヒントだけで、ボックスそのものは一切返しません。エンジンはそのテキストを、ビジョンOCRがページ上で実際に検出したシンボルと文字単位で突き合わせます。だからこそボックスは、それらの文字が見つかった実際のピクセルに収まり、各値には見つかった度合いを示す match_ratio が付きます。トークンのヒントはノイズを含むことがあり(繰り返し行どうしで取り違えることもあります)、そのため鵜呑みにはせず、列・行の整合性チェックで裏を取ります。これこそ、モデルが主張するだけの座標と、ページに照らして検証し直した座標との違いです。
space-ocr が値ごとに返すもの
抽出された各値には4つの情報が付いて返るので、座標が「ただ信じるしかない数字」で終わることはありません。
bbox— 0〜1000で正規化されたグリッド上の、整数からなる軸並行矩形{ xmin, ymin, xmax, ymax }(0,0 = 左上、1000,1000 = 右下)。画像のピクセルサイズに依存しません。vertices— 書類の傾きに沿った回転対応ボックスを形作る、順序付きの4点(左上、右上、右下、左下)。傾いたスマホ写真でもきれいに枠が付きます。match_ratio— その値の文字のうち、実際にページ上で見つかった割合(0〜1)。フィールドは 0.85以上 で確実にマッチしたものとして扱われ、1.0はすべての文字が見つかったことを意味します。bbox_source— 座標がどう導き出されたかを示すラベル(例:文字照合の経路を表すvision_symbol_match、マッチ率がしきい値を下回ったときのlow_confidence)。
{
"total": {
"value": "2,045",
"bbox": { "xmin": 381, "ymin": 803, "xmax": 500, "ymax": 825 },
"vertices": [
{ "x": 380, "y": 804 }, { "x": 500, "y": 801 },
{ "x": 500, "y": 823 }, { "x": 381, "y": 826 }
],
"match_ratio": 1.0,
"bbox_source": "vision_symbol_match"
}
}ピクセルか、正規化か?一度変換しておけば、リサイズで崩れなくなる
OCR統合でよく起きるバグの1つが、ピクセル座標がアップロードした画像そのものに紐づいてしまうことです。保存のためにリサイズや再圧縮をしたり、EXIFの回転フラグを見落としたりすると、重ねたボックスがずれたり、切れたり、別のテキストに乗ったりします。正規化座標なら、この手のバグをまるごと避けられます。0〜1000のボックスは、同じページをどう描画しても、その上にそのまま当てはまるからです。
表示中の画像にボックスを描くときは、一度だけ変換します。
- SVGオーバーレイ — SVGに
viewBox="0 0 1000 1000"を与え、bbox/verticesをそのまま描きます。 - 絶対配置のdiv —
leftPct = xmin / 1000 * 100、topPct = ymin / 1000 * 100、widthPct = (xmax - xmin) / 1000 * 100、heightPct = (ymax - ymin) / 1000 * 100。 - ピクセルへ戻す —
pixel_x = bbox_x / 1000 * image_width、pixel_y = bbox_y / 1000 * image_height。
エンジンは読み込み時にEXIFの向きも適用するので、返ってくる座標は最初から表示画像と一致しています。回転したスマホ写真(向き 6/8)でも、あなたの側で補正処理をかける必要はありません。
curl -s https://api.space-ocr.com/ocr/fields \
-H "Authorization: Bearer $SPACE_OCR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "https://example.com/receipt.jpg",
"imageType": "url",
"fields": [
{ "name": "vendor", "type": "string" },
{ "name": "total", "type": "string" }
]
}'信頼度スコアとマッチ率は、別物です
ここは押さえておく価値のある区別です。ほとんどのOCR APIが用意しているのは認識信頼度で、これはフォントの鮮明さや画質などをもとに、エンジンが自分の読み取りにどれだけ自信があるかを表す数値です。役には立ちますが、いわばモデルが自分の答案を自分で採点しているようなもの。これに対してマッチ率は、外側の事実を測ります。モデルが返した値の文字のうち、ページ全体のOCRが検出したシンボルの中で実際に見つかったのは何文字か、です。値は認識信頼度付きで返ってきても、ページ上の何にも一致しないことがあり得ます。低い match_ratio は、まさにそうしたケースを捉えます。これをゲートとして使いましょう。match_ratio < 0.85 でソートやフィルタをかければ、すべてを見直さなくても、人の目で確認すべき一握りの値だけを浮かび上がらせられます。
検証したら、OCRを再実行せずにクエリする
座標は、データが残り続けてこそいちばん役立ちます。POST /upload で画像をシートに投入し、あとは GET /view でサーバー側からクエリするだけです。where、sort、select、limit、offset を使って、たとえば match_ratio が低い行や total >= 40000 の行をまとめて取り出す、といった操作が、OCRの再実行も追加課金もなしに行えます。返ってくる各値は bbox/vertices をそのまま保持します(boxes=0 を付ければ外して軽いペイロードにもできます)。検証ワークフローの詳しい解説は、バウンディングボックスによるOCRの検証とOCR監査証跡をご覧ください。
APIから検証可能なバウンディングボックスを得る手順
- フィールドをリクエストするimageType を 'url' または 'base64' にして画像を /ocr/fields へPOSTし、templateId か独自の fields 配列のどちらかを指定します。エンジンが受け付けるのはラスター画像(JPEG、PNG、GIF、BMP、TIFF、WebP)です。
- 座標を読む各値は、0〜1000グリッド上の bbox { xmin, ymin, xmax, ymax }、回転対応の4頂点、match_ratio、bbox_source を返します。
- 重ねる、または変換するSVG の viewBox '0 0 1000 1000' でボックスを描くか、pixel_x = bbox_x / 1000 * image_width でピクセルに変換します。EXIF回転はすでに適用済みなので、ボックスは表示画像と一致します。
- マッチ率でゲートするmatch_ratio が0.85以上なら確実にマッチしたものとして扱い、それを下回るものは、すべてを見直す代わりに人の目で確認すべきものとして浮かび上がらせます。
- 保存してクエリする/upload で画像をシートに投入し、GET /view(where、sort、select)でクエリします——座標は保持され、OCRの再実行も追加料金もありません。