Google Cloud Vision APIで画像メインのPDFから直接OCRする(PDF/TIFF Document Text Detection)
スポンサーリンク
この記事で紹介しているAPIは今のところベータ版です。正式リリースまでに仕様が変化する可能性があります。
[2018/09/04 追記]
ドキュメントの翻訳はまだのようですが2018年7月24日付でGAリリースになっています。
PDF、TIFFともに2000ページまで。
- PDF/TIFF Document Text Detection | Cloud Vision API Documentation | Google Cloud
- Release Notes | Cloud Vision API | Google Cloud
[追記 ここまで]
件のPDFデータの処理*1のため、Google Cloud Vision APIのOCR機能のうち、PDFおよびTIFF画像を対象にした一括処理モードを試してみました。
新しいモードではなく、従来のDOCUMENT_TEXT_DETECTION
の対応ファイル形式が一括処理モード限定で増えたという感じです。
PDF/TIFF Document Text Detection | Cloud Vision API Documentation | Google Cloud
いつの間にか追加されており、存在は知っていましたがなんか面倒な雰囲気だったので。
感想としては対象がPDFなら便利、かも。ただ出力形式はfullTextAnotation
形式なのでパースがちょっと面倒な印象。APIのバージョンが新しい分、出力は同一ではないです。
注意点など
過去記事でも紹介しているように、Google Cloud Vision APIのOCR機能は指定するパラメーターがTEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの種類あります。このPDFおよびTIFF形式画像処理モードは後者の方で、処理結果ののJSONもfullTextAnotationキーの形式です。
つまり、文書の構造を解析した上で、ページ、ブロック、文章、文字、という階層構造で結果が返ってきます。パースがちょっとめんどいです*2。注意点は座標情報が正規化された数値(最大値が1?)になっている点。対象ファイル形式がPDF/TIFF形式だからというよりは、APIのバージョンの関係だと思いますが。
なお、TIFF形式もPDF同様に複数ページを1ファイルに含めることが可能なファイルフォーマットです*3。この記事ではPDFのOCRしか試していません。
この機能は現状ではあくまでもベータ版です。料金体系は既存のAPIの、複数画像一括モードと同じとのこと。
Googleのサンプルコードについて
下記のサンプルコードは90秒以内に処理が完了しないとタイムアウトおよび処理未完了の例外が発生して終了する仕様なので不便。これをベースに改造してみます。
python-docs-samples/detect_pdf.py at master · GoogleCloudPlatform/python-docs-samples · GitHub
パラメーターが決めうちになっていて一度に2ページずつOCR処理が実行される。タイムアウトの値が90秒となっている。
そのままでもそれなりに役に立つ。実際に使うなら以下のパラメータを適当に修正する。
- batch_size = 2
- timeout=90
ページ数が多いと時間内に終わらないのでスクリプト自体は異常終了する。ただし、一度リクエストを受け付けてしまえば別に途中でプログラムが落ちても処理は続行されるので問題はない。
なお、他にもGoogle Cloud Vision APIのPythonのサンプルコードはいろいろあります。
- python-docs-samples/vision/api/label at master · GoogleCloudPlatform/python-docs-samples
- python-docs-samples/vision/cloud-client/detect at master · GoogleCloudPlatform/python-docs-samples
準備
APIの有効化とかその辺は適当にググって下さい。
環境
改造するサンプルを入手
以下のようにしてサンプルコードを入手。
$ wget https://raw.githubusercontent.com/GoogleCloudPlatform/python-docs-samples/master/vision/cloud-client/detect/detect_pdf.py
Google Storageに出力先を確保
Cloud Vision APIを有効にしたプロジェクトを選択した状態でバケットを作成する。
ストレージ バケットの作成 | Cloud Storage ドキュメント | Google Cloud
ここに対象のPDFをアップロード。完了したら「一般公開で共有する」というカラムのチェックボックスをオン。
権限が適切なら一般公開は不要なはずです。
バケット名とオブジェクト名(ファイル名)を確認しておく。
ライブラリのインストール
$ pip3 install --upgrade google-cloud-vision google-cloud-core
バージョンが古いとベータ版のAPIを実行できないので注意。
改造したプログラム
OCR処理が完了するまでスリープし続けるように修正したPYthonスクリプト
処理の完了待ちをresult()
メソッドからdone()
の戻り値をチェックする方式に変更している。あとはメソッドの分割。
ハードコードしているバッチサイズ(batch_size = 2
)を2
から5
に変更。
まれにJSONのパースに失敗して例外を投げて終了することがありますが、ちゃんと調べていません。すいません。
実行
クレデンシャル(APIキーの記載されたファイル)のパスを環境変数にセットしておく。direnvを使うと管理がラクでいいのでおすすめ。
$ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/api.json"
実行例
基本的な使い方は改造前と同じ。まず作成したバケットにPDFファイルまたはTIFFファイルを配置。引数にGoogle Storageのファイルのパスと、出力先のパスを指定して実行。
$ python3 ocr_and_wait.py --gcs-source-uri gs://<bucket-name>/input.pdf --gcs-destination-uri gs://<bucket-name>/ocr_output/
出力先については末尾のスラッシュが無いとフォルダ名扱いされず、ファイル名の接頭辞扱いになるので注意する。また、出力先のフォルダが存在しない場合は自動的に作成される。
出力ファイル名はoutput-1-to-5.json
のような開始ページ番号と出力ページ番号を含む形式。ソートしにくい。
JSONのパース
JSONからページ全体の認識結果を取り出すには以下のようにする。
import json filename = "output-1-to-5.json" fp = open(filename) data = json.load(fp) data['responses'][0]['fullTextAnnotation']['text']
出力のJSONから各ページのそれぞれの文字にアクセスするには、以下のように階層をたどる。
data['responses'][0]['fullTextAnnotation']['pages'][0]['blocks'][0]['paragraphs'][0]['words'][1]['symbols'][0]['text']
もしくはGoogleのサンプルコードがやっているように、json_format.Parse()
を使用してパースする。
from google.cloud import vision_v1p2beta1 as vision from google.protobuf import json_format json_str = open(filename).read() response = json_format.Parse(json_str, vision.types.AnnotateFileResponse()) response.responses[0].full_text_annotation.text
同じように、ドキュメント構造を考慮してテキストをたどることもできる。認識したテキストの位置情報や確からしさも取得できる。
response.responses[0].full_text_annotation.pages[0].blocks[0].paragraphs[0].words[0].symbols[0].text # 出力は省略
以下のように言語や位置の情報も取得できる。
response.responses[0].full_text_annotation.pages[0].blocks[0].paragraphs[0].words[0].symbols[0].property # 検出した言語 detected_languages { language_code: "ja" }
行末や空白も情報としては取得できる。
response.responses[0].full_text_annotation.pages[0].blocks[0].paragraphs[0].words[0].symbols[1].property detected_break { type: SPACE }
response.responses[0].full_text_annotation.pages[0].blocks[0].paragraphs[0].words[0].bounding_box normalized_vertices { x: 0.1596638709306717 y: 0.026128266006708145 } normalized_vertices { x: 0.2218487411737442 y: 0.027315914630889893 } normalized_vertices { x: 0.2218487411737442 y: 0.04156769439578056 } normalized_vertices { x: 0.1596638709306717 y: 0.04156769439578056 }
Symbol
クラスの要素(symbols
プロパティ以下の配列の値)にもbounding_boxというプロパティはあるけど値はセットされていない。
response.responses[0].full_text_annotation.pages[0].blocks[0].paragraphs[0].words[0].symbols[0].confidence # 確からしさ 0.9800000190734863
JSONを自力でパースするよりはGoogleのライブラリを使うほうが少しは楽か?。
そのほか
JSONファイルの分割など
例えば、複数ページ1ファイルなので分割したい時とか。
バッチサイズを1にすればいいだけな気もするけど、すでに処理済みのデータが有る場合の話。
JSONを読み込んでページごとに分割するとして、Googleのサンプルコードのようにjson_format.Parse()
でデータを読み込んだ場合、そのままではJSONに戻せない。
読み込んだデータはgoogle.cloud.vision_v1p2beta1.types.AnnotateFileResponse
クラスのオブジェクトになっている。
JSONに戻したい場合は、google.protobuf.json_format
モジュールに含まれるMessageToJson
という関数を使う。
- How to serialize to json or dict that response from vision api? · Issue #3485 · GoogleCloudPlatform/google-cloud-python · GitHub
- [python] JSONファイルのフォーマットを整えてDumpする
はじめから普通にJSONとしてロードすればこういう面倒事は発生しない……。
JSONを分割するスクリプト
以下、参考。
#! /usr/bin/env python3 # encoding: utf-8 from google.cloud import vision_v1p2beta1 as vision from google.protobuf import json_format from google.protobuf.json_format import MessageToJson import json import pathlib from natsort import natsorted ## JSONファイルのあるパスを指定 #pl = pathlib.Path("./from_ministry-of_finace/output") pl = pathlib.Path("./masked/from_ministry-of_finace/index_data/") filelist = list(pl.glob('*.json')) for file_path in filelist: print(file_path) with file_path.open(mode='rt',encoding='utf-8') as jfp: str = jfp.read() response = json_format.Parse(str, vision.types.AnnotateFileResponse()) for i, res in enumerate(response.responses) : page_num = res.context.page_number print("{0} {1} {2}".format(file_path.name, i,page_num ) ) filename = "output_json_{0}.json".format(page_num) ## 出力先のパスを指定 #output_path = pathlib.Path("ocr_result_masked", filename) output_path = pathlib.Path("./index_output", filename) with output_path.open(mode='w',encoding='utf-8') as output: serialized = MessageToJson(res) temp = json.loads(serialized) json.dump(temp, output, ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': ')) #output.write(serialized)
ブロック単位でOCR結果を表示する例
あまりスマートではないですが参考程度に。
for block in json_data['fullTextAnnotation']['pages'][0]['blocks'] : print(block['boundingBox']) print(block['blockType']) for pg in block['paragraphs'] : words = [ w for w in pg['words'] ] symbols = [] for w in words : symbols = [ s for s in w['symbols'] ] text = [] for s in symbols : text.append(s['text']) if "detectedBreak" in s["property"] : node = s["property"]['detectedBreak'] if node['type'].startswith('EOL_SURE_SPACE'): text.append("\n") elif node['type'].endswith('SPACE') : text.append(" ") else: text.append("\n") print("".join(text), end='') print("")
貼り付けるときにミスってインデントがおかしい可能性が微レ存。
参考URL
- PDF/TIFF Document Text Detection | Cloud Vision API Documentation | Google Cloud
- Vision — google-cloud 0.28.1 documentation
- Long-Running Operations — google-cloud 0.28.1 documentation
- google-cloud-python/operation.py at master · GoogleCloudPlatform/google-cloud-python
リクエストとレスポンスに関しては 以下のドキュメントを参照。
- Package google.cloud.vision.v1p2beta1 | Cloud Vision API | Google Cloud
- Package google.cloud.vision.v1p2beta1 | Cloud Vision API | Google Cloud
- Package google.cloud.vision.v1p2beta1 | Cloud Vision API | Google Cloud
まとめ
妙に長い記事になってしまった。ものすごく書きぶりが変な感じだけどまあいいか。
下書き状態で貯め込むよりはいいでしょうってことで。
他の記事も含めて要書き直し。

Google Cloud Vision APIとPythonで文字認識
- 作者: machine powers
- 発売日: 2018/09/14
- メディア: Kindle版
- この商品を含むブログを見る