Vision API OCR事始め(1):TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの違い

これまでの記事で、Google Vision APIの使い方の側面(特にPython)について見てきましたが、平行して、今回からOCR(光学文字認識)の機能面についても見ていきたいと思います。まずは、OCRにあたる2つの機能(特徴タイプ)であるTEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの違いについて見ていきます。

【目次】

[1]はじめに

Google Vision APIのOCRは評判が良く、私もデモを見て凄いなと思いました。昔、仕事でOCRをちょっとだけ使ったことがありますが、その頃を思うと、隔世の感があります。

そこで実際に使ってみようと思って、ドキュメント(ガイド)を読んだり、サンプルを試してみたりしました。それはなんとなく理解できるのですが、現実の問題に適用しようとすると、ある画像形式に対してどのような機能を使い、どのように結果を解釈するのが良いのか、など、もう少し自分なりに頭の整理が必要だと感じました。

というわけで、Googleが公開している資料や実験を通して、私なりに理解したことを共有していきたいと思います。

ただし、私はOCRや画像処理、機械学習の専門家ではありませんので、誤った理解もあるかもしれません。また、資料や実験結果は、記事を書いている時点のものであり、モデルのバージョンアップやサービス内容の変更によって、結果が異なる可能性があることをご了承ください。

[2]Vision APIが提供する2つのOCR機能

Vision APIでは、OCR機能として、「テキスト検出」と「ドキュメント テキスト検出(高密度テキスト / 手書き)」の2つを提供しています。
これらは、機能リスト(https://cloud.google.com/vision/docs/features-list?hl=ja)で以下のように記載されています。

テキスト検出
  • 画像の光学式文字認識(OCR)によって、テキストを認識し、マシンコード化されたテキストへ変換します。画像内の UTF-8 テキストを識別して抽出します。
  • 画像:
    • 大きな画像内のテキストのスパース領域向けに最適化されます。
  • レスポンス:
    • テキストであると識別された単語、境界ボックス、textAnnotations のリストに加え、OCR で検出されたテキスト(fullTextAnnotation)の構造的階層を返します。
    • 抽出されたテキスト構造の階層
      • TextAnnotation -> Page -> Block -> Paragraph -> Word -> Symbol.
      • Page 以降の各構造要素には、検出された言語、区切りなどの独自のプロパティがある場合があります。
  • サポートされている言語:
  • 機能の列挙値:TEXT_DETECTION

ドキュメント テキスト検出(高密度テキスト / 手書き)
  • ファイル(PDF / TIFF)または高密度テキスト画像用の光学式文字認識(OCR)によって、高密度テキストを認識し、マシンコード化されたテキストに変換します。
  • ファイル:
    • ドキュメント ファイル(PDF / TIFF)向けに最適化されます。
  • 画像:
    • 画像内(文書の画像)の高密度テキスト領域、および手書きを文字を含む画像向けに最適化されます。
  • レスポンス:
    • OCR で検出されたテキスト(fullTextAnnotation)の構造的階層を返します。
    • 抽出されたテキスト構造の階層
      • TextAnnotation -> Page -> Block -> Paragraph -> Word -> Symbol.
      • Page 以降の各構造要素には、検出された言語、区切りなどの独自のプロパティがある場合があります。
  • サポートされている言語:
  • 機能の列挙値:DOCUMENT_TEXT_DETECTION
    • DOCUMENT_TEXT_DETECTION と TEXT_DETECTION の両方がリクエストされた場合、優先されます。

私には、なんとなく、わかったような、でも、実感としてはよくわからない内容ですので、これらについて、自分なりに少しづつ深堀していきたいと思います。

以下では、機能名ではなく、機能の列挙値(Vision APIのFeature.typeフィールドの設定値)を使って、
  • TEXT_DETECTION => テキスト検出
  • DOCUMENT_TEXT_DETECTION => ドキュメント テキスト検出(高密度テキスト / 手書き)
として書いていきます。

(補足)

Googleの日本語ドキュメントを見ていると、Featureを「機能」と書いているところと「特徴」と書いているところがあります。私はどちらでもいいのですが、記事に書こうとすると、どちらを使うべきか迷います。

[3]「~向けに最適化されます」とは?

今回は画像を中心に、TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの具体的な違いを見ていきますが、先の説明文には以下のように書かれていました。
  • TEXT_DETECTION
    • 大きな画像内のテキストのスパース領域向けに最適化されます。
  • DOCUMENT_TEXT_DETECTION
    • 画像内(文書の画像)の高密度テキスト領域、および手書きを文字を含む画像向けに最適化されます。

ここで、手書き文字はよいとして、「テキストのスパース領域」と「高密度テキスト領域」はどのようなものでしょうか。Googleのガイドなどを探してみましたが、具体的な説明を見つけることができませんでした。

一方、Googleのドキュメントには以下のような記述もあります。(https://cloud.google.com/vision/docs/ocr?hl=ja
  • TEXT_DETECTION は、任意の画像からテキストを検出、抽出します。たとえば、写真に道路名や交通標識が写っていれば、抽出された文字列全体、個々の単語、それらの境界ボックスが JSON レスポンスに含まれます。
  • DOCUMENT_TEXT_DETECTION も画像からテキストを抽出しますが、高密度のテキストやドキュメントに応じてレスポンスが最適化され、ページ、ブロック、段落、単語、改行の情報が JSON に含まれます。

スパース(sparse)とは「すかすか」、「少ない」を意味するようです。例えば、風景の中にある標識の文字(単語レベル)を抽出することは、画像全体から見ると、テキストのスパース領域を抽出すると言える気がします。

一方、「高密度テキスト領域」は、スパースの反対と考えることもできそうです。例えば、小説をスキャンすると、文章中心で画像全体から見ると、文字の密度も高い気がします。

とはいえ、与えられた画像がスパースか高密度かの境界が明確ということでもありませんので、これを深く考えても仕方ない気がしました。

むしろ気になるのは、「~向けに最適化されます」という表現です。利用する側から見ると、「~向けに最適化」とは、具体的には、どのような最適化で、どのような抽出結果の違いが生じるのか、が知りたいところです。

そこで、まずは簡単な実験をして違いを見てみます。

[4]簡単な実験での違いの例

TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの大雑把な違いを見るために、簡単な実験をしてみました。
なお、本来はレスポンスデータの詳細な違いやファイル形式の違いなど、いろいろ考慮すべきですが、話を簡単にするため、今回は、以下の条件で行っています。

  • Pythonクライアントライブラリのtext_detectionとdocument_text_detectionメソッドを固有パラメータ無しで呼び出しています。また、データはJPEGまたはPNGファイルの画像を利用しています。
    • これはBatchAnnotateImagesを利用した結果であり、PDFファイルなどのBatchAnnotateFilesとの結果比較は行っていません。
  • レスポンスは、text_annotationsフィールドの最初のEntityAnnotationの値(切り出したテキスト全体)のみ結果表示しています。
    • テキスト領域の可視化は、最初のEntityAnnotationを赤、それ以降を緑で領域を描画しています。
    • full_text_annotationフィールドの値は表示していませんが、少なくとも今回の実験に限ると、text_annotations[0].descriptionとfull_text_annotation.textの値が同じであることは確認しました。
  • 画像データについては著作権やプライバシーの問題もあり、また、私はこの分野に詳しくないため、自前の画像データのみ貼り付けることにしました。

なお、Pythonクライアントライブラリについては『Vision API Pythonクライアントライブラリを少し深堀りする(BatchAnnotateImages編)』を参照してください。

(1)スパースと思われる画像の例

スパースと思われる画像の例として、手前みそながら以前の記事『「エージェント」と「環境」を意識して業務システムを少し賢くしたい』で利用した図を試してみます。
この図を agent.jpg として保存、実行して結果を比較します。

【画像】


①TEXT_DETECTION

【実行コード】
print( client.text_detection(
    {'source':{'filename':'agent.jpg'}}
    ).text_annotations[0].description)

【検出されたテキスト】
業務システム
入力
処理
人間
記憶装置
出力

【テキスト領域の可視化】

【印象】
  • 正しく文字が検出され、余分な文字もありません。

②DOCUMENT_TEXT_DETECTION

【実行コード】
print( client.document_text_detection(
    {'source':{'filename':'agent.jpg'}}
    ).text_annotations[0].description)

【検出されたテキスト】
業務システム
入力
。)
处理
人間
記憶装置
出力

【テキスト領域の可視化】

【印象】
  • 「処」が「处」と認識されています。
  • 顔の部分を文字領域として、「。)」と認識しています。(絵文字として認識したのかと思いましたが、そうではないみたい?)
  • 細かく見ると、TEXT_DETECTIONは、「入力」が一つの領域としているのに対して、DOCUMENT_TEXT_DETECTIONでは「入」と「力」の二つに分割されています。(検出している文字は同じですが、領域の認識が異なります。)

(2)高密度と思われる画像の例

高密度と思われる画像として、こちらも以前の記事『「エージェント」と「環境」を意識して業務システムを少し賢くしたい』の冒頭部分を画像化しました。
この画像をblogtext.jpg として保存、実行して結果を比較します。

【画像】

①TEXT_DETECTION

【実行コード】
print( client.text_detection(
    {'source':{'filename':'blogtext.jpg'}}
    ).text_annotations[0].description )

【検出されたテキスト】
[1] はじめに
私にはA技術の深い専門的知識はありません。しかし、長年業務システムの設計や開発に携わってき
たこともあり、Al技術そのものよりも、Al技術を応用して業務システムを少し賢くする、という観点
から、A技術に注目しています。 大企業の業務システムや大規模パッケージは別として、中小規模の
業務システムやパッケージに携わっている方は、 同じような興味を持たれているかもしれません。
そこで、今回は業務システムのデータ入力をとっかかりとして、「便利」というより 「少し賢い」こ
とを目指した業務システムについて考えていることを書いてみます。 結論は当たり前の話ですが、そ
う考えた過程に参考になる部分があれば幸いです。

【テキスト領域の可視化】

【印象】
  • 「AI」の「I」が抜けて、「A技術」となっていますが、それ以外の文字は正確に抽出できています。
  • 元文書は[1]が全角ですが、抽出結果は半角になっています。
  • 【検出されたテキスト】には、ところどころ半角空白が入っています。
    • 表示したEntityAnnotation[0].descriptionには半角空白が入っていますが、それ以降の各EntityAnnotation.descriptionには半角空白はありません。
  • テキスト領域は、単語/文字や句読点の大きさが一定ではありません。(認識した単語/文字や句読点に応じたものになっている印象です。)

②DOCUMENT_TEXT_DETECTION

【実行コード】
print( client.document_text_detection(
    {'source':{'filename':'blogtext.jpg'}}
    ).text_annotations[0].description )

【検出されたテキスト】
[1] はじめに
私にはAI技術の深い専門的知識はありません。しかし、長年業務システムの設計や開発に携わってき
たこともあり、AI技術そのものよりも、AI技術を応用して業務システムを少し賢くする、という観点
から、AI技術に注目しています。大企業の業務システムや大規模パッケージは別として、中小規模の
業務システムやパッケージに携わっている方は、同じような興味を持たれているかもしれません。
そこで、今回は業務システムのデータ入力をとっかかりとして、「便利」というより「少し賢い」こ
とを目指した業務システムについて考えていることを書いてみます。結論は当たり前の話ですが、そ
う考えた過程に参考になる部分があれば幸いです。

【テキスト領域の可視化】

【印象】
  • 期待通りに、正しく抽出されています。ただし、元文書は[1]が全角ですが、抽出結果は半角になっています。
  • 【検出されたテキスト】には、TEXT_DETECTIONでは、ところどころ半角空白が入っていましたが、DOCUMENT_TEXT_DETECTIONでは、[1]と「はじめに」の間の半角空白のみとなっています。(半角空白の入り方が異なります。)
    • 表示したEntityAnnotation[0].descriptionには半角空白が入っていますが、それ以降の各EntityAnnotation.descriptionには半角空白はありません。
  • テキスト領域は、文字/単語や句読点の高さが、ほぼ同じとして認識されているようです。

(3)少し難しい漢字の例(文字の大きさによる違い)

文字の大きさによる認識の違いがあるのか気になったので、Windowsのペイントを使って、12ポイントのテキスト「竜髭菜はアスパラガスのことらしい」の画像を作って比較してみました。画数が多いという意味で少し難しい漢字(私には読み方も難しいです)を含んだ文章です。

【画像】12ポイントの文字列

①TEXT_DETECTION

【実行コード】
print( client.text_detection(
    {'source':{'filename':'asparagus12.png'}}
    ).text_annotations[0].description )

【検出されたテキスト】
竜霊菜はアスパラガスのことらしい

【印象】
  • 「髭」が「霊」となっていますが、それ以外は正しく抽出されています。

②DOCUMENT_TEXT_DETECTION

【実行コード】
print( client.document_text_detection(
    {'source':{'filename':'asparagus12.png'}}
    ).text_annotations[0].description )

【検出されたテキスト】
竜髭菜はアスパラガスのことらしい

【印象】
  • 全て正しく抽出されています。(「髭」も正しく抽出されています。)

③文字の大きさを変えてみたとき

12ポイントの「髭」で違いが出たので、TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONで認識できる漢字に違いがあるのか、それとも、文字の大きさを調整すれば認識できるのかを確認するため、同じテキストを24ポイントで画像を作り直して試してみました。

【画像】24ポイントの文字列

【検出されたテキスト】
竜髭菜はアスパラガスのことらしい

TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONは同じ結果になりました。

【印象】
  • 文字を大きくすると、TEXT_DETECTIONでも「髭」を正しく抽出できました。
  • どの程度の大きさが基準なのか分かりませんが、一定以上の大きさであれば、認識できる漢字に違いはないのかもしれません。(が、全てを試すことはできませんので、推測です。)

(4)手書き文書の例

手書きについては、いろいろ試してみたいところですが、まずは、Googleのガイドにある(手書きでGoogle Cland Platformと書いてある)サンプル画像で違いが出るか試してみることにしました。(著作権の関係がよくわかりませんので、画像はここには貼りません。)

サンプル画像はこちらを参照してください。

①TEXT_DETECTION

【実行コード】
print( client.text_detection({'source':{
    'image_uri':'gs://vision-api-handwriting-ocr-bucket/handwriting_image.png'
    }}).text_annotations[0].description )

【検出されたテキスト】
Google Cland
Platform

【印象】
  • 正しく抽出されています。

②DOCUMENT_TEXT_DETECTION

【実行コード】
print( client.document_text_detection({'source':{
    'image_uri':'gs://vision-api-handwriting-ocr-bucket/handwriting_image.png'
    }}).text_annotations[0].description )

【検出されたテキスト】
Google Cloud
308
Platform

【印象】
  • 「308」という余分な文字を抽出しています。
    • 308を抽出したテキスト領域を見てみると、Googleのテキスト領域と重なって、GoogleのGooあたりの部分領域を308と解釈しているように見えます。

(5)TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの二つを同時に指定した場合の例

DOCUMENT_TEXT_DETECTION と TEXT_DETECTIONの違いには直接関係ありませんが、説明文に、「DOCUMENT_TEXT_DETECTION と TEXT_DETECTION の両方がリクエストされた場合、優先されます。」とありましたので、確認しておきます。

【画像】
(1)のスパースと思われる画像の例を用いて、結果を比較してみます。

【実行コード】
print( client.annotate_image({
    'image':  {'source': {'filename': 'agent.jpg'} },
    'features':[
        {'type': vision.enums.Feature.Type.TEXT_DETECTION},
        {'type': vision.enums.Feature.Type.DOCUMENT_TEXT_DETECTION}
    ]
}).text_annotations[0].description)

【検出されたテキスト】
業務システム
入力
。)
处理
人間
記憶装置
出力

結果は、(1)のDOCUMENT_TEXT_DETECTIONの結果と同じですので、説明の通り、2つを同時に指定すると、DOCUMENT_TEXT_DETECTIONの結果になりました。

このことから、TEXT_DETECTIONと、DOCUMENT_TEXT_DETECTIONの両方の結果が欲しい場合は、リクエストを二つ作る必要があります。
Pythonクライアントライブラリを利用する場合は、text_detectionメソッドとdocument_text_detectionメソッドをそれぞれ呼び出すか、batch_annotate_imagesメソッドを利用して2つのリクエストをバッチ送信する必要があります。

[5]実験から受ける印象

ここまで、簡単な画像を通して、TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの違いを大雑把に見てみましたが、これだけでも違いが出るのは少し驚きでもありました。
このことからも、解析したい画像に適した機能を選ぶ必要があることは分かります。

そして選択基準として、具体的に「~向けに最適化」とはどのようなものか、を知りたかったわけですが、これだけの実験では、なかなか本質に迫ることは難しいです。
そこで、ひとまず、今回の実験から受けた印象、として、まとめてみたいと思います。

<印象>
  • TEXT_DETECTIONは、大雑把に文字を探して、単語(形態素?)レベルの認識を基本としているように見える。
  • DOCUMENT_TEXT_DETECTIONは、より細かく文字を探して、近接する文字をまとめて文章レベルを認識しようとしているように見える。
  • TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの両方とも、検出結果の文字は、UNICODEの正規化した文字になるように思える。(半角や全角の違いは区別せず、正規化した文字コードに対応させる。)

もっとざっくり言ってしまうと、メガネに遠近両用というのがありますが、この「遠」と「近」の違いのようにも思えます。
このような印象がいい線を行っているのかどうかは、今後、レスポンスデータを細かく見たり、実験を増やして検討してきたいと思います。

以下に、上記のような印象を持った理由を書いておきます。

TEXT_DETECTIONは単語レベル、DOCUMENT_TEXT_DETECTIONは文章レベルを意識しているという印象について

(2)の文章の例で、TEXT_DETECTIONのテキスト領域は均等ではありませんが、DOCUMENT_TEXT_DETECTIONのテキスト領域は高さが揃っています。近傍の単語を集めて文章として扱おうとしているように見えました。
このことは、DOCUMENT_TEXT_DETECTIONが、書類や本などの文章中心の画像向けに最適化しているということかと思いました。

TEXT_DETECTIONは、大雑把に文字を探し、DOCUMENT_TEXT_DETECTIONは、できるだけ細かく文字を探そうとしているという印象について

DOCUMENT_TEXT_DETECTIONが、(1)の画像で顔から「。)」を検出したり、(4)で「308」を検出していて、人間から見ると余計なところを文字として扱おうとしています。
DOCUMENT_TEXT_DETECTIONが高密度テキストに最適化されているというのは、本のように、文字が多く含まれていることを前提として、文字を多く探すということのようにも思えました。また、その結果として、検出する文字数がTEXT_DETECTIONより多くなる印象を受けます。
こう考えると、DOCUMENT_TEXT_DETECTIONは、(1)と(4)のように、スパースな画像に対しては余計な領域を認識しやすいと考えることもできます。((4)はスパースではないのかもしれませんが)

反対に、TEXT_DETECTIONは、(1)と(4)は余分な文字を検出していません。
大きな画像内のテキストのスパース領域に最適化というのは、文字が少ないことを前提に、細かく文字を探さず、大雑把に見ることで、余分な領域を抽出しないようにしていると考えることもできます。

根拠の薄い想像ですが、ノイズ(または背景)除去あるいは画像圧縮のような前処理が行われた後に残る文字検出のヒントになる情報が、TEXT_DETECTIONのほうが少なく、DOCUMENT_TEXT_DETECTIONのほうが多い印象です。
その結果として、TEXT_DETECTIONは(1)や(4)で不要な文字の検出がないかわりに、(2)では「I」を検出できず、(3)の12ポイントで「髭」を誤認識しています。一方、DOCUMENT_TEXT_DETECTIONは、(2)で「I」を検出し、(3)の12ポイントで「髭」を正しく検出でき、(人間から見ると)余分な文字まで検出しています。
もっとも、これだけでは(1)で「処」を「处」と認識する説明にはならないので、この考察は当てになりません。(なら、書くな!と言われそうですが。。。)

このあたりについては、もしGoogleの説明や論文などがあれば、読んでみたいところです。(理解できるかどうかは別ですが(笑))

検出結果の文字は、UNICODEの正規化が行われているように見えることについて

結果データの文字コードについてGoogleのドキュメントを探してみましたが、関連する記述を見つけることができませんでした。
(2)の文章の例で、[1]の結果は[1]となっていました。また今回の実験には含めていませんが、半角カタカナ文字が全角カタカナとして抽出されるようですので、何らかのUNICODEの正規化処理が行われていると思います。

これについては、別記事で正規化処理に関係しそうな文字をいろいろ試して確認したいと思います。

[6]最後に

文字と模様の違いはどこにあるのか、と考えててみただけでも、OCRがとても難しい技術であることは想像できます。
Vision APIの機能を見ると、Googleをもってしても、1つのOCR機能ではなく、画像の種類によって2つの機能を使い分けないといけないということからも、難しい領域であることが何となく伺えます。しかし、逆に言えば、OCRの難しさを良く理解して作られているとも思いました。
今は2つのOCRの機能がありますが、将来は1つに統合されるのか、あるいは、新しいOCR機能(スパースと高密度とは違う何か)が増える方向に行くのか、興味がわいてきました。といいつつも、Googleでは、Document AIサービスでフォームやテーブルの解析を扱い始めていますので、OCRの機能レベルの拡張よりも、より上位のサービスで対応していくという方向かもしれません。

(参考)
Document AIについては以下の記事を書きましたので、宜しければ参考にしてください。


コメント

このブログの人気の投稿

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

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

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