Vision API Pythonクライアントライブラリを少し深堀りする(BatchAnnotateImages編)

本記事では、『Vision API クライアントライブラリの概要(Python編)』に続いて、Vision APIのPythonクライアントライブラリのパッケージ内容を概観し、画像から特徴検出を行う同期メソッド(BatchAnnotateImagesに関連するメソッド)について少し深堀します。

<<お知らせ(2020/10/05)>>
Vision API Pythonクライアントライブラリの新バージョン(v2.0.0)が2020年9月29日付でリリースされています。本記事の内容はv1.0.0をもとに書いていますので、v2.0.0とは異なる内容、V2.0.0では動作しないコードを含んでいます。


【目次】

[1]はじめに

本記事は、『Vision API クライアントライブラリの概要(Python編)』の続きで、具体的にPythonの Vision API クライアントライブラリを深堀していきたいと思います。

ライブラリの機能を理解しようとすると、少なくとも
  • ライブラリの使い方(構成やコードの書き方など)
  • 処理結果の扱い(どのような結果が得られるかを含む)
  • 認証や通信など
を考えないといけないと思いますが、今回は、ライブラリの使い方に絞って見ていきます。
(それ以外は、また別の記事で書きたいと思います。)

(1)参考情報など

Vision APIの機能概要と仕様については、以下のドキュメントがあります。

また、Vision API Pythonのクライアントライブラリについては、ライブラリリファレンスに加えて、GitHubでソースコードが公開されています。

(補足:2010/10/18)
上記リファレンスとソースコードは最新版です。本記事はv1.0.0を対象としていますので、v1.0.0のリファレンスとソースコードはこちらのURLから参照できます。


そして、Vision APIのRPCサービスのメソッドやデータ構造(メッセージ)は、Protocol Buffersを使って定義されています。

Protocol Buffersを知らないと Vision APIを利用できない、というわけではありませんが、概要だけでも知っておくと、一般的なAPIの設計や gRPCへの理解も深まり、勉強になると思います。

(2)ライブラリを利用するための準備作業

Pythonクライアントライブラリを用いてVision APIを利用するには、
  • Google Cloud Platform(GCP)側での利用設定
  • クライアントライブラリを利用する環境での設定
が必要です。

本記事では、認証や通信などには立ち入らないため、公式ドキュメントの手順に従った方法を前提としています。

具体的には、サービスアカウントを作成します。そして認証情報として、サービスアカウントの秘密鍵ファイル(JSONファイル)を環境変数(GOOGLE_APPLICATION_CREDENTIALS)に設定します。

例)Linuxの場合
export GOOGLE_APPLICATION_CREDENTIALS="秘密鍵ファイルのパス"

そして、Vision APIのクライアントライブラリをインストールします。
pip install --upgrade google-cloud-vision

これらの手順については、以下の公式ドキュメントが参考になります。

もしColaboratoryでVision APIを利用する場合は、『Colaboratory+GoogleドライブでVision APIの実験環境を作る』を参照してください。


[2]google.cloud.visionパッケージ概要

Pythonクライアントライブラリは、RPCでImageAnnotatorサービスにアクセスするラッパーのように作られています。

Vision APIのImageAnnotatorサービスとImageAnnotatorClientクラス

クライアントライブラリは、google.cloud.visionパッケージをインポートして利用します。
from google.cloud import vision

このパッケージが公開するImageAnnotatorClientとenums、typesを利用してプログラミングすることになります。

(1)vision.ImageAnnotatorClientクラス

ImageAnnotatorClientは、Vision APIの特徴検出機能を提供するImageAnnotatorサービスとRPCで通信するクラスです。このクラスには、ImageAnnotatorサービスに対応するメソッドに加えて、より簡単に利用できるメソッドも用意されています。

ImageAnnotatorClientクラスのインスタンスを作成して各種メソッドを利用します。
client = vision.ImageAnnotatorClient()

このコードは初期化用パラメータは渡していませんので、認証を含む通信手順などはデフォルト動作となります。
例えば、認証については、環境変数GOOGLE_APPLICATION_CREDENTIALSにサービスアカウントの秘密鍵ファイル(JSON)が設定されていれば、それが利用されます。

これらの動作はカスタマイズ可能ですが、本記事では認証や通信に深入りしませんので、説明を割愛します。

各メソッドの引数(リクエストデータなど)と戻り値(レスポンスデータなど)の表現には、enumsやtypesの定義を利用することができます。

(2)vision.enums(定数定義)

Vision APIで定義されている各種定数は、Pythonのenum.IntEnumで定義されています。
但し、これは、Vision APIのサンプルコードには使われているものの、なぜかPythonクライアントライブラリのリファレンスには記載されていないようです。
どのような定義があるかは、ソースコードを参照するのが手っ取り早いです。

例えば、テキスト検出(TEXT_DETECTION = 5)の定義は以下のようになります。
ftype = vision.enums.Feature.Type.TEXT_DETECTION
print("type={},val={}".format(type(ftype),ftype))
これを実行すると、type=<enum 'Type'>,val=5 と表示されます。

(3)vision.types(データ型定義)

Vision APIのRPCサービスのメソッドやデータ構造(メッセージ)は、Protocol Buffersを使って定義されています。そして、Vision APIのProtocol Buffersの定義ファイル(.proto)は、クライアントライブラリのソースコードとともに公開されています。

ここで、vision.typesのコード(https://github.com/googleapis/python-vision/blob/release-v1.0.0/google/cloud/vision_v1/types.py)を見ると、.protoからコンパイルされたPythonクラスなど(_pb2.py)をインポートしています。

例えば、リクエストデータであるBatchAnnotateImagesRequest型のインスタンスは、以下のように作成できます。
req = vision.types.AnnotateImageRequest()
print(type(req))

これを実行すると、type=<class 'google.cloud.vision_v1.types.AnnotateImageRequest'>と表示されます。

このクラス階層を見るために以下のコードを実行してみます。
import inspect
for item in inspect.getmro(vision.types.AnnotateImageRequest):
  print(item)

実行すると以下のように表示され、Protocol BuffersのMessageクラスを継承しているクラスであることが分かります。
<class 'google.cloud.vision_v1.types.AnnotateImageRequest'>
<class 'google.protobuf.pyext._message.CMessage'>
<class 'google.protobuf.message.Message'>
<class 'object'>

このようなProtocol Buffersの型を利用する利点の一つは、データの型チェックが行われることです。例えば、フィールドの型と異なる型の値を代入しようとしたり、定義されていないフィールドに値を設定しようとすると例外がスローされます。

例1)存在しないフィールドに値を設定すると例外がスローされる
req = vision.types.AnnotateImageRequest()
req.dummy_field = 1
結果:AttributeError: 'AnnotateImageRequest' object has no attribute 'dummy_field'

例2)数値型のフィールドに文字列を設定すると例外がスローされる
feature = vision.types.Feature()
feature.max_results = "value"
結果:TypeError: 'value' has type str, but expected one of: int, long

(4)補足

v1.0.0リリースにおける、visionのソースコードは以下のようになっています。

from __future__ import absolute_import

from google.cloud.vision_v1 import ImageAnnotatorClient
from google.cloud.vision_v1 import ProductSearchClient
from google.cloud.vision_v1 import enums
from google.cloud.vision_v1 import types

__all__ = ("enums", "types", "ImageAnnotatorClient", "ProductSearchClient")

ここから、以下のことが分かります。
  • visionはvision_v1を割り当てています。(vision_v1をインポートしても同じ)
  • "enums", "types", "ImageAnnotatorClient"", "ProductSearchClient"を明示的に公開しています。(今回はProductSearchClient(商品検索)は扱いません)

[3]BatchAnnotateImagesに関連するメソッドの構成

ImageAnnotatorサービスのBatchAnnotateImagesメソッドは、JPEGなどの画像から特徴抽出を行う同期メソッドです。

主な特徴は以下の通りです。
  • 画像形式は、JPEG、PNG、GIF、BMP、WEBP、RAW、ICOです。(但し、アニメーションGIFは最初のフレームのみ)
  • 検出できる特徴は、Vision APIの機能リストにあるもの全てです。一つの画像に対して同時に複数の特徴を検出できます。
  • 一回のメソッド呼び出しで、16枚まで画像の特徴検出ができます。
  • 同期処理のため、メソッドの戻り値として指定した特徴検出の結果を得ることができます。このため、非同期版に比べて手軽に利用できます。

このBatchAnnotateImagesに対応するImageAnnotatorClientクラスのメソッドは、階層的に構成されています。

ImageAnnotatorClientクラスのメソッド構成

  • batch_annotate_images
    • ImageAnnotatorサービスのBatchAnnotateImagesメソッドに直接対応するメソッドです。
    • 複数の画像に対する特徴検出を一括して処理できます(バッチ処理)。
  • annotate_image
    • batch_annotate_imagesを1枚の画像に限定して(1枚の画像に対して複数の特徴検出は可能)、少し使いやすくなっています。
  • 特徴検出毎のメソッド(text_detectionなど)
    • annotate_imageを、さらに1つの特徴検出に限定することで、より使いやすくなっています。

最も汎用的なbatch_annotate_imagesを利用すれば全ての機能が利用できますが、少々コーディングが面倒です(もしかしたら、可読性のほうが問題かもしれません)。
これに対して、用途を絞ることで、より使いやすいメソッドを提供する構成となっているように思います。

これらの基本的な使い分けは以下のようになると思います。
  • 1枚の画像から1つの特徴検出をするだけでよい場合:
    • 特徴検出毎のメソッド(text_detectionなど)を利用する
  • 画像は1枚でよいが、複数の特徴検出を同時に行いたい場合:
    • annotate_imageを利用する
  • 上記以外:複数の画像を一括して処理したい(バッチ処理)
    • batch_annotate_images
簡易メソッドでは設定できない少し特殊なパラメータもあるため、用途によっては、より制限の少ないメソッド(最終的にはbatch_annotate_images)を利用する必要があります。

(参考)ImageAnnotatorClientクラスの実装構成

ImageAnnotatorClientクラスの定義は以下のようになっています。

from google.cloud.vision_helpers import VisionHelpers
from google.cloud.vision_v1 import types
from google.cloud.vision_v1.gapic import enums
from google.cloud.vision_v1.gapic import image_annotator_client as iac

@add_single_feature_methods
class ImageAnnotatorClient(VisionHelpers, iac.ImageAnnotatorClient):
    __doc__ = iac.ImageAnnotatorClient.__doc__
    enums = enums

__all__ = ( "enums", "types", "ProductSearchClient", "ImageAnnotatorClient",)


実装方法の勉強になりました。

[4]リクエストデータの表現方法

ImageAnnotatorサービスのBatchAnnotateImagesメソッドの引数(リクエストデータ)は、BatchAnnotateImagesRequestです。

関連するデータ構造を図で表してみます。

Vision API:BatchAnnotateImagesRequestのデータ構造

クライアントライブラリのメソッドの引数は、BatchAnnotateImagesRequest全体ではなく、メソッドが指定するデータ(例えばImageなど)を渡すだけで、クライアントライブラリが内部でProtocol Buffersのデータ型(BatchAnnotateImagesRequest)を構成して通信してくれます。

クライアントライブラリに渡すデータは、Protocol Buffersのデータ型で直接表現する(vision.typesを利用する)方法と、Pythonのディクショナリ(dict)で表現する方法の二通りがあります。これらを混在させることもできます。

Googleのガイドにあるサンプルコードでも、二つの方法が混在していますので、どちらがよい、という問題ではなく、書きやすさや可読性で選択したのでよいと思います。

ところで、Protocol Buffersを良く知っている人やGoogle APIに慣れている人は問題ないと思いますが、そうでない人は少し戸惑うかもしれません。

AnnotateImageRequestの基本要素はImage、Feature、ImageContextですので、これらの表現方法を確認しておきます。

(1)Image:画像のバイナリデータを送信する場合

contentフィールドに、画像ファイルのバイナリデータを設定します。

例えば、以下のように画像ファイルのデータをbinary_content変数に読み込んだとします。
import io
with io.open('画像ファイルのパス', 'rb') as image_file:
  binary_content = image_file.read()

①vision.typesで表現

img = vision.types.Image()
img.content = binary_content

フィールドの値は、オブジェクト作成時にキーワード引数として渡すこともできます。
img = vision.types.Image(content = binary_content)

この書き方は、Googleのガイドのサンプルにもある書き方で、よく用いられる書き方のようです。

②ディクショナリで表現

Imageオブジェクトのフィールドと値をそのまま「キー:値」のペアで表現します。
img = {'content': binary_content }

③ディクショナリでファイル名を指定する表現

annotate_imageまたはtext_detectionなどの特徴検出毎のメソッドに限っては、ディクショナリを用いて以下のようにファイルパスを指定すると、その画像ファイルを読み込んでくれる機能が実装されています。(ドキュメントには記載がないように思われますので、非公式な機能かもしれません。)
img = {'source': {'filename': 'ファイルパス'} }

なお、この機能はbatch_annotate_imagesメソッドでは利用できません(annotate_imageメソッド内で実装されているため)。また、vision.typesのオブジェクトにはfilenameフィールドがないため、利用できません。

(2)Image:画像のURIを指定する場合

画像の場所を、Google Cloud Storageのパス(gs://bucket_name/object_nameの形式)、あるいはWeb上で公開されている画像のURLで指定する方法です。
ImageSourceのimage_uriにuriを設定し、Imageのsourceフィールドに設定します。

①vision.typesで表現

img = vision.types.Image()
img.source.image_uri = uri

この場合、キーワード引数で指定する方法は以下のようになります。
img = vision.types.Image(source = vision.types.ImageSource( image_uri = uri))

ちなみに、以下のようにフィールドのオブジェクトを置き換えることはできません。(例外になります。)
img = vision.types.Image()
img.source = vision.types.ImageSource() # ※ここで例外になる
img.source.image_uri = uri
結果:AttributeError: Assignment not allowed to field "source" in protocol message object.

②ディクショナリで表現

オブジェクトのネストも簡単に表現できます。
img = {'source': {'image_uri': uri} }

ちなみにvision.typesと混在しても動作します。(お勧めするものではありません)
img = {'source': vision.types.ImageSource( image_uri = uri)}

(3)Feature:検出したい特徴の指定

vision.enums.Feature.Typeを利用してFeatureを作ります。(以下はTEXT_DETECTIONの例です。)

①vision.typesで表現

ft = vision.types.Feature()
ft.type = vision.enums.Feature.Type.TEXT_DETECTION

キーワード引数で指定する方法は以下のようになります。
ft = vision.types.Feature(type = vision.enums.Feature.Type.TEXT_DETECTION)

必要に応じて、max_resultsやmodelも指定します。
例えば、顔検出で顔を1つのみ検出したい場合はmax_resultsに1を指定します。
ft = vision.types.Feature()
ft.type = vision.enums.Feature.Type.FACE_DETECTION
ft.max_results = 1
ft.model = "builtin/stable"

キーワード引数で指定する方法は以下のようになります。
ft = vision.types.Feature( type=vision.enums.Feature.Type.FACE_DETECTION, max_results = 1, model = "builtin/stable")

②ディクショナリで表現

ft = {'type': vision.enums.Feature.Type.TEXT_DETECTION}

必要に応じて、max_resultsやmodelも指定します。
vision.typesの例は、以下のように書けます。
ft = {'type': vision.enums.Feature.Type.FACE_DETECTION, 'max_results':1,  'model':"builtin/stable"}

(4)ImageContext:特徴タイプ固有のパラメータを設定する場合

特徴タイプによっては、パラメータを設定できるものがあります。クロップヒントの設定例をみてみます。

①vision.typesで表現

im_context = vision.types.ImageContext()
im_context.crop_hints_params.aspect_ratios.extend([1.77])
このようにも書けます。
im_context = vision.types.ImageContext(
        crop_hints_params=vision.types.CropHintsParams(aspect_ratios=[1.77]))

②ディクショナリで表現

im_context = {'crop_hints_params': {'aspect_ratios':[1.77]} }

[5]特徴タイプ毎のメソッド(text_detectionなど)

1枚の画像に対して1つの特徴検出を行うことができるメソッドです。
以下のように特徴タイプ毎にメソッドが用意されています。

メソッド名 特徴タイプ(vision.enums.Feature.Type)
face_detection FACE_DETECTION
landmark_detection LANDMARK_DETECTION
logo_detection LOGO_DETECTION
label_detection LABEL_DETECTION
text_detection TEXT_DETECTION
document_text_detection DOCUMENT_TEXT_DETECTION
safe_search_detection SAFE_SEARCH_DETECTION
image_properties IMAGE_PROPERTIES
crop_hints CROP_HINTS
web_detection WEB_DETECTION
product_search PRODUCT_SEARCH
object_localization OBJECT_LOCALIZATION

各メソッドの引数と戻り値は以下のようになります。
メソッド名(image, max_results=None, retry=None, timeout=None, **kwargs)


引数
  • image:(必須)
    • vision.types.Image型(ディクショナリもOK)で、解析する画像を指定します。
    • 「リクエストデータの表現方法」の(1)(2)を参考に設定できます。
  • max_results:(オプション)
    • Feature.max_resultsに該当します。
    • 必要に応じて、特徴検出の最大数を指定できます。なお、TEXT_DETECTION、DOCUMENT_TEXT_DETECTION、CROP_HINTSには適用されないとされています。
  • retry:(オプション)
    • リトライ設定
  • timeout:(オプション)
    • タイムアウトまでの秒数
  • **kwargs:
    • AnnotateImageRequestに追加する属性。
    • 特徴タイプ固有のパラメータ(ImageContext)をimage_contextフィールドで与える場合に利用できます。
    • 「リクエストデータの表現方法」の(4)を参考に設定できます。
戻り型
  • vision.types.AnnotateImageResponse
  • (注意)BatchAnnotateImageResponseではありません。

(1)利用例

必須項目はimageだけですので、オプションパラメータを指定する必要が無ければ、「リクエストデータの表現方法」の(1)(2)で作ったimgを指定するだけで利用できます。
response = client.メソッド名(img)

特徴検出の最大数を指定したい場合は、max_results引数で指定します。

特徴タイプ固有のパラメータを設定する場合は、キーワード引数を利用して(kwargs)、image_contextに「リクエストデータの表現方法」(4)で作ったim_contextを指定します。
response = client.メソッド名(img,image_context=im_context)

以下に、「リクエストデータの表現方法」のうち、引数にディクショナリを利用する例を書いてみます。

(例1)画像のバイナリデータを送信する

# binary_content = 画像ファイルを読み込んだもの
response = client.text_detection({'content': binary_content })

(例2)ファイル名を指定して画像のバイナリデータを送信する

response = client.text_detection({'source': {'filename': 'ファイルパス'} })

(例3)画像のURLを指定する

response = client.text_detection( {'source': {'image_uri': uri} })
※uriには、Google Cloud Storageのパス(gs://bucket_name/object_nameの形式)、あるいはWeb上で公開されている画像のURLを指定します。

(例4)顔検出で顔を1つのみ検出したい場合、max_resultsに1を指定する

response = client.face_detection({'source': {'image_uri': uri}},1)

(例5)crop_hintsのパラメータを指定する

response = client.crop_hints(
  {'source': {'image_uri': uri}},
  image_context={'crop_hints_params': {'aspect_ratios':[1.77]} })

(2)Feature.modelに関する注意事項

このメソッドでは、Featureが内部的に作られます。Featureのフィールドのうち、typeはメソッド内で設定され、max_resultsは必要に応じてメソッドの引数で設定できますが、modelは設定できないようです。
通常の利用においては問題ありませんが、実運用などにおいて、モデルのバージョンアップが関係する場合は、annotate_imageまたは、batch_annotate_imagesを利用する必要があるかもしれません。(『Vision APIのBatchAnnotateImagesメソッド(画像からの同期特徴抽出)を少し深堀りする/[5]モデルのバージョンアップへの対応』を参照してください。)

[6]annotate_imageメソッド

1枚の画像に対して複数の特徴検出を行うことができるメソッドです。

annotate_image(request, retry=None, timeout=None)

引数
  • request:(必須)
    • vision.types.AnnotateImageRequest型(ディクショナリもOK)を指定します。
    • リクエストデータの表現方法」を参考に、vision.typesまたはディクショナリで指定できます。
  • retry:(オプション)
    • リトライ設定
  • timeout:(オプション)
    • タイムアウトまでの秒数

戻り型
  • vision.types.AnnotateImageResponse
  • (注意)BatchAnnotateImageResponseではありません。

(1)利用例

リクエストデータの表現方法」を参考にAnnotateImageRequestを組み立てます。

現実的な例ではありませんが、複数特徴+固有パラメータの例として、以下ではTEXT_DETECTIONとCROP_HINTSの2つの特徴タイプを指定し、かつCROP_HINTS固有のパラメータを設定しています。
なお、AnnotateImageRequestのfeaturesフィールドを設定しない場合は、全ての特徴検出が行われますので注意が必要です(後述)。

①vision.typesで表現

以下ははベタな書き方ですが、キーワード引数を用いた書き方もできます。
req = vision.types.AnnotateImageRequest()
# imageの指定
# contentかimage_uriのどちらかを指定する
req.image.content = binary_content
#req.image.source.image_uri = uri

# featuresの指定
ft1 = req.features.add()
ft1.type = vision.enums.Feature.Type.TEXT_DETECTION
ft2 = req.features.add()
ft2.type = vision.enums.Feature.Type.CROP_HINTS

# CROP_HINTSのパラメータを指定
req.image_context.crop_hints_params.aspect_ratios.extend([1.77])

# 呼び出し
response = client.annotate_image( req )

②ディクショナリで表現

vision.typesで表現した例をディクショナリで書いてみます。
なお、ディクショナリの表現では、ファイルパスを指定して画像を読み込ませることもできます。
req = {
    # mageの指定: content、image_uri、filenameのどれかを指定する
    'image':  {'content': binary_content },
    # 'image':  {'source': {'image_uri': uri} },
    # 'image':  {'source': {'filename': 'ファイルパス'} },

    # featuresの指定
    'features':[
        {'type': vision.enums.Feature.Type.TEXT_DETECTION},
        {'type': vision.enums.Feature.Type.CROP_HINTS}
    ],
    # CROP_HINTSのパラメータを指定
    'image_context':  {'crop_hints_params': {'aspect_ratios':[1.77]} }
}
# 呼び出し
response = client.annotate_image( req )

(2)featuresに関する注意事項

本来BatchAnnotateImagesメソッドは、特徴タイプを1つ以上指定する必要がありますが、annotate_imageメソッドでは、特徴タイプが指定されていない場合、エラーとはならず、内部で全ての特徴タイプを設定するように動作します。
全ての特徴タイプの結果を得たい場合は便利な機能ですが、特徴タイプの指定を忘れると、意図しない特徴検出まで行われてしまうため、注意が必要です。(無駄な課金が発生する可能性もあるかもしれません。)

[7]batch_annotate_imagesメソッド

16個までのAnnotateImageRequestをまとめて送信できます。
batch_annotate_images(requests, parent=None, retry=google.api_core.gapic_v1.method.DEFAULT, timeout=google.api_core.gapic_v1.method.DEFAULT, metadata=None)

引数
  • requests:(必須)
    • vision.types.AnnotateImageRequest型(ディクショナリもOK)のリストを指定します。
    • 基本的には、annotate_imagesで与えるrequestのリストです。
  • parent:(オプション)
  • retry:(オプション)
    • リトライ設定
  • timeout:(オプション)
    • タイムアウトまでの秒数
  • metadata:(オプション)
    • gRPCのmetadataを追加できるようです。
戻り型
  • vision.types.BatchAnnotateImagesResponse
  • (注意)AnnotateImageResponseではありません。

(1)利用例

annotate_imageと同じようにAnnotateImageRequestを作成します。作成方法は、vision.typesを使って作成しても、ディクショナリでもかまいません。これらのリクエストをリストで指定します。

# AnnotateImageRequestのデータとして、req1, req2を作成したとする。
requests =  [ req1, req2 ]
# 呼び出し
batch_response = client.batch_annotate_images( requests )

特徴毎のメソッドやannotate_imageと違って、以下の点については注意が必要です。
  • ディクショナリでリクエストを作っても、source.filenameによるファイルの読み込みは利用できません。(annotate_imageが実装する機能であるため)
  • featuresは必ず指定する必要があります(annoate_imageとは異なる動作。)。指定しない場合は、例外になります。
    • InvalidArgument: 400 Request must specify image and features.

[8]レスポンスデータとエラー

ImageAnnotatorサービスのBatchAnnotateImagesメソッドの戻り値(レスポンスデータ)は、BatchAnnotateImagesResponseです。
関連するデータ構造を図で表してみます。

Vision API:BatchAnnotateImagesResponseのデータ構造


batch_annotate_imagesメソッドの戻り値は、BatchAnnotateImagesResponseです。各AnnotateImageRequestに対応する結果(AnnotateImageResponse)を、responsesフィールドからリストで取得できます。

一方、annotate_imageとtext_detectionなどの特徴毎のメソッドの戻り値は、AnnotateImageResponseとなります。

リクエストはvision.typesとディクショナリのどちらかで表現することができましたが、レスポンスはvision.typesのProtocol Buffersのクラスとして返却されます。
もし、結果をその他の形式で扱いたい場合は、「JSON、ディクショナリとの相互変換」を参照してください。

AnnotateImageResponseに設定されるフィールドは、特徴タイプに応じて決まります。
エラー情報はerrorフィールドから得られます(が、画像を1枚だけ処理する場合は、殆どの場合例外がスローされる形になると思います)。

特徴タイプ毎の処理やフィールドについては、ボリュームも大きいことから、別の記事で書きたいと思います。

(追記:2020/10/27)
OCR関連のレスポンスデータ(fulTextAnnotationとtextAnnotatins)については、以下の記事を書きましたので参考にしてください。


[9]JSON、ディクショナリとの相互変換

<<お知らせ(2021/03/13)>>
以下のJSON、ディクショナリとの相互変換の内容は、クライアントライブラリのv1.0.0で動作しますが、v2.0.0以降では動作しません。
v2.0.0以降のバージョンで相互変換を行う場合は、記事『Vision APIのレスポンスデータとJSONやディクショナリを相互変換する(Proto Plus for Python、google.protobuf.json_format)』を参照してください。

メソッドの戻り値は、vision.typesで定義されているProtocol Buffersのクラスとなっています。これらは型付けされたプログラムでは扱いやすい形式ですが、様々な理由からJSONやディクショナリに変換したい場合もあると思います。また、JSONやディクショナリからレスポンスデータを再生したい場合もあるかもしれません。(結果を保存しておき、後に読み込んで処理を行うような場合など。)

そこで、google.protobuf.json_formatモジュールを利用したProtocol BuffersのクラスとJSONやディクショナリとの相互変換する方法を見ていきます。

(1)google.protobuf.json_format モジュール

Protocol Buffers Python API に json_formatというモジュールがあります。

このProtocol Buffers Python APIは、Vision APIのクライアントライブラリでも利用しているため、追加でpipなどによるインストールは不要で、importするだけで利用することができます。

(2)Protocol BuffersのクラスとJSONとの相互変換

json_formatモジュールを利用すると、Vision APIのRESTインターフェイスで定義されるJSON形式と相互変換できます。

なお、RESTとRPCでは、フィールド名が微妙に異なります。いわゆるスネークケースとキャメルケースの違いです。
(例えば、Protocol Buffersのフィールド名がスネークケースでface_annotationsと定義しているのに対して、JSONのフィールド名はキャメルケースでfaceAnnotationsと定義しています。)

①MessageToJson:Protocol Buffers=>JSON

from google.protobuf.json_format import MessageToJson
js = MessageToJson( response )
print(js)

この場合、フィールド名はキャメルケースに変換されてJSON文字列に変換されます。 なお、preserving_proto_field_name=True を引数に追加すると、キャメルケースの変換は行われず、Protocol Buffersで定義されたフィールド名のままJSON文字列に変換されます。 その他のオプションもありますので、必要に応じてリファレンスを参照してください。

②Parse:JSON=>Protocol Buffers

from google.protobuf.json_format import Parse
response  = Parse( js, vision.types.AnnotateImageResponse())

第一引数にJSON文字列、第二引数にProtocol Buffersのクラスのインスタンスを指定することで、インスタンスにJSONの内容を取り込んでくれます。
なお、JSONのフィールド名が、キャメルケースあるいはProtocol Buffersで定義されている名前(スネークケース)のどちらでも取り込んでくれます。
オプションもありますので、必要に応じてリファレンスを参照してください。

(3)Protocol Buffersのクラスとディクショナリとの相互変換

ディクショナリに対してもJSONとの相互変換と同じことができます。

①MessageToDict:Protocol Buffers=>ディクショナリ

from google.protobuf.json_format import MessageToDict
js_dict = MessageToDict( response, preserving_proto_field_name = True )
print(js_dict)

この場合、フィールド名はキャメルケースに変換されず、Protocol Buffersで定義しているフィールドのまま出力されます。もし、JSONと同様にキャメルケースに変換したい場合は、preserving_proto_field_name = Falseとするか、引数を省略します。
その他のオプションもありますので、必要に応じてリファレンスを参照してください。

②ParseDict:ディクショナリ=>Protocol Buffers

from google.protobuf.json_format import ParseDict
response  =ParseDict( js_dict, vision.types.AnnotateImageResponse())

第一引数にディクショナリ、第二引数にProtocol Buffersのクラスのインスタンスを指定することで、インスタンスにディクショナリの内容を取り込んでくれます。
なお、ディクショナリのフィールド名が、キャメルケースあるいはProtocol Buffersで定義されている名前のどちらでも取り込んでくれます。
オプションもありますので、必要に応じてリファレンスを参照してください。

コメント

このブログの人気の投稿

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

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

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