Google Document AI の Python クライアントライブラリを利用して画像から表形式データを抽出する

本記事では、Google Document AIを利用するための準備作業、Pythonクライアントライブラリを利用したAPIの呼び出し方、そして文書画像に対するテーブル解析の結果をみていきます。

【目次】

[1]はじめに

Google の Document AI を利用すると、文書画像から、OCRより一歩踏み込んだ情報を抽出することができそうです。

以前の記事で、Document AI を利用して、文書画像の中にある表形式のレイアウトが認識される例を見ました。

この記事では、以下のサイトを利用して無料でテーブル解析を行って、Vision API のOCRとの違いをみました。

この記事を書いた時点では、APIがベータ版でしたが、現時点ではv1になっているようです。

そこで本記事では、Google Document AIを利用するための準備作業、Pythonクライアントライブラリを利用したAPIの呼び出し方、そして文書画像に対するテーブル解析の結果をみていきます。

ただし現時点(2021年7月)で、 Document AIは、まだ肝心の?日本語文書には対応していため、表形式の認識性能には焦点を当てず、APIの利用方法や解析結果のデータ構造に絞って見ていきます。

本来は基本となるOCRからはじめてフォーム解析やテーブル解析と進むのがよいとは思いつつも、単なるOCRとの違いに興味があるため、応用事例から書くことにしました。

(参考:追記 2021/07/19)
Document AIのフォーム解析について記事を書きました。罫線なしの表形式画像の認識についても書いておりますので、よろしければご参照ください。

[2]GCPプロジェクトの準備

Google Document AI のAPIを利用するためには、まず、GCP(Google Cloud Platform)での準備作業が必要です。

(注意)

現状ではDocument AIは無料枠が無いようですので、利用料金にご注意ください。

<GCPプロジェクトの準備の流れ>

  1. 利用するプロジェクトの決定
  2. 課金を有効化
    • プロジェクトの課金が有効になっていることを確認します。
  3. APIの有効化
    • Cloud Console 左側メニューの「APIとサービス/ライブラリ」を選択し、「Cloud Document AI API」を検索して「Cloud Document AI API」画面へ進みます。そして「有効にする」ボタンをクリックします。
  4. サービスアカウントの準備
    • サービスアカウントキーを作成して、JSONファイルをダウンロードします。

具体的な手順は以下を参照して下さい。

[3]プロセッサの準備

(1)プロセッサの選択

Document AI は、文書(様式など)に特化(最適化?)した解析を可能とするための(と思われます)、プロセッサという概念を持っています。

このため、解析目的に応じたプロセッサを選択する必要があります。

以下のページに、現在利用できるプロセッサのリストがあります。

現時点では、特定の文書様式向けではない、汎用的な解析を目的としたプロセッサは、以下の二つのようです。
  • Document OCR (Optical Character Recognition)
  • Form Parser

本記事では、表形式データの抽出を題材としているため、Form Parserを利用します。

Form Parser の主な用途は、フォーム形式のような文書から要素(キー/バリュー)を抽出することだと思いますが、テーブル解析機能も含まれています。また、Form Parser の解析結果には、Document OCRの解析結果も含まれているようです。

但し、利用料金はDocument OCRより Form Parser の方が高いので、ご注意ください。。。

(2)プロセッサの作成手順

Cloud Console を利用してプロセッサを作成します。

<作成手順>
  1. Cloud Console の左側メニューで「(人工知能)Document AI/プロセッサ」を選択します。
  2. 画面上部にある「+プロセッサを作成」をクリックします。
  3. 今回は、Generalにある「Form Parser」をクリックします。
  4. 右側に「プロセッサの作成」ダイアログが表示されますので、以下の項目を入力します。
    • プロセッサ名
      • 作成したプロセッサを識別するために、名前を付けます。
    • リージョン
      • 現在はUSとEUのみ選択できます。デフォルトはUSです。
      • リージョンの選択はAPIのエンドポイントにも関係します。
Cloud Console のプロセッサ画面に、作成したプロセッサが一覧表示されます。
この画面から、プロセッサの無効化や削除を行うことができます。

(3)Cloud Console でプロセッサの動作を確認

Cloud Console のプロセッサ画面から、登録したプロセッサの簡単な動作確認を行うことができます。
  1. プロセッサ画面のプロセッサのリストから、動作確認したいプロセッサ名をクリックしてプロセッサの詳細画面に移動します。
  2. 詳細画面に「テストドキュメントをアップロード」ボタンがありますので、ここからテスト用ドキュメントをアップロードすると、解析結果が表示されます。

ただし、この機能では詳細な解析結果は表示されないようです。現状は、簡単な動作確認用といった用途に限定されているようです。

[4]APIの仕様

以上で Document AI に関するGCP側での準備が整いましたので、ここでは特定のプログラミング言語に依存しないDocument AI  API の仕様をざっとみておきます。

先の準備の中で、解析目的に応じたプロセッサを選択する必要がありましたが、プロセッサの種類に関係なく、APIの仕様は共通です。(但し、プロセッサに依存して使用する/使用しないフィールドがあるため、少々分かり難い点があるかもしれません。)

そして、APIのレスポンスデータの構造を見れば、Document AIが認識できること、あるいは認識しようとしていることを垣間見ることができるように思います。

さて、Document AI のAPIは、DocumentProcessorServiceで提供されており、サービスとクライアントは、 gRPCかREST方式で通信します。

DocumentProcessorService

APIのリファレンスは以下にあります。

本記事では、Pythonのクライアントライブラリを利用したAPIの利用サンプルを見ていきますので、gRPCのリファレンス(v1)を中心に見ていきます。(Python クライアントライブラリは、gRPC のラッパーになっています。)

(1)DocumentProcessorService.ProcessDocument メソッド

DocumentProcessorService には以下の3つのメソッドが定義されています。
  • ProcessDocument
    • 1つの文書を送信し、同期処理で解析結果を受け取ります。
  • BatchProcessDocuments
    • Cloud Storage上の複数ファイルをバッチ処理して、非同期で解析結果を受け取ります。
  • ReviewDocument
    • 人間参加型(Human in the Loop:HITL)に関するもののため、本記事では割愛します。

本記事では、簡単に解析内容を確認できる ProcessDocumentメソッドを見ていきます。

ProcessDocument メソッドの定義
rpc ProcessDocument( ProcessRequest ) returns (ProcessResponse)

考え方はとても簡単で、引数(ProcessRequest)を渡すと、同期処理して戻り値(ProcessResponse)を返してくれます。

以下では引数(ProcessRequest)と戻り値(ProcessResponse)の内容を簡単に見ていきます。

(2)引数:ProcessRequest

ProcessDocument メソッドの引数となるデータで、利用するプロセッサを特定する情報と処理対象の画像データを指定します。

ProcessRequest

  • name
    • 利用するプロセッサのリソース名を以下の形式で指定します。
      • projects/{project}/locations/{location}/processors/{processor}
      • project:GCPのプロジェクトID
      • location:プロセッサのリージョン。現状は、usかeu。
      • processor:プロセッサのID
    • バージョン指定を行いたい場合など、詳細についてはドキュメントを参照して下さい。
  • raw_document
    • 処理対象画像のバイナリデータ(content)と、そのファイルタイプをMIMEタイプ(mime_type)で指定します。
    • Form ParserとDocument OCRプロセッサで利用できるファイルタイプは、PDF, TIFF, GIF, JPEG, PNG, BMP, WEBP です。

入力画像にはPDFファイルだけでなく、JPGやPNG等の画像ファイルも指定できます。

なお、ProcessDocumentで処理できるページ数やサイズには制限があります。詳細はドキュメントを参照して下さい。

(3)戻り値:ProcessResponseとTable

以下は、ProcessDocument メソッドの戻り値の概要です。

ProcessResponse

様々なフィールドや型が定義されていますが、Form Parser を利用した ProcessDocument メソッドが、全てのフィールドを利用するわけではありません。ここではテーブル解析で利用する主な項目のみ補足します。(各項目の詳細はリファレンスを参照して下さい。)
  • document.text
    • 抽出したドキュメント全体の文字(列)が設定されます。空白や改行文字なども含まれています。
    • プロセッサにより解析された構造要素(パラグラフ、フォーム要素、テーブルのセルなど)に対応する文字列は、document.textのインデックス範囲で指定されます。
      • これらの解析情報は、document.textの文字(列)に対する注釈情報という形になっているため、拡張性が高い構造だと思います。
  • document.pages/Page
    • ドキュメント内のページ毎の解析結果が設定されます。
    • Vision APIのOCR解析結果のような、block、paragraphなどの文書の構造が設定されます。
    • Form Parser を利用すると、tablesやform_fieldsなども設定されます。

本記事はテーブル解析が題材ですので、Pageにあるtables以下の構造もみておきます。

Document AI/Table

  • Pageの下にTableが格納されます。
  • Tableは、ヘッダ(header_rows)とボディ(body_rows)の行(TableRow)のリストを持ち、さらに行はセル(TableCell)のリストを持ちます。
    • この構造はHTMLのTABLEタグと同様に考えられます。
  • 各セルのlayoutフィールドに、セル内のテキストと座標などの情報を持っています。
    • セル内のテキストは、text_anchor/text_segmentsのstart_indexとend_indexフィールドで与えられます。これは document.text の範囲(開始位置と終了位置)ですので、document.text から指定範囲の文字列を取り出して利用します。

[5]Python クライアントライブラリの準備

Googleが推奨するAPIの利用方法は、クライアントライブラリを利用する方法です。
実際に、RESTやgRPCを直接利用するのに比べて、簡単かつ確実に利用できる方法だと思います。

本記事では、Document AI のPython 言語のクライアントライブラリを利用します。

Pythonクライアントライブラリのドキュメントとソースコードは以下にあります。

クライアントライブラリを利用するためには、環境変数の設定とライブラリのインストールが必要です。

(参考)
本記事の動作確認は、Google Colaboratory で行いました。Colaboratory については以下の記事も参考にしてください。

①ライブラリのインストール

pipでインストールできます。
pip install --upgrade google-cloud-documentai

②環境変数(GOOGLE_APPLICATION_CREDENTIALS)の設定

先のGCPプロジェクトの準備でダウンロードしたキーファイル(JSONファイル)のパスを環境変数に設定します。

Colaboratoryでは、セルで以下のコードを実行します。
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = 'JSONファイルのパス'

Linux環境では、exportコマンドでも設定できます。
export GOOGLE_APPLICATION_CREDENTIALS="JSONファイルのパス"

[6]Python クライアントライブラリを利用した解析サンプル

ここから、Python クライアントライブラリを用いて、具体的な画像を解析してみます。
(実際に試してみる場合は、Google Colaboratoryのセルに以下のコードを貼り付けて実行できると思います。)

(1)DocumentProcessorService.ProcessDocument を利用する方法

以下のコード(call_process_document関数)は、指定の画像イメージを送信して解析結果を得るためのコードサンプルです。

import re
from google.cloud import documentai_v1 as documentai

def call_process_document(endpoint_url, image_content, mimetype):
    """DocumentProcessorService.ProcessDocumentを実行する。

        引数:
        endpoint_url: プロセッサ詳細画面に表示されている予測エンドポイント
        image_content: 画像ファイルのバイナリデータ
        mimetype: 画像ファイルのMIMEタイプ文字列

        戻り値:
        ProcessDocumentの実行結果(ProcessResponse型)
    """
    # 予測エンドポイントから、リージョンとnameを取り出す。
    # リージョンは、現状は us または eu
    # nameは、projects/project-id/locations/location/processor/processor-id の形
    pattern = "https://(\\w+)-documentai.googleapis.com/v1/([\\w\\/]+):process"
    m = re.fullmatch(pattern,endpoint_url)
    if m is None:
        raise Exception("bad endpoint_url")
    region = m.group(1)
    name = m.group(2)

    # DocumentProcessorServiceClientのインスタンスを作成
    opts = {}
    if region != "us":
        # リージョンがus以外は、api_endpointを設定する
        api_endpoint = f"{region}-documentai.googleapis.com"
        #print("api_endpoint={}".format(api_endpoint))
        opts = {"api_endpoint": api_endpoint}

    client = documentai.DocumentProcessorServiceClient(client_options=opts)

    # リクエスト(ProcessRequest)を作成
    process_request = {
        "name": name,
        "raw_document": {"content": image_content, "mime_type": mimetype }
        }
    # メソッド呼び出し
    result = client.process_document(request=process_request)
    return result

なんだか少々面倒な感じがしますが、内容は難しいものではありません。
以下に、少し補足しておきます。
  • APIを利用するには、リージョン固有のAPIエンドポイントを指定する必要があります。
    • プロセッサの作成の際にリージョンを選択しますが、選択したリージョンに応じたエンドポイントを指定する必要があります。
    • USリージョンの場合は、クライアントライブラリがデフォルト処理しますので、特別な指定は不要みたいです。
  • ProcessRequestのnameフィールドには、プロセッサのリソース名を以下の形式で指定する必要があります。
    • projects/{project}/locations/{location}/processors/{processor}
    • project:GCPのプロジェクトID
    • location:プロセッサのリージョン。現状は、usかeuを指定する。
    • processor:プロセッサのID
  • 上記メソッドでは、「予測エンドポイント」から、リージョン固有のAPIエンドポイントとProcessRequestのnameフィールドの値を作成、設定しています。
    • 「予測エンドポイント」は、プロセッサの詳細画面に表示されています。この値(URL)は、リージョンやnameフィールドの値を含んでいますので、これを用いてパラメータ設定を行っています。
    • 「予測エンドポイント」の表示部分には「クリップボードにコピー」ボタンがありますので、切り貼りできて便利です。
    • ちなみにGoogleのサンプルでは、予測エンドポイントではなく、project、location、processorを引数にしてパラメータを設定しています。(どちらも似たようなものですけど。)
  • ProcessRequestのデータ構造は、ディクショナリ型で簡単に指定できます。
  • 画像データの形式は、PDF, TIFF, GIF, JPEG, PNG, BMP, WEBPが利用できます。
    • 形式はMIMEタイプで指定しますので、引数には、PDFなら「application/pdf」、JPEGなら「image/jpeg」などを指定します。

(2)サンプル画像

本記事では、Document AI がまだ日本語に対応していないこともあり、表形式の認識性能には焦点を当てず、結果データの扱いに集中するため、画像サンプルは単純なものとします。

このため『Google Document AIで画像から表形式データを抽出する(Vision API OCRとの違い)』で用いた下記の画像を利用します。


実際に試してみる場合は、この画像をダウンロードして、名前を「shoppinglist.jpg」にして下さい。

以下は、この画像を読み込む簡単な関数サンプルです。
def load_image(file_path):
    """画像ファイルをメモリに読み込む"""
    with open(file_path, "rb") as image:
        return image.read()

(3)解析結果の確認

以下のコードを実行すると、解析結果を得ることができます。
なお、endpoint_url には作成したプロセッサの「予測エンドポイント」を設定してください。
# endpoint_url にはコンソールの予測エンドポイントに表示される値を設定する。
endpoint_url = "https://(Region)-documentai.googleapis.com/v1/projects/(省略):process"
# 画像を読み込む
image_content = load_image("shoppinglist.jpg")
# 解析を実行
response = call_process_document(endpoint_url,image_content,"image/jpeg")

以下では responseの値を見ていきます。
(responseの値をJSON形式に変換したり、ファイルに保存しておきたい場合は、(参考)の項を参照して下さい。)

①document.textの値

document.text には文書画像から抽出した全ての文字が設定されています。
Shopping List\nPrice\nDate Description\n2021/2/1 Sweets\n2021/2/1 Bento\n2021/2/2 TechnoDaifukucho\nTotal\n$300.00\n$1,000.00\n$0.00\n$1,300.00\n

ここには、空白文字や改行文字が入っていることに注意して下さい。

参考までに、print(response.document.text) を実行した場合の表示は以下のようになります。

Shopping List
Price
Date Description
2021/2/1 Sweets
2021/2/1 Bento
2021/2/2 TechnoDaifukucho
Total
$300.00
$1,000.00
$0.00
$1,300.00

この状態では解釈が難しいと思います。しかし、テーブル解析の結果を利用すると状況は改善します。

②Tableの値

画像からテーブル情報が抽出できた場合は、Pageのtablesフィールドに結果が格納されます。本サンプル画像も1つのテーブル情報が抽出されると思います。

Table型の情報は、HTMLのTABLEタグと似たような構造になっていますので、ここでは解析されたテーブル情報をHTMLのTABLE構造に変換してみます。
(以下の変換サンプルは、row_span、col_spanも考慮しておらず、簡単のため、とても割り切った内容になっています。)

<変換サンプル>
def tables_to_htmlstr(tables, doctext):
    res = ""
    for tb in tables:
        res += table_to_htmlstr(tb, doctext)
    return res

def table_to_htmlstr(table, doctext):
    res = "<table border=1>\n"
    if len(table.header_rows) > 0:
        res += "<thead>\n"
        res += rows_to_htmlstr("th", table.header_rows, doctext)
        res += "</thead>\n"
    if len(table.body_rows) > 0:
        res += "<tbody>\n"
        res += rows_to_htmlstr("td", table.body_rows, doctext)
        res += "</tbody>\n"

    res += "</table>\n"
    return res

def rows_to_htmlstr(ctag, rows, doctext):
    res = ""
    for row in rows:
        res += "<tr>\n"
        for cell in row.cells:
            val = get_anchor_text(doctext, cell.layout.text_anchor)
            res += "<{}>{}</{}>\n".format(ctag,val,ctag)
        res += "</tr>\n"
    return res

def get_anchor_text(doctext, anchor):
    txt = ""
    for seg in anchor.text_segments:
        txt += doctext[int(seg.start_index) : int(seg.end_index)]
    return txt

このコードを用いて、以下のように実行するとHTMLのTABLEタグの文字列が出力されます。
htmlstr = tables_to_htmlstr(response.document.pages[0].tables, response.document.text)

このテーブルタグを表示(このブログページに張り付け)すると以下のようになります。

Date Description Price
2021/2/1 Sweets $300.00
2021/2/1 Bento $1,000.00
2021/2/2 TechnoDaifukucho $0.00
Total $1,300.00

テーブル解析の結果を利用すれば、値の関係が明確になります。

ちなみに上記のようなHTML表示だと分かり難いですが、セルの値には、document.textの空白や改行文字も含まれています。このためセルの値をデータとして利用する場合などでは、必要に応じてトリミングなどの処理が必要になるかもしれません。

さて、抽出されたTable情報にはセルの矩形情報も入っていますので、元の画像にセル領域を描画してみます。(以下の画像は、headerRowsをRED、bodyRowsをGREENで描画しています。)




(参考)レスポンスデータ(ProcessResponse)をJSONファイルで保存、再現する方法

テーブル解析とは話題が異なりますが、Document AI の結果をファイルなどに保存して、後で再利用したい場合もあると思います。ここでは簡単なコード例をメモしておきます。

下記コードの補足説明として、よろしければ以下の記事も参考にしてください。

①APIの戻り値(ProcessResponse)をJSON形式に変換してファイルに保存

# レスポンス(ProcessResponse)をJSONファイルとしてファイルに保存する
def save_response_to_jsonfile( response, ofname ):
    js = response.__class__.to_json( response )
    with open(ofname, 'w') as f:
        f.write(js)

以下のように利用します。
save_response_to_jsonfile( response, "保存するファイル名")

②JSONファイルの内容をAPIの戻り値(ProcessResponse)に変換する

from google.cloud.documentai_v1 import ProcessResponse

# 保存したJSONファイルをレスポンス(ProcessResponse)として再現する
def load_response_from_jsonfile(ifname):
    with open(ifname) as f:
        js = f.read()
        return ProcessResponse.from_json(js)

以下のように利用します。
response = load_response_from_jsonfile("JSONファイル名")


[7]最後に

GCPでの利用準備からAPIの概要、サンプルコードを概観しただけですが、書いてみると予想以上に長くなってしまいました。(面倒だという印象を受けられるかも。。。)

実際には、例えば Vision API のような、Googleの他のAPIを利用したことがある人にとっては、いつもの「儀式+α」くらいの感覚で利用できるものという気もします。

現時点では、Document AI が日本語に対応していないため、具体的な画像を試す意味はあまり無いと思いますが、正式リリース時に、APIで定義されている構造がちゃんと認識されるとしたら、これは凄いです。

対応する自然言語の種類によってAPI定義が異なるわけではないし、日本語対応版にとても期待しているので、APIの使い方を予習しておこうと思って本記事を書きました。

また、今回はテーブル解析のみ取り上げましたが、その他の抽出可能な情報についても折を見て書いてみたいと思います。

(参考:追記 2021/07/19)
Document AIのフォーム解析について記事を書きました。罫線なしの表形式画像の認識についても書いておりますので、よろしければご参照ください。


コメント

このブログの人気の投稿

Google Document AIで画像から表形式データを抽出する(Vision API OCRとの違い)

VirtualBoxのスナップショット機能

Ubuntu/Colab環境でPDFファイルのページを画像化する(pdf2image、pdftoppm、pdftocairo)