Amazon Textract の機能を整理した

Amazon Textract の機能を整理した

AWS の マネージド OCR サービス Amazon Textract の9つの機能について、実際の帳票を使いながら網羅的に検証してみました。取得したい項目の決まり方や信頼度の使い方など、VLM による帳票読み取り設計の参考になるヒントが満載です。
2026.09.29

こんにちは、けーまです。

AWS 上で帳票の読み取りや OCR を行おうとすると、Amazon Textract を利用するか、あるいは Amazon SageMaker 上に専用の OCR モデルをホスティングして動かす構成が一般的です。
しかし、Textract は現状日本語に対応しておらず、一方で SageMaker で GPU インスタンス(ml.g4dn.xlarge)のエンドポイントを常時動かすと、インスタンス費用だけで月8万円前後かかります。
そのため、コストを抑えつつサーバーレスで柔軟に対応する手段として、Amazon Bedrock のVLMを活用して OCR を行うアプローチを検討する機会が増えています。

しかし、VLM に帳票を読み取らせる際、「どのような JSON 構造で出力させるべきか」「表やキーと値のペア、書類ごとの表記揺れをどうモデルに解釈・抽出させるか」という出力設計で迷うことが少なくありません。
そこで、AWS のマネージド OCR サービスである Amazon Textract が、文書構造をどのように解釈し、どのような形式で JSON を返しているかを分析して、VLM 設計の参考にしたいと考えました。

私自身はこのような背景から調査を始めましたが、本記事の内容は Amazon Textract の9つの機能や返ってくる JSON の構造、処理時間、料金を網羅的に検証したまとめになっています。
そのため、純粋に「Amazon Textract の具体的な機能や使い分けを知りたい」という方もぜひ参考にしてみてください。

公式ドキュメントのクォータのページには、対応言語が次のように書かれています。

Amazon Textract supports English, French, German, Italian, Portuguese, and Spanish text detection. Amazon Textract will not return the language detected in its output. Query detection is only available in English document detection.

引用元: Set Quotas in Amazon Textract | AWS ドキュメント

1. 検証の概要

1.1 検証した API・機能

Textract の API・機能のうち、次の9つを検証しました。
あわせて、AnalyzeDocument の5つの機能(Forms・Tables・Queries・Signatures・Layout)を組み合わせたときの動きも、31通りすべて試しました。
次の表の「できること」は、公式の料金ページと開発者ガイドの説明をもとに、短くまとめたものです。

API / 機能 できること
DetectDocumentText 文字を行・単語の単位で読み取る(OCR のみ)
AnalyzeDocument: Forms 「Full Name:Emily Carter」のようなキーと値のペアを取る
AnalyzeDocument: Tables 表を行・列・セルの単位で取る
AnalyzeDocument: Queries 英語で質問すると、文書中の該当箇所を答えとして返す
AnalyzeDocument: Signatures 署名・イニシャルの位置を検出する
AnalyzeDocument: Layout タイトル・見出し・段落・リスト・図を読む順に並べて返す
AnalyzeExpense 請求書・レシートの項目を決まった項目名で返す
AnalyzeID 米国の運転免許証・パスポートの項目を決まった項目名で返す
AnalyzeLending 住宅ローンの申請書類を種類ごとに分類し、項目を取る

引用元: Amazon Textract の料金 | AWS、Amazon Textract 開発者ガイド | AWS ドキュメント

1.2 サンプル帳票

Textract が日本語に対応していないため、サンプル帳票はすべて英語かつ米国の書式で作成しました。
氏名・住所・会社名・番号はすべて架空のもので、電話番号は架空用の 555 番、社会保障番号は実在しない 000 始まりとし、身分証には SPECIMEN の透かしを入れています。
帳票には、あえて次のような読み取りにくい要素を混ぜました。

  • 手書き風フォントで書いた記入欄やメモ

  • チェックボックスと、何も書いていない空欄

  • 縦方向・横方向に結合したセルと、表のタイトル・注記

  • 帳票に書かれたラベルとは違う言い回しの質問(「Full Name」に対して「applicant's name」と聞く)

  • 合計欄のラベル表記(請求書は「TOTAL」ではなく「Amount Due」と表記)

  • 実在しない州の運転免許証

  • 種類の違う3つの書類を1つにまとめた PDF

  • フォーム・表・署名・見出しを1枚にまとめた注文書

章 機能 帳票 形式
2 DetectDocumentText 社内メモ PNG 1ページ
3.1、3.3 Forms、Queries 会員登録申込書 PNG 1ページ
3.2 Tables 見積書 PNG 1ページ
3.4 Signatures 写真利用の同意書 PNG 1ページ
3.5 Layout 2段組みの社内報 PNG 1ページ
4 AnalyzeExpense 請求書 PNG 1ページ
5 AnalyzeID 運転免許証 PNG 1ページ
6 AnalyzeLending 給与明細・W-2・銀行明細 PDF 3ページ
7 AnalyzeDocument の組み合わせ 注文書 PNG 1ページ

1.3 検証環境と測り方

項目 値
検証日 2026年9月28日
リージョン 米国西部(オレゴン)us-west-2
AWS CLI 2.34.25
Python / boto3 3.14.6 / 1.43.102

リージョンは、料金ページの計算例と同じオレゴンを選択しました。
なお、Textract は東京リージョン(ap-northeast-1)では提供されていないため、東京リージョンからは試せません。
公式のエンドポイント一覧を見ると、アジアパシフィックで Textract が使えるのはムンバイ・ソウル・シンガポール・シドニーの4リージョンです。

引用元: Amazon Textract endpoints and quotas | AWS ドキュメント

処理時間は、boto3 で API を呼び出してから結果が返るまでの時間を、同じ帳票で3回ずつ測定しました。
画像のアップロードと、日本からオレゴンまでの通信時間を含んだ数値です。
非同期処理である AnalyzeLending は、ジョブを開始してから1秒間隔でポーリングし、完了ステータスになるまでの時間を計測しています。

各節には、同じ帳票を AWS CLI で読み込ませるコマンドを載せています。
公式ドキュメントでは、AWS CLI から Bytes で画像を渡す方法はサポートされておらず、S3 にアップロードして S3Object で渡すよう案内されています。
今回の検証では、AWS CLI v2 で画像を Base64 に変換した文字列を Bytes に渡し、すべてのコマンドが動くことを確認しました。

引用元: API Reference: Document | AWS ドキュメント

料金の日本円換算は 1ドル=150円で計算しています。

2. DetectDocumentText

DetectDocumentText は、画像や PDF から文字だけを読み取る、いちばん基本的な OCR の API です。
表やフォームの構造は解析せず、書かれている文字を行と単語の単位で返します。
公式ドキュメントによると、DetectDocumentText はページを表す PAGE、行を表す LINE、単語を表す WORD という3種類の Block を返します。
LINE は子の WORD の Id を Relationships に保持し、WORD には手書きか印刷かを示す TextType(HANDWRITING / PRINTED)が付与されます。

引用元: Lines and Words of Text | AWS ドキュメント

実際に、社内メモの画像風なものを読み込ませてみました。

社内メモのサンプル
DetectDocumentText に読み込ませた社内メモ

aws textract detect-document-text \
  --document "Bytes=$(base64 -i 01_memo.png)" \
  --region us-west-2 > detect_text.json

実行したところ、LINE が17個、WORD が95個返ってきました。
最後の行を除く16行は、手書き風フォントの「Please confirm by Friday!」も含めて正しく読み取れ、LINE の信頼度(Confidence)は99〜100を示していました。
一方で、書類末尾の署名部分には手書き風フォントで「-J. Rivera」と書かれていますが、「Rivera」が「Rívera」と誤認され、該当する単語の信頼度だけが76.7まで低下しています。

[
  {
    "BlockType": "LINE",
    // 今回のレスポンスでは、LINE の Confidence は子の WORD の平均と一致した((97.26 + 76.72) / 2 ≒ 86.99)。仕様としての記載はない
    "Confidence": 86.99358367919922,
    "Text": "-J. Rívera",
    "Id": "8e355b17-b982-4a81-b9ee-8facb55415bf",
    "Relationships": [
      {
        "Type": "CHILD",
        // この行を構成する単語(WORD)の Id リスト
        "Ids": [
          "3959f719-2e6e-4ad1-9aec-dc0f88174949", // -> 1つ目の WORD("-J.")の Id
          "6d63b943-41ad-4859-87eb-2cbfffb14d41"  // -> 2つ目の WORD("Rívera")の Id
        ]
      }
    ]
  },
  {
    "BlockType": "WORD",
    "Confidence": 97.26223754882812,
    "Text": "-J.",
    "TextType": "PRINTED",
    "Id": "3959f719-2e6e-4ad1-9aec-dc0f88174949" // 上記 LINE の Relationships.Ids[0] と一致
  },
  {
    "BlockType": "WORD",
    // 「Rivera」が「Rívera」と誤認され、この単語の信頼度だけが低下している
    "Confidence": 76.72492980957031,
    "Text": "Rívera",
    "TextType": "PRINTED",
    "Id": "6d63b943-41ad-4859-87eb-2cbfffb14d41" // 上記 LINE の Relationships.Ids[1] と一致
  }
]
レスポンスの抜粋:ページ・行・単語の書かれ方(クリックすると展開します)

レスポンスの先頭と、PAGE の Block です。

{
  "DocumentMetadata": {
    "Pages": 1  // ページ数
  },
  "Blocks": [
    {
      "BlockType": "PAGE",  // Blocks の先頭に、ページごとに1つ入る
      "Geometry": {  // 座標の書き方の例。以降の抜粋では座標情報を省略する
        // BoundingBox : 画像の水平・垂直軸に平行な長方形。切り抜き処理(crop)などに扱いやすい
        "BoundingBox": {  // 位置と大きさは、ページ全体に対する割合(0〜1)で表す
          "Width": 1.0,
          "Height": 1.0,
          "Left": 0.0,
          "Top": 0.0
        },
        // Polygon : 要素そのものの傾きに沿った4頂点の座標。斜めに傾いた文字のハイライトや傾き補正に使う
        "Polygon": [  // 4つの頂点の座標(左上 → 右上 → 右下 → 左下の順)
          {
            "X": 3.0994542044027185e-08,
            "Y": 0.0
          },
          {
            "X": 1.0,
            "Y": 0.0
          },
          {
            "X": 1.0,
            "Y": 1.0
          },
          {
            "X": 0.0,
            "Y": 1.0
          }
        ]
      },
      "Id": "471965a9-8155-425e-929f-52807ae4bbf6",
      "Relationships": [
        {
          "Type": "CHILD",  // 子の Block への参照
          "Ids": [  // このページにある LINE 17行分の Id
            "84d7af12-6560-4240-b7bf-5341aa8d3f26",
            "d4b07c05-6efc-4407-9827-8549dd42afc0",
            // ほか 15 件は省略
          ]
        }
      ]
    }
  ]
}

書類の宛先欄には「TO: All Warehouse Staff」と書かれていますが、DetectDocumentText ではキーと値の概念がないため、ラベル部分の「TO:」と宛先の内容「All Warehouse Staff」がそれぞれ独立した LINE として返ってきます。
本文の1行目「The quarterly inventory count will take place on Saturday, October 3.」の行と、その単語の例です。印刷の文章を正しく読めた例で、本文の1行が1つの LINE になり、その子に単語ごとの WORD が並びます(WORD は11個のうち最初の2つを抜粋)。

[
  {
    "BlockType": "LINE",
    "Confidence": 99.94718170166016,  // 印刷の文字なので信頼度は100に近い
    "Text": "The quarterly inventory count will take place on Saturday, October 3.",  // 本文の1行が、そのまま1つの LINE になる
    "Geometry": { ... },  // 座標情報は省略
    "Id": "1ce84df3-5652-4d34-b8ed-7d5edc157b52",
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [  // この行の WORD 11個の Id。文の先頭から順に並ぶ
          "185f42f9-699f-4595-8a85-86b97aac35f9",
          "0eeecd53-46c8-47f7-8065-61fe2a8e3251",
          // ほか 9 件は省略
        ]
      }
    ]
  },
  {
    "BlockType": "WORD",
    "Confidence": 100.0,
    "Text": "The",  // 1つ目の WORD
    "TextType": "PRINTED",  // 印刷の文字は PRINTED
    "Geometry": { ... },  // 座標情報は省略
    "Id": "185f42f9-699f-4595-8a85-86b97aac35f9"  // LINE の Relationships の Ids の1つ目と一致
  },
  {
    "BlockType": "WORD",
    "Confidence": 99.92028045654297,
    "Text": "quarterly",  // 2つ目の WORD(3つ目以降は省略)
    "TextType": "PRINTED",
    "Geometry": { ... },  // 座標情報は省略
    "Id": "0eeecd53-46c8-47f7-8065-61fe2a8e3251"
  }
]

この出力から分かったことは、次のとおりです。

  • TextType: 手書き風フォントの部分も含め、すべての単語が PRINTED(印刷)と判定された

  • Confidence: 読み違えた単語だけ、同じ行にあるもう1つの単語(97.26)より20ほど低い

フォントで再現した手書き風の文字は、Textract 側からは印刷文字として認識されていました。
本物の手書き文字をどこまで読み取れるかについては、本記事では検証していません。
誤認識が発生した箇所は信頼度の低下として明確に表れるため、単語ごとの Confidence に閾値を設定し、スコアの低いものを人手による確認へ回すフローにしておくのが確実です。

処理時間は2.04秒、1.33秒、1.32秒で、料金は1ページあたり $0.0015(約0.23円)でした。
全文のテキストデータさえ抽出できればよい用途であれば、9機能の中で最も低コストなこの API で十分に対応できます。

3. AnalyzeDocument

AnalyzeDocument は、単なるOCRにとどまらず、帳票のレイアウトや表、記入欄といった「文書の構造」まで解析する API です。

使いたい機能(フォーム解析、表抽出、質問応答、署名検出、レイアウト認識)をオプションで指定して呼び出します。どの機能を選んでも、通常の文字読み取り結果(行単位の LINE や単語単位の WORD)は最初からすべてセットで返ってくるため、「文字を取るための OCR」と「構造を解析するための API」を別々に2回呼び出す必要はありません。

3.1 Forms

Forms は、申込書のような帳票から、項目名(キー)と記入された値の組を取り出す機能です。
公式ドキュメントによると、Forms はキーと値のペアを KEY_VALUE_SET という Block で返します。
キーか値かは EntityTypes(KEY / VALUE)で識別され、キーの Block が VALUE のリレーションによって値の Block の Id を参照する構造です。
チェックボックスについては、キーがラベル文字列、値が SELECTION_ELEMENT(SelectionStatus が SELECTED または NOT_SELECTED)のペアとして返却されます。
日付については、記載されている形式のまま返ります。

引用元: Form Data (Key-Value Pairs) | AWS ドキュメント、Selection Elements | AWS ドキュメント

実際に、会員登録申込書の画像を読み込ませてみました。
記入欄の値は手書き風フォントで入力し、緊急連絡先の2項目はあえて空欄のまま残しています。

会員登録申込書のサンプル
Forms と Queries に読み込ませた会員登録申込書

aws textract analyze-document \
  --document "Bytes=$(base64 -i 02_application_form.png)" \
  --feature-types FORMS \
  --region us-west-2 > forms.json

実行した結果、キーと値のペアが15組返ってきました。

帳票のラベル 返った値 正誤
Full Name Emily Carter 正
Date of Birth 04/12/1988 正
Email emily.carter@example.com 正
Phone (空) 誤:(555) 010-2233 が入るはず
Address 1234 Maple Street 正
City / State / ZIP Springfield, IL 62701 正
Start Date 10/01/2026 正
Contact Name (空) 正(空欄)
Contact Phone (空) 正(空欄)
Standard / Student NOT_SELECTED 正
Premium SELECTED 正
Personal training SELECTED 正
Locker rental / Email newsletter NOT_SELECTED 正

Forms でキーと値を取得する場合、本来は「正解の例(氏名欄など)」のように KEY → VALUE → WORD の順に Id が数珠つなぎになります。

ところが「間違った例(今回の電話番号欄)」では、OCR で「(555) 010-2233」という文字自体は読めているにもかかわらず、VALUE のブロックに文字の参照(CHILD)が 1 つも入らず、空欄扱いになってしまいました。

2 つの JSON を見比べると、構造の違いが一目でわかります。

仮に正しく結び付いていた場合の想定(Phone: (555) 010-2233)

実際のレスポンスではなく、値の Block が電話番号の WORD と正しく結び付いていたらこうなる、という想定の形です(Id は実際のレスポンスのものを使っています)。
キー(KEY)が値(VALUE)を指し、値(VALUE)が記入された単語(WORD)の Id を指します。

[
  {
    "BlockType": "KEY_VALUE_SET",
    "EntityTypes": ["KEY"],
    "Id": "fe58803f-5ea3-45e8-8ffd-c30541631877",
    "Relationships": [
      {
        "Type": "VALUE",
        "Ids": ["4a93debd-431a-40ec-83b7-8e93b543a497"]  // -> 下の VALUE ブロックを指す
      },
      {
        "Type": "CHILD",
        "Ids": ["f73ea861-34a8-45dd-8f0e-feab28262a62"]  // -> キーの単語「Phone」を指す
      }
    ]
  },
  {
    "BlockType": "KEY_VALUE_SET",
    "EntityTypes": ["VALUE"],
    "Id": "4a93debd-431a-40ec-83b7-8e93b543a497",  // 上の KEY から指されている Id
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [
          "2c8449b3-5dd4-464b-84fb-b44c1e4c47b1"   // -> 正解なら値の単語「(555) 010-2233」を指す
        ]
      }
    ]
  },
  {
    "BlockType": "WORD",
    "Id": "2c8449b3-5dd4-464b-84fb-b44c1e4c47b1",
    "Text": "(555) 010-2233"
  }
]

間違った例(Phone: 記入があるのに空欄扱いになってしまったとき)

キー(KEY)は値(VALUE)を指しているものの、肝心の値(VALUE)の側に Relationships(単語を指す矢印)が一切ありません。

[
  {
    "BlockType": "KEY_VALUE_SET",
    "EntityTypes": ["KEY"],
    "Confidence": 80.0,  // ほかのペア(89〜96)より低い
    "Id": "fe58803f-5ea3-45e8-8ffd-c30541631877",
    "Relationships": [
      {
        "Type": "VALUE",
        "Ids": ["4a93debd-431a-40ec-83b7-8e93b543a497"]  // -> 下の VALUE ブロックを指す
      },
      {
        "Type": "CHILD",
        "Ids": ["f73ea861-34a8-45dd-8f0e-feab28262a62"]  // -> キーの単語「Phone」を指す
      }
    ]
  },
  {
    "BlockType": "KEY_VALUE_SET",
    "EntityTypes": ["VALUE"],
    "Confidence": 80.0,
    "Id": "4a93debd-431a-40ec-83b7-8e93b543a497"
    // ★ 本来あるべき "Relationships"(値の単語 CHILD)が存在せず、空欄扱いになっている!
  }
]

なお、同じレスポンスの LINE ブロックを見ると、文字自体は信頼度 95% で正しく読めていました。

{
  "BlockType": "LINE",
  "Confidence": 95.07792663574219,
  "Text": "(555) 010-2233"  // OCR では読めているが、Phone の入力枠と結びつかなかった
}
レスポンスの抜粋:キーと値・チェックボックス・空欄の書かれ方(クリックすると展開します)

会員種別の選択欄には、チェックボックスの横に「Premium」と書かれています。この選択状態は、値の Block の子が WORD ではなく SELECTION_ELEMENT になります。

[
  {
    "BlockType": "KEY_VALUE_SET",  // キーはチェックボックスの横のラベル(Premium)
    "Confidence": 94.01258087158203,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "b7c1488d-5261-4b02-b6d9-06ee73a286f5",
    "Relationships": [
      {
        "Type": "VALUE",
        "Ids": [
          "bc7b9734-cef8-431e-8cd2-044f11e178c2"
        ]
      },
      {
        "Type": "CHILD",
        "Ids": [
          "6e0f4307-92d7-4b97-978a-e4f6e6b3605a"
        ]
      }
    ],
    "EntityTypes": [
      "KEY"
    ]
  },
  {
    "BlockType": "KEY_VALUE_SET",
    "Confidence": 94.01258087158203,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "bc7b9734-cef8-431e-8cd2-044f11e178c2",
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [
          "670ccd5f-7bd5-4f3c-8d1d-8e75e0bddb0d"
        ]
      }
    ],
    "EntityTypes": [
      "VALUE"
    ]
  },
  {
    "BlockType": "SELECTION_ELEMENT",  // チェックボックス本体
    "Confidence": 91.455078125,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "670ccd5f-7bd5-4f3c-8d1d-8e75e0bddb0d",
    "SelectionStatus": "SELECTED"  // SELECTED か NOT_SELECTED
  }
]

同じレスポンスの LINE には「(555) 010-2233」という文字列が正しく含まれているため、文字自体は認識できているにもかかわらず、キーとのマッピングだけが外れてしまった状態です。
このペアの信頼度は80.0と、ほかの正常なペア(89〜96)と比べて低めに出ていました。

処理時間は2.33秒、2.32秒、3.08秒で、料金は1ページあたり $0.05(約7.5円)でした。
帳票にどんな項目が並んでいるか事前に把握できず、ラベルと値を網羅的に拾い上げたいケースに適しています。
もし取得したい項目があらかじめ決まっているなら、後述する Queries を利用したほうがコストを抑えられます。

3.2 Tables

Tables は、帳票の中の表を、行・列・セルの構造ごと取り出す機能です。
公式ドキュメントによると、Tables は表全体を TABLE、各マスを CELL として返し、結合セル・列見出し・表のタイトル・フッターも識別可能です。
結合セルは MERGED_CELL、表のタイトルは TABLE_TITLE、フッターは TABLE_FOOTER という Block として返却されます。

引用元: Tables | AWS ドキュメント

実際に、見積書の明細表を読み込ませてみました。

見積書のサンプル
Tables に読み込ませた見積書

aws textract analyze-document \
  --document "Bytes=$(base64 -i 03_quote_table.png)" \
  --feature-types TABLES \
  --region us-west-2 > tables.json

実際には、TABLE が1個、CELL が50個(10行×5列)、MERGED_CELL が5個、TABLE_TITLE と TABLE_FOOTER が1個ずつ返ってきました。

見積書の明細表では、1列目のカテゴリ名「Hardware」が3行分にまたがって縦方向に結合されています。Textract では次のように MERGED_CELL として返り、参照先の CELL から実際のテキスト(WORD)を辿ることができます。

[
  {
    "BlockType": "MERGED_CELL",  // 結合セル
    "Confidence": 90.33203125,
    "RowIndex": 2,  // 2行目から
    "ColumnIndex": 1,
    "RowSpan": 3,  // 3行分を縦に結合
    "ColumnSpan": 1,  // 列は1列のみ
    "Geometry": { ... },  // 座標情報は省略
    "Id": "95e798ce-55d1-4be2-9bb8-8c021bf0c22e",
    "Relationships": [
      {
        "Type": "CHILD",  // 結合前の CELL 3つの Id。文字が入っているのは真ん中の1つ
        "Ids": [
          "6c3f707a-b3dc-4660-87f1-82767e9e669d",
          "c7829778-3434-4160-9414-8e9565a1a7b1",
          // ほか 1 件は省略
        ]
      }
    ]
  },
  {
    "BlockType": "CELL",  // 結合前の CELL(3行目)
    "Confidence": 90.33203125,
    "RowIndex": 3,
    "ColumnIndex": 1,
    "RowSpan": 1,
    "ColumnSpan": 1,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "c7829778-3434-4160-9414-8e9565a1a7b1",
    "Relationships": [
      {
        "Type": "CHILD",  // 文字「Hardware」の WORD の Id
        "Ids": [
          "0b86217d-d7fb-4f58-900d-c263f3e7c370"
        ]
      }
    ]
  },
  {
    "BlockType": "WORD",  // 実際のテキスト
    "Confidence": 100.0,
    "Text": "Hardware",
    "TextType": "PRINTED",
    "Geometry": { ... },  // 座標情報は省略
    "Id": "0b86217d-d7fb-4f58-900d-c263f3e7c370"
  }
]
レスポンスの抜粋:表・セル・結合セル・タイトルの書かれ方(クリックすると展開します)

表全体を表す TABLE です。子として、すべてのセルと結合セル、タイトル、フッターの Id を持ちます。

{
  "BlockType": "TABLE",  // 表1つにつき1つ
  "Confidence": 99.853515625,
  "Geometry": { ... },  // 座標情報は省略
  "Id": "9cf63430-0b28-4876-bcd6-06e62993efd2",
  "Relationships": [
    {
      "Type": "CHILD",  // 子の CELL の Id(50個)
      "Ids": [
        "ebc8d1ed-6264-438c-bc69-011057befd9d",
        // 省略します
      ]
    },
    {
      "Type": "MERGED_CELL",  // 結合セルの Id
      "Ids": [
        "95e798ce-55d1-4be2-9bb8-8c021bf0c22e",
        "0e849fd0-da5b-4a56-adc8-bf5c4eca8664",
        // ほか 3 件は省略
      ]
    },
    {
      "Type": "TABLE_TITLE",  // タイトルの Id
      "Ids": [
        "cd2ddbb9-d58d-41d9-9c51-dec3a086332e"
      ]
    },
    {
      "Type": "TABLE_FOOTER",  // フッターの Id
      "Ids": [
        "dca749b3-54a4-43a2-91c6-b928057c5546"
      ]
    }
  ],
  "EntityTypes": [
    "STRUCTURED_TABLE"  // 表の種類。行と列がそろった表は STRUCTURED_TABLE
  ]
}

見積書の明細表にある1列目の列見出し「Category」のセルです。RowIndex と ColumnIndex で位置が決まり、見出しセルには COLUMN_HEADER が付きます。通常のセルには EntityTypes が付かず、合計の行のセルには TABLE_SUMMARY が付きます。

[
  {
    "BlockType": "CELL",
    "Confidence": 91.9921875,
    "RowIndex": 1,  // 行番号(1始まり)
    "ColumnIndex": 1,  // 列番号(1始まり)
    "RowSpan": 1,
    "ColumnSpan": 1,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "ebc8d1ed-6264-438c-bc69-011057befd9d",
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [
          "f77e4128-0907-4d73-b9d3-a253677a3a12"
        ]
      }
    ],
    "EntityTypes": [
      "COLUMN_HEADER"  // 見出し行のセル
    ]
  },
  {
    "BlockType": "WORD",
    "Confidence": 99.92028045654297,
    "Text": "Category",  // セルの文字は子の WORD に入る
    "TextType": "PRINTED",
    "Geometry": { ... },  // 座標情報は省略
    "Id": "f77e4128-0907-4d73-b9d3-a253677a3a12"
  }
]

表のタイトルとフッターです。子は、それぞれの文字の WORD です。

[
  {
    "BlockType": "TABLE_TITLE",  // 表の上のタイトル
    "Confidence": 99.853515625,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "cd2ddbb9-d58d-41d9-9c51-dec3a086332e",
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [
          "6ab2bd03-9b5c-4c85-a089-a8fa4048b74b",
          "00aa2102-b001-439c-b665-e27585b6a033",
          // ほか 3 件は省略
        ]
      }
    ]
  },
  {
    "BlockType": "TABLE_FOOTER",  // 表の下の注記
    "Confidence": 99.755859375,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "dca749b3-54a4-43a2-91c6-b928057c5546",
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [
          "74ab5ea4-e53b-48d5-aa7b-7316df017569",
          "7bbfa381-90a3-46f7-a49a-9a08d9d504a6",
          // ほか 9 件は省略
        ]
      }
    ]
  }
]

この出力から分かったことは、次のとおりです。

  • RowSpan: 3: 2行目から3行分(Hardware の3品目)が1つのセルとして結合されている

  • Relationships: 結合前の元となる3つの CELL を子要素として保持している

ここで注意が必要なのは、テキストデータが格納されるのは子 CELL のうち1つだけという点です。
今回の例では「Hardware」という文字列は真ん中にある3行目の CELL に格納され、2行目と4行目の CELL は空になっていました。

処理時間は2.93秒、4.72秒、1.82秒で、料金は1ページあたり $0.015(約2.25円)でした。

3.3 Queries

Queries は、帳票に対して英語で質問を投げると、その答えにあたる文字列を文書の中から探して返す機能です。
公式ドキュメントによると、Queries は質問を QUERY、それに対する回答を QUERY_RESULT という Block で返します。
QUERY には送信した質問文とエイリアス(Alias)が含まれ、ANSWER というリレーションで回答 Block を参照します。
回答が見つからない場合は、回答要素は空になります。

引用元: Queries | AWS ドキュメント

実際に、3.1 で使用した会員登録申込書に対して、5つの質問を投げてみました。
質問文には、あえて帳票上のラベルとは異なる言い回しを使っています。

aws textract analyze-document \
  --document "Bytes=$(base64 -i 02_application_form.png)" \
  --feature-types QUERIES \
  --queries-config '{"Queries":[
    {"Text":"What is the applicant'"'"'s name?","Alias":"APPLICANT_NAME"},
    {"Text":"When was the applicant born?","Alias":"BIRTH_DATE"},
    {"Text":"Which membership type was selected?","Alias":"MEMBERSHIP_TYPE"},
    {"Text":"When does the membership start?","Alias":"START_DATE"},
    {"Text":"What is the emergency contact name?","Alias":"EMERGENCY_CONTACT"}]}' \
  --region us-west-2 > queries.json

実際の処理結果は次のとおりです。

質問 帳票のラベル 答え 信頼度
What is the applicant's name? Full Name Emily Carter 100
When was the applicant born? Date of Birth 04/12/1988 99
Which membership type was selected? Membership Type(チェックボックス) Premium 100
When does the membership start? Start Date 10/01/2026 99
What is the emergency contact name? Contact Name(空欄) (答えなし) -

申込書の「Full Name」に対して「What is the applicant's name?」と質問し、手書き風フォントの氏名「Emily Carter」が正しく返ってきた例です。

[
  {
    "BlockType": "QUERY",  // 質問の Block
    "Id": "7f111ee1-4438-41fa-ae71-bd01c8dc8147",
    "Relationships": [
      {
        "Type": "ANSWER",  // 答えの QUERY_RESULT の Id を指す
        "Ids": [
          "e9b8f1a9-d81e-46f9-a554-d51d04847164"
        ]
      }
    ],
    "Query": {
      "Text": "What is the applicant's name?",  // 投げた質問文
      "Alias": "APPLICANT_NAME"  // リクエストで付けた別名がそのまま返る
    }
  },
  {
    "BlockType": "QUERY_RESULT",  // 答えの Block
    "Confidence": 100.0,
    "Text": "Emily Carter",  // 答えの文字が直接入る
    "Id": "e9b8f1a9-d81e-46f9-a554-d51d04847164"
  }
]
レスポンスの抜粋:答えが返った例と返らなかった例(クリックすると展開します)

申込書で空欄にしておいた緊急連絡先欄に対して「What is the emergency contact name?」と質問した例です。無理に値を推測せず、QUERY に Relationships が付かない形で返ります。

{
  "BlockType": "QUERY",  // Relationships がない = 答えなし
  "Id": "a633784a-464b-406a-bb1d-8966a1b36ed0",
  "Query": {
    "Text": "What is the emergency contact name?",
    "Alias": "EMERGENCY_CONTACT"
  }
}

処理時間は3.31秒、3.46秒、2.23秒で、料金は1ページあたり $0.015(約2.25円)でした。
Forms の3分の1以下の単価で済み、さらに Alias を指定することでシステム側のキー名で直接値を受け取れるため、取得したい項目が定まっている英語帳票であれば、Forms よりも Queries のほうが適しています。
なお、1ページあたりに投げられるクエリ数は、同期 API で最大15件、非同期 API で最大30件という上限があります(Set Quotas in Amazon Textract | AWS ドキュメント)。

3.4 Signatures

Signatures は、帳票のどこに署名があるかを検出する機能です。
Signatures は文書や画像内に存在する署名を検出し、その位置座標と信頼度を返却します。
料金ページでは、検出の対象として手書きの署名だけでなく、電子署名とイニシャルも挙げられています。

Analyze Document API for Signatures provides the ability to detect handwritten signatures, electronic signatures, and initials on any document or image.

引用元: Amazon Textract Features | AWS、Amazon Textract の料金 | AWS

実際に、写真利用の同意書を読み込ませてみました。

同意書のサンプル
Signatures に読み込ませた同意書

aws textract analyze-document \
  --document "Bytes=$(base64 -i 04_consent_form.png)" \
  --feature-types SIGNATURES \
  --region us-west-2 > signatures.json

実行したところ、SIGNATURE の Block が1件のみ返ってきました。
同意書の最下部にある署名欄に筆記体フォントで書かれた「Emily Carter」の署名部分です。

{
  "BlockType": "SIGNATURE",  // Text も Relationships も持たない
  "Confidence": 80.12157440185547,  // 署名である信頼度
  "Id": "2938d500-24b5-4c16-88bc-0ac986b5c3a4",
  "Geometry": { ... }  // 座標情報は省略
}

Text が含まれない: 署名の文字自体を文字認識するのではなく、「ここに署名が存在する」というバウンディングボックスのみを返す

一方で、2箇所に記載した Initials「EC」は、どちらも SIGNATURE としては検出されませんでした。
公式には Initials も検出の対象とされていますが、今回の帳票では検出されなかったことになります。
今回の帳票を見る限り、署名欄のフルネームのように一定の長さを持つ筆跡は署名として認識されたものの、2文字程度の短い Initials は署名とみなされませんでした。

処理時間は2.21秒、3.62秒、1.64秒で、料金は1ページあたり $0.0035(約0.53円)でした。
契約書や申込書において、署名欄に記入があるかどうかをまとめて一次チェックしたい用途に向いています。

3.5 Layout

Layout は、文書をタイトル・見出し・段落・図などの要素に分け、人が読む順に並べて返す機能です。
Layout は文書内の要素を10種類の Block に分類して返します。
具体的には、タイトル(LAYOUT_TITLE)、ヘッダー(LAYOUT_HEADER)、フッター(LAYOUT_FOOTER)、見出し(LAYOUT_SECTION_HEADER)、ページ番号(LAYOUT_PAGE_NUMBER)、リスト(LAYOUT_LIST)、図(LAYOUT_FIGURE)、表(LAYOUT_TABLE)、キー・値(LAYOUT_KEY_VALUE)、本文(LAYOUT_TEXT)です。
要素は人間の読書順に合わせて並び、段組み構成のページであれば左カラムを上から下まで読み切ったあとに右カラムへと進みます。

引用元: Layout Response Objects | AWS ドキュメント

実際に、2段組みレイアウトの社内報を読み込ませてみました。

社内報のサンプル
Layout に読み込ませた社内報

aws textract analyze-document \
  --document "Bytes=$(base64 -i 05_newsletter.png)" \
  --feature-types LAYOUT \
  --region us-west-2 > layout.json

実際には、次の順序で18個の要素が返却されました。

順 種類 中身
1 LAYOUT_HEADER Example Robotics Inc. / Employee Newsletter
2 LAYOUT_TITLE Fall 2026 Company Update
3 LAYOUT_SECTION_HEADER New Office Opening
4〜5 LAYOUT_TEXT 左の段の段落2つ
6 LAYOUT_SECTION_HEADER Upcoming Events
7 LAYOUT_LIST 箇条書き(子として8〜11を持つ)
8〜11 LAYOUT_TEXT 箇条書きの4項目
12 LAYOUT_SECTION_HEADER Quarterly Results
13 LAYOUT_TEXT 右の段の段落
14 LAYOUT_FIGURE 棒グラフ(中の文字 Q1 / Q2 / Q3)
15 LAYOUT_TEXT Figure 1. Units shipped by quarter
16 LAYOUT_SECTION_HEADER Welcome New Members
17 LAYOUT_TEXT 右の段の段落
18 LAYOUT_PAGE_NUMBER Page 1
レスポンスの抜粋:ヘッダー・タイトル・見出し・箇条書き・図の書かれ方(クリックすると展開します)

社内報の大見出しタイトル「Fall 2026 Company Update」の例です。LAYOUT_* の Block は Text を持たず、子の LINE に文字が入ります。

[
  {
    "BlockType": "LAYOUT_TITLE",  // 文書のタイトル
    "Confidence": 81.15234375,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "76c387ba-4787-4440-820d-34f5eff87be4",
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [
          "2e4802de-86f2-497c-affe-b5bd64262e31"
        ]
      }
    ]
  },
  {
    "BlockType": "LINE",
    "Confidence": 99.98027038574219,
    "Text": "Fall 2026 Company Update",
    "Geometry": { ... },  // 座標情報は省略
    "Id": "2e4802de-86f2-497c-affe-b5bd64262e31",
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [
          "6df1269f-deca-4ade-9dde-410308748f73",
          "249150de-0a62-457b-bd46-f1dd2ae2d284"
          // ほか 2 件は省略
        ]
      }
    ]
  }
]

社内報の「Upcoming Events」セクションにある箇条書きの例です。

[
  {
    "BlockType": "LAYOUT_LIST",  // 箇条書き全体
    "Confidence": 96.38671875,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "78670f4f-1b24-4048-8c3a-734add37e3d8",
    "Relationships": [
      {
        "Type": "CHILD",  // 項目ごとの LAYOUT_TEXT の Id(4つ)
        "Ids": [
          "5b32a3f0-9b7f-4a9a-83aa-9c7155ad0c16",
          "b4222667-f087-4b5a-8a4e-4a13d783c7fa",
          // ほか 2 件は省略
        ]
      }
    ]
  },
  {
    "BlockType": "LAYOUT_TEXT",  // 箇条書きの1項目
    "Confidence": 97.705078125,
    "Geometry": { ... },  // 座標情報は省略
    "Id": "5b32a3f0-9b7f-4a9a-83aa-9c7155ad0c16",
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [
          "88e5a8a2-2156-4c80-8afb-54f5f67bfbc9"
        ]
      }
    ]
  },
  {
    "BlockType": "LINE",  // 項目の文字
    "Confidence": 98.95352172851562,
    "Text": "- Oct 8: Safety training",
    "Geometry": { ... },  // 座標情報は省略
    "Id": "88e5a8a2-2156-4c80-8afb-54f5f67bfbc9",
    "Relationships": [
      {
        "Type": "CHILD",
        "Ids": [
          "eb54fd0c-f2b8-43e9-91ea-c8e981835805",
          "542fa62b-e162-4660-be95-45337a77c1ad",
          // ほか 3 件は省略
        ]
      }
    ]
  }
]

処理時間は2.85秒、3.82秒、2.91秒で、料金は1ページあたり $0.004(約0.6円)でした。
Tables と同時に指定した場合は、Layout 側の追加料金は発生しません。

4. AnalyzeExpense

AnalyzeExpense は、請求書やレシートに特化した API で、合計金額や日付などを決まった項目名にそろえて返します。
公式ドキュメントによると、AnalyzeExpense のレスポンスのトップレベルは ExpenseDocuments という独自の構造で、DetectDocumentText と同じ OCR の Blocks はその中に入ります。

引用元: Invoice and Receipt Response Objects | AWS ドキュメント

実際に、請求書を読み込ませてみました。
合計金額欄は一般的な「TOTAL」ではなく、「Amount Due」と記載された請求書を用意しています。

請求書のサンプル
AnalyzeExpense に読み込ませた請求書

aws textract analyze-expense \
  --document "Bytes=$(base64 -i 06_invoice.png)" \
  --region us-west-2 > expense_invoice.json

請求書の SummaryFields から主要な項目を抽出した結果がこちらです。

Type LabelDetection ValueDetection
VENDOR_NAME (なし) Sample Supply Co.
RECEIVER_NAME (なし) Example Robotics Inc.
INVOICE_RECEIPT_ID Invoice No. INV-2026-0042
INVOICE_RECEIPT_DATE Invoice Date 09/15/2026
DUE_DATE Due Date 10/15/2026
PO_NUMBER PO Number PO-7781
SUBTOTAL Subtotal $1,135.00
TAX Sales Tax (7.5%) $85.13
SHIPPING_HANDLING_CHARGE Shipping $14.37
TOTAL Amount Due $1,234.50
AMOUNT_DUE Amount Due $1,234.50
PAYMENT_TERMS Payment terms: Net 30.
OTHER Attn: Accounts Payable

請求書の合計金額欄(「Amount Due: $1,234.50」)の例です。正規化された項目名「TOTAL」と、帳票に書かれていた原文ラベル「Amount Due」、抽出された金額がセットで返ります。

{
  "Type": {
    "Text": "TOTAL",  // 決まった項目名
    "Confidence": 99.99701690673828
  },
  "LabelDetection": {
    "Text": "Amount Due",  // 帳票に書かれていたラベルがそのまま残る
    "Confidence": 99.99556732177734
  },
  "ValueDetection": {
    "Text": "$1,234.50",  // 値
    "Confidence": 99.98663330078125
  }
}
レスポンスの抜粋:全体の形・項目・明細の書かれ方(クリックすると展開します)

レスポンス全体の形です(中身は省略)。Blocks はトップではなく、ExpenseDocuments の中に入ります。

{
  "DocumentMetadata": {
    "Pages": 1
  },
  "ExpenseDocuments": [  // 請求書1通ごとの結果
    {
      "ExpenseIndex": 1,
      "SummaryFields": [  // 合計や日付などの項目
        "…"
      ],
      "LineItemGroups": [  // 明細
        "…"
      ],
      "Blocks": [  // DetectDocumentText と同じ OCR の結果
        "…"
      ]
    }
  ]
}

請求書上部に印刷された会社名「Sample Supply Co.」のように、ラベルがなく値だけが書かれている項目の例です。LabelDetection がなく、代わりに GroupProperties で売り手(VENDOR)の情報だと分かります。

{
  "Type": {
    "Text": "NAME",  // 名前。誰の名前かは GroupProperties で分かる
    "Confidence": 86.15778350830078
  },
  "ValueDetection": {
    "Text": "Sample Supply Co.",
    "Geometry": { ... },  // 座標情報は省略
    "Confidence": 86.15138244628906
  },
  "PageNumber": 1,
  "GroupProperties": [  // 同じ Id の住所などと1つのまとまりになる
    {
      "Types": [
        "VENDOR"  // 売り手(請求元)のまとまり
      ],
      "Id": "10c15a45-31b8-4235-88ed-03bd7eba3a2a"
    }
  ]
}

請求書の購入明細表にある1行目(品名「Shipping boxes (large)」、数量など)の例です。LineItemGroups → LineItems → LineItemExpenseFields の3段の入れ子になります。

{
  "LineItemGroupIndex": 1,
  "LineItems": [  // 明細の行の配列(ここでは1行目だけ抜粋)
    {
      "LineItemExpenseFields": [  // 1行の中の項目
        {
          "Type": {
            "Text": "ITEM",  // 品名
            "Confidence": 70.0
          },
          "LabelDetection": {
            "Text": "Description",
            "Geometry": { ... },  // 座標情報は省略
            "Confidence": 79.99808502197266
          },
          "ValueDetection": {
            "Text": "Shipping boxes (large)",
            "Geometry": { ... },  // 座標情報は省略
            "Confidence": 99.99697875976562
          },
          "PageNumber": 1
        },
        {
          "Type": {
            "Text": "QUANTITY",  // 数量
            "Confidence": 70.0
          },
          "LabelDetection": {
            "Text": "Qty",
            "Geometry": { ... },  // 座標情報は省略
            "Confidence": 79.99756622314453
          },
          "ValueDetection": {
            "Text": "200",
            "Geometry": { ... },  // 座標情報は省略
            "Confidence": 99.99722290039062
          },
          "PageNumber": 1
        }
        // このあとに UNIT_PRICE(単価)・PRICE(金額)・EXPENSE_ROW(行全体)が同じ形で続く(省略)
      ]
    }
  ]
}
  • Type: 帳票側の表記が「Amount Due」であっても、TOTAL と AMOUNT_DUE という両方の標準項目名に同じ値がマッピングされる

店舗名や住所もラベルの有無にかかわらず VENDOR_NAME や VENDOR_ADDRESS に振り分けられ、住所はさらに STREET・CITY・STATE・ZIP_CODE へと細分化されて返却されます。
標準項目に該当しない「Attn: Accounts Payable」などは、OTHER として抽出されていました。

処理時間は、2.41秒、2.34秒、2.84秒でした。
料金は1ページあたり $0.01(約1.5円)と、Forms の5分の1に抑えられています。
請求書から合計金額や日付を抽出したい場合、表記ゆれを自動で吸収してくれる AnalyzeExpense を使うのが、Forms よりも低コストかつ確実です。

5. AnalyzeID

AnalyzeID は、米国の運転免許証やパスポートに特化した API で、氏名や生年月日などを決まった項目名で返します。
公式ドキュメントによると、AnalyzeID は IdentityDocuments 内の IdentityDocumentFields に、正規化された項目名(Type)と抽出値(ValueDetection)のペアを返します。

なお、ほかの API とは異なり、項目ごとの座標情報(Geometry)は返却されません。

引用元: Identity Documentation Response Objects | AWS ドキュメント

実際に、架空の州「STATE OF EXAMPLE」の運転免許証を読み込ませてみました。
住所の州コード欄には、実在しない「EX」を記載しています。

運転免許証のサンプル
AnalyzeID に読み込ませた運転免許証(すべて架空)

aws textract analyze-id \
  --document-pages "Bytes=$(base64 -i 08_id_card.png)" \
  --region us-west-2 > id.json

実行したところ、21個のフィールドが返ってきました。
帳票上に記載のない項目(SUFFIX、COUNTY、ENDORSEMENTS など)も、空文字の値として返却されます。
実際に値が取得できた項目は次のとおりです。

Type 返った値 信頼度 正誤
FIRST_NAME EMILY 97.1 正
MIDDLE_NAME ANNE 98.5 正
LAST_NAME CARTER 98.4 正
DATE_OF_BIRTH 04/12/1988 97.5 正
EXPIRATION_DATE 09/30/2030 96.7 正
DATE_OF_ISSUE 09/30/2022 97.1 正
DOCUMENT_NUMBER D1234567 96.7 正
CLASS C 97.9 正
ID_TYPE DRIVER LICENSE FRONT 97.0 正
ADDRESS 1234 MAPLE ST 95.7 正
CITY_IN_ADDRESS SPRINGFIELD 98.8 正
ZIP_CODE_IN_ADDRESS 00000 94.0 正
STATE_IN_ADDRESS AK 52.9 誤:帳票は EX
STATE_NAME PENNSYLVANIA 57.0 誤:帳票は STATE OF EXAMPLE
レスポンスの抜粋:全体の形と、項目の書かれ方(クリックすると展開します)

レスポンス全体の形です(中身は省略)。AnalyzeExpense と同じく、Blocks は書類ごとの結果の中に入ります。

{
  "IdentityDocuments": [  // 身分証1枚ごとの結果
    {
      "DocumentIndex": 1,
      "IdentityDocumentFields": [  // 氏名や生年月日などの項目
        "…"
      ],
      "Blocks": [  // OCR の結果
        "…"
      ]
    }
  ],
  "DocumentMetadata": {
    "Pages": 1
  },
  "AnalyzeIDModelVersion": "1.0"
}

運転免許証に記載された生年月日欄(「DOB: 04/12/1988」)の例です。項目には Geometry がなく、日付には ISO 形式の NormalizedValue が付きます。

{
  "Type": {
    "Text": "DATE_OF_BIRTH"  // 決まった項目名
  },
  "ValueDetection": {
    "Text": "04/12/1988",  // 帳票の表記のまま
    "NormalizedValue": {  // 日付の項目にだけ付く
      "Value": "1988-04-12T00:00:00",
      "ValueType": "Date"
    },
    "Confidence": 97.48731231689453
  }
}

サンプル免許証には記載がない「SUFFIX(Jr. や III などの接尾辞)」の例です。帳票に存在しない標準項目も、値が空文字のまま返ります。

{
  "Type": {
    "Text": "SUFFIX"
  },
  "ValueDetection": {
    "Text": "",  // 空文字
    "Confidence": 99.16055297851562
  }
}

日付項目には、NormalizedValue として 1988-04-12T00:00:00 のような ISO 8601 形式の値も併せて付与されていました。
一方で、州に関する2項目には、帳票に存在しない「AK」や「PENNSYLVANIA」という値が返ってきてしまいました。
実在しない州名であったため、モデル側が実在する米国の州のどれかに無理やり当てはめて推測したものと考えられます。
これら誤判定された2項目の信頼度は50台にとどまっており、ほかの正常な項目(94〜99)と比べて明確に低い数値でした

なお、帳票上には性別(SEX)・身長(HGT)・目の色(EYES)も記載していましたが、定義済みフィールドに含まれていないため、これらはキー・バリューとしては返却されませんでした。

処理時間は1.32秒、1.48秒、1.34秒で、料金は1ページあたり $0.025(約3.75円)でした。
米国の運転免許証やパスポートから、氏名・生年月日・有効期限などの必須情報を定型フォーマットで抽出したいケースに向いています。

6. AnalyzeLending

AnalyzeLending は、米国の住宅ローン審査で提出される書類の束を、書類の種類ごとに仕分けてから項目を取り出す API です。
AnalyzeLending は住宅ローン関連の複数書類が束ねられたファイルをページ単位に分割し、ページごとの書類種別を自動分類したうえで、その種別に適した抽出処理を行います。
こちらは非同期処理専用の API となっており、S3 にアップロードしたファイルに対して StartLendingAnalysis でジョブを発行し、ページごとの解析結果を GetLendingAnalysis、書類種別ごとに集約した要約を GetLendingAnalysisSummary で取得します。
ページ単位の結果には、分類結果(PageClassification)と抽出データ(Extractions)が含まれます。

引用元: Analyzing Lending Documents | AWS ドキュメント、Analyze Lending Response Objects | AWS ドキュメント

実際に、給与明細・W-2(米国の源泉徴収票)・銀行明細の3ページを1つの PDF にまとめたファイルを読み込ませてみました。

給与明細のサンプル
1ページ目:給与明細

W-2 のサンプル
2ページ目:W-2(架空の書式)

銀行明細のサンプル
3ページ目:銀行明細

# PDF を S3 に置いてジョブを開始する
aws s3 cp 09_lending_package.pdf s3://<YOUR_BUCKET>/ --region us-west-2
aws textract start-lending-analysis \
  --document-location "S3Object={Bucket=<YOUR_BUCKET>,Name=09_lending_package.pdf}" \
  --region us-west-2 --query JobId --output text

# JobStatus が SUCCEEDED になったら結果を取得する
aws textract get-lending-analysis --job-id <JOB_ID> --region us-west-2 > lending.json
aws textract get-lending-analysis-summary --job-id <JOB_ID> --region us-west-2 \
  --query 'Summary.DocumentGroups[].Type'

最後のサマリー取得コマンドを実行した際の出力例です。

[
    "PAYSLIPS",  // 1ページ目:給与明細
    "W_2",  // 2ページ目:W-2
    "UNCLASSIFIED"  // 3ページ目:銀行明細は分類できなかった
]

実際の各ページの分類結果は次のとおりでした。

ページ 書類 分類 信頼度 取れた項目
1 給与明細 PAYSLIPS 99.99 支給期間、支給日、氏名、会社名と住所、総支給額・手取り額(当期と年累計)、時給
2 W-2 W_2 99.99 年度、社会保障番号、雇用主番号、氏名と住所、給与額、各種の源泉徴収額、州
3 銀行明細 UNCLASSIFIED 56.40 (なし)

給与明細と W-2 は高精度で正しく分類され、それぞれの書類フォーマットに応じた定義済み項目名で値が抽出されました。
たとえば給与明細では CURRENT_GROSS_PAY に「$3,655.00」、YTD_NET_PAY に「$43,430.58」が割り当てられ、帳票内に存在しなかった項目(BORROWER_ADDRESS など)は、ValueDetections が空の配列で返却されています。
給与明細の当期総支給額欄に書かれた「Gross Pay: $3,655.00」から、金額が正しく抽出された例です。

{
  "Type": "CURRENT_GROSS_PAY",  // 当期の総支給額
  "ValueDetections": [  // 値は配列で返る(複数形)
    {
      "Text": "$3,655.00",  // 給与明細に書いた金額
      "Confidence": 97.0
    }
  ]
}
レスポンスの抜粋:全体の形・分類できたページとできなかったページ・要約(クリックすると展開します)

GetLendingAnalysis のレスポンス全体の形です(中身は省略)。非同期のジョブなので JobStatus が付きます。

{
  "DocumentMetadata": {
    "Pages": 3  // PDF のページ数
  },
  "JobStatus": "SUCCEEDED",  // SUCCEEDED なら完了
  "Results": [  // ページごとの結果
    "…"
  ],
  "AnalyzeLendingModelVersion": "1.0"
}

1ページ目の給与明細として正しく分類され、項目が抽出された例です(LendingFields は2項目だけ抜粋)。

{
  "Page": 1,  // PDF の何ページ目か
  "PageClassification": {
    "PageType": [  // 書類の種類の候補
      {
        "Value": "PAYSLIPS",  // 給与明細と分類
        "Confidence": 99.9999771118164
      }
    ],
    "PageNumber": [  // 書類に印字されたページ番号
      {
        "Value": "undetected",  // ページ番号は書いていないので undetected
        "Confidence": 100.0
      }
    ]
  },
  "Extractions": [
    {
      "LendingDocument": {  // 書類の種類に合った項目
        "LendingFields": [
          {
            "Type": "BORROWER_ADDRESS",  // 帳票にない項目
            "ValueDetections": []  // 空の配列
          },
          {
            "Type": "CURRENT_GROSS_PAY",  // 当期の総支給額
            "ValueDetections": [  // 値は配列で返る(複数形に注意)
              {
                "Text": "$3,655.00",
                "Geometry": { ... },  // 座標情報は省略
                "Confidence": 97.0
              }
            ]
          }
        ]
      }
    }
  ]
}

分類できなかったページです。

{
  "Page": 3,
  "PageClassification": {
    "PageType": [
      {
        "Value": "UNCLASSIFIED",  // どの書類の種類にも当てはまらなかった
        "Confidence": 56.40407180786133
      }
    ],
    "PageNumber": [
      {
        "Value": "undetected",
        "Confidence": 100.0
      }
    ]
  },
  "Extractions": []  // 項目は取り出されない
}

GetLendingAnalysisSummary のレスポンスの Summary です。書類の種類ごとに、どのページに入っていたかがまとまります。

{
  "DocumentGroups": [  // 書類の種類ごとのまとまり
    {
      "Type": "PAYSLIPS",
      "SplitDocuments": [
        {
          "Index": 1,
          "Pages": [  // その書類が入っていたページ
            1
          ]
        }
      ],
      "DetectedSignatures": [],
      "UndetectedSignatures": []
    }
  ],
  "UndetectedDocumentTypes": [  // 対応しているが、今回は見つからなかった書類の種類
    "1040_SCHEDULE_C",
    "1005",
    "1099_SSA",
    "…"
  ]
}

今回、UNCLASSIFIED と判定された3ページ目は、Extractions が空で、項目は1つも取り出されませんでした。
分類できなかったページの扱いは公式ドキュメントに書かれていないため、UNCLASSIFIED のページは別の方法で処理する流れを用意しておくのが確実です。

処理時間は、ジョブの発行から完了まで8.08秒、6.73秒、6.81秒でした。
料金は1ページあたり $0.07(約10.5円)です。

料金ページの計算例では、処理したページのうち、対応する書類として分類・抽出されたページの分だけが計算されています。
これに従うと、今回は正常に処理された2ページ分の $0.14(約21円)が課金される計算です。
ただし、UNCLASSIFIED のページが課金の対象外になるかどうかは、料金ページに明記されていません。
米国の住宅ローン審査手続きのように、フォーマットの異なる複数種類の書類がひとまとめに提出される業務で、事前仕分けと抽出を自動化したい用途に適しています。

7. AnalyzeDocument の機能を組み合わせる

AnalyzeDocument の FeatureTypes には、複数の機能をまとめて指定できます。
組み合わせたときに結果や処理時間が変わるのかを確かめるため、フォーム・チェックボックス・表・署名・見出しを1枚にまとめた注文書を用意しました。
この注文書に対して、5つの機能の組み合わせ31通り(単独5通りと、2つ以上を組み合わせた26通り)をすべて3回ずつ実行しています。
Queries には「What is the customer name?」「What is the order total?」「Which shipping method was selected?」の3つの質問を投げました。

注文書のサンプル
組み合わせの検証に使った注文書

5つの機能をすべて指定するときのコマンドは次のとおりです。

aws textract analyze-document \
  --document "Bytes=$(base64 -i 10_order_form.png)" \
  --feature-types FORMS TABLES QUERIES SIGNATURES LAYOUT \
  --queries-config '{"Queries":[
    {"Text":"What is the customer name?","Alias":"CUSTOMER_NAME"},
    {"Text":"What is the order total?","Alias":"ORDER_TOTAL"},
    {"Text":"Which shipping method was selected?","Alias":"SHIPPING_METHOD"}]}' \
  --region us-west-2 > combo_all.json

7.1 組み合わせた結果は、単独で実行した結果の足し合わせになる

31通りの結果を比べると、どの組み合わせでも、BlockType ごとの Block の数は、含まれる機能を単独で実行したときの数とすべて一致しました。

また、Queries を含む16通りでは、3つの質問の答え(Emily Carter、$1,274.00、Express)がすべて同じでした。

組み合わせ 平均処理時間
Forms 2.76秒
Tables 1.90秒
Queries 2.60秒
Signatures 2.50秒
Layout 2.35秒
Forms + Tables 3.63秒
Forms + Queries 2.89秒
Forms + Signatures 3.68秒
Forms + Layout 3.04秒
Tables + Queries 2.65秒
Tables + Signatures 2.06秒
Tables + Layout 2.66秒
Queries + Signatures 2.32秒
Queries + Layout 2.72秒
Signatures + Layout 2.67秒
Forms + Tables + Queries 4.22秒
Forms + Tables + Signatures 2.73秒
Forms + Tables + Layout 2.65秒
Forms + Queries + Signatures 3.66秒
Forms + Queries + Layout 3.37秒
Forms + Signatures + Layout 3.26秒
Tables + Queries + Signatures 2.20秒
Tables + Queries + Layout 2.89秒
Tables + Signatures + Layout 2.30秒
Queries + Signatures + Layout 2.68秒
Forms + Tables + Queries + Signatures 3.01秒
Forms + Tables + Queries + Layout 2.88秒
Forms + Tables + Signatures + Layout 2.43秒
Forms + Queries + Signatures + Layout 3.20秒
Tables + Queries + Signatures + Layout 2.54秒
Forms + Tables + Queries + Signatures + Layout 3.73秒

処理時間は、31通りすべてが1.70秒から5.30秒の範囲に収まりました。
単独5通りの中央値は2.33秒、3つ以上を組み合わせた16通りの中央値は2.74秒で、機能を増やしても処理時間はほとんど延びていません。
同じ組み合わせでも回によって2秒以上ぶれることがあるため、この程度の差は計測のぶれの範囲と考えられます。
機能ごとに API を分けて呼ぶより、必要な機能を1回の呼び出しにまとめて指定するほうが、通信の回数も待ち時間も少なく済みます。

7.2 Forms と Tables を一緒に使うと、同じ値が2回返る

Forms と Tables を同時に指定すると、注文書の同じ箇所がそれぞれの切り口で読み取られます。

特に注意が必要なのが上部の「顧客情報欄」です。Forms はキーと値のペアとして返し、Tables はこれを5行2列の表(SEMI_STRUCTURED_TABLE)として認識しました。その結果、値が Forms の KEY_VALUE_SET と Tables の CELL の両方に重複して返却されます。どちらの Block から値を読むかをあらかじめ決めておかないと、同じ値を二重に取り込んでしまいます。

また、Forms は明細表の合計行や署名欄からもペアを拾うほか、区切り文字「|」を値とした不要なペア(信頼度31.1)も返していました。Forms の結果は、信頼度(Confidence)で絞り込んでから利用するのが確実です。

8. 処理時間と料金

ここまで各章で計測してきた処理時間と、1ページあたりの単価を一覧表にまとめました。
単価は、オレゴンリージョンにおける月間100万ページまでの利用枠を基準としています。

機能 帳票 処理時間(1回目 / 2回目 / 3回目) 1ページ単価
DetectDocumentText 社内メモ 2.04秒 / 1.33秒 / 1.32秒 $0.0015(約0.23円)
Forms 申込書 2.33秒 / 2.32秒 / 3.08秒 $0.05(約7.5円)
Tables 見積書 2.93秒 / 4.72秒 / 1.82秒 $0.015(約2.25円)
Queries 申込書 3.31秒 / 3.46秒 / 2.23秒 $0.015(約2.25円)
Signatures 同意書 2.21秒 / 3.62秒 / 1.64秒 $0.0035(約0.53円)
Layout 社内報 2.85秒 / 3.82秒 / 2.91秒 $0.004(約0.6円)
AnalyzeExpense 請求書 2.41秒 / 2.34秒 / 2.84秒 $0.01(約1.5円)
AnalyzeID 運転免許証 1.32秒 / 1.48秒 / 1.34秒 $0.025(約3.75円)
AnalyzeLending 3ページの PDF 8.08秒 / 6.73秒 / 6.81秒 $0.07(約10.5円)

同期型の8機能については、いずれも処理時間が概ね1〜5秒の範囲に収まりました。
同一の帳票であっても実行のタイミングによって2倍以上のブレが生じることがあり、機能ごとの明確な処理時間の差は見られませんでした。
一方で、非同期処理の AnalyzeLending は、3ページのドキュメントで7〜8秒程度かかっています。

料金については、AnalyzeDocument 内の複数機能を組み合わせて指定した場合、次のようなセット料金体系が適用されます。
単独単価の合算になるパターンと、あらかじめ割引されたバンドル単価が用意されているパターンがあります。

組み合わせ 1ページ単価
Forms + Tables $0.065(それぞれの単価の合計)
Forms + Queries $0.055
Queries + Tables $0.02
Forms + Tables + Queries $0.07
Tables + Layout $0.015(Layout は無料)

月間処理数が100万ページを超えると、利用規模に応じて各機能の単価が段階的に引き下げられます。

引用元: Amazon Textract の料金 | AWS

9. まとめ

信頼できるconfidenceの閾値を決めて、Human-in-the-Loop を周しましょう。

OCRの検証は目が疲れますね!!!

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事