Microsoft Azure Cognitive Services Faceを動作させてみる その2


その1からの続きです。

動作させてみる

・開発環境は、コメントが見やすく、かつ他人に処理を説明しやすいPython+Jupyter-notebookでやりました。ざっくりした始め方は以下です。

動作環境の準備

 1. Anacondaを導入して適当な仮想環境作って、Jupyter-Notebook環境を準備します。参考はこちらです。クラサバ形式の開発環境を用意できる場合は、外部からブラウザアクセスできるようにc.NotebookApp.ip = '0.0.0.0'を設定することが重要です。
 2. Quickstartに明記されているのですが、Python環境は3.x以上を使用します。理由は、Microsoftが提供しているFaceのサンプルコードがasyncioを使用しており、Python 3.x以上でないと動作しないからです。ちなみに、Edge側でカメラ撮影してFaceでInferenceする、という処理を想定していて、Raspberry Piでも、x86のUbuntu + EdgeGatewayでも動作できるように、カメラ操作処理でOpenCVを使いたかったので、こちらを参考にPython3.x環境で動作するように構成しました(OpenCVはPython2.xを前提にしていて、Python3.xでは、そのままでは動作しません)。とりあえず、まずはFaceを開発用PCで動作させてRaspberry PiやEdge機器に移植するか、などと考えて、カメラとFaceの処理を別々に開発するとPythonのバージョン違いで手戻りでうんざりしますので要注意です。Raspberry Pi限定でpicameraを使う場合は、最初からPython3.xを使えばよいので考慮不要です。
 3. QuickStartの"Create a new Python application"でimportしているライブラリを、pip3などでインストールします。問題が発生したら、まじめにQuick Startを読みます。

ソース入手と動作のお作法

  1. 前述のQuickstartを読んで、関連するLibraryの機能や動作を30%くらい斜め読みします。
  2. QuickStartではPerson Groupからperson_idを削除する部分は紹介されません。こちらのPackagesのFace、ClassesのFaceClientを順に開いて関心のあるメソッドを理解しておきます。付録に削除処理のサンプルを記述しました。
  3. 上記Quickstartのリンクに含まれるGithubを開くと、動作するソースコード全体を入手できます。
  4. ソースコード自体は# <snippet_imports> ~ # </snippet_imports>といった感じのコメント付きのタグで囲まれています。PersonGroupを定義→人物を登録→人物の画像を登録→PersonGroupをトレーニング→テスト画像をトレーニング済のPersonGroupに渡してInferenceの結果を受け取る→PersonGroupを削除して終了、という処理を行う場合に必要なタグを以下の表に整理しました。タグ単位でJupyter-notebookのセルにコピペして関数と出力を見て処理を理解することが目的です。記事の最後に付録としてJupyter-notebookで動作するコード全体を掲載します。
tag名 概要・メモ
<snippet_imports> 必要なライブラリのimport処理。実行してエラーが出力された場合はpip3で追加。
<snippet_subvars> 変数宣言。上記1.の処理で必要な変数はKEY、ENDPOINT、PERSON_GROUP_IDのみ。
<snippet_auth> KEY、ENDPOINTでFaceClientのインスタンスを生成。
<snippet_persongroup_create> PERSON_GROUP_IDでPerson Groupを作成し、Woman、Man、Chiledという人物名を登録。
<snippet_persongroup_assign> ローカルフォルダからwoman、man、childで始まるファイル名の画像を読み込んで、PersonGroupにwoman、man、childのIDとそれぞれの画像をセットで登録。
<snippet_persongroup_train> PersonGroupをトレーニング。
<snippet_identify_testimage> test画像(test-image-person-group.jpg)に人物の顔が含まれていることを確認
<snippet_identify> PersonGroupに該当するPerson存在する場合はidを返す。nameを取り出したい場合はPersonGroupに対して別のクエリーを実行

<snippet_xxx>の補足

  1. このソースではKEYとENDPOINTをOS環境変数から受け取るように記述されています。ソースをgithubで公開しpullした人ががそのままにLocal環境で動作させたり、DockerイメージにFaceアプリを組み込むなら、OS環境変数を使ったほうが便利だとは思いますが、今回はJupyter-notebookで動作させるだけですのでハードコーディングしています。KEYとENDPOINTはAzure Portalで確認します。また、Localコンテナーを使用する場合はENDPOINTにLocalコンテナーのIPアドレス、またはホスト名(名前解決可能な場合)を設定します。
  2. トレーニング処理では、OSのフォルダに含まれる画像とperson_idを関連付けていますが、そのままでは肝心の画像がありません。こちらからImageをダウンロードして.pyの実行ディレクトリに配置しておきます。本記事では扱ってない、その他の処理ではインターネットからhttpで処理対象となるImageを入手しているので、この作業は不要です。
  3. 今回の処理には含まれていませんが、PersonGroupにPersonを追加する場合は、<snippet_persongroup_create>、<snippet_persongroup_assign>に従ってPersonと画像を登録し、<snippet_persongroup_train>に従ってトレーニングを実行する必要があります(トレーニングしないとInference結果に反映されません)。また、PersonGroupからPersonを削除する場合は、face_client.person_group_person.deleteメソッドで該当するPersonを削除後に、<snippet_persongroup_train>に従ってトレーニングを再実行します(トレーニングしないと、削除したはずのpersonがInference結果に含まれてしまいます)。
  4. 付録のソースでは、処理内容を理解しやすくするため、person_id、name、顔を含む画像を出力するようにprint()を追記しています。
  5. オリジナルのソースでは冒頭のライブラリimportで"from PIL import Image, ImageDraw"を宣言して、<snippet_frame>というタグの部分でimg.show()で画像を表示しているのですが、Jupyter-notebook環境では使えません。どの顔画像が、どの様なInference結果になるのかを確認しないと不便ですので、代わりに、"from IPython.display import Image, display_jpeg"を使います。ImageというオブジェクトがPILと重複するのでその都度、宣言しなおしています。こんな感じで、Jupyter-notebookで画像表示したい処理の直前に"from IPython.display import Image, display_jpeg"を宣言して、処理が終了すると"from PIL import Image, ImageDraw"を再宣言します。
FaceQuickstartの抜粋.py
# <snippet_persongroup_assign>
'''
Detect faces and register to correct person
'''
# Find all jpeg images of friends in working directory
woman_images = [file for file in glob.glob('*.jpg') if file.startswith("woman")]
man_images = [file for file in glob.glob('*.jpg') if file.startswith("man")]
child_images = [file for file in glob.glob('*.jpg') if file.startswith("child")]

from IPython.display import Image, display_jpeg

print('test-image-person-group.jpg')
display_jpeg(Image('test-image-person-group.jpg')) 

# Add to a woman person
for image in woman_images:
    w = open(image, 'r+b')
    face_client.person_group_person.add_face_from_stream(PERSON_GROUP_ID, woman.person_id, w)
    # png_file: str
    print(str(image))
    display_jpeg(Image(image)) 

# Add to a man person
for image in man_images:
    m = open(image, 'r+b')
    face_client.person_group_person.add_face_from_stream(PERSON_GROUP_ID, man.person_id, m)
    # png_file: str
    print(str(image))
    display_jpeg(Image(image)) 

# Add to a child person
for image in child_images:
    ch = open(image, 'r+b')
    face_client.person_group_person.add_face_from_stream(PERSON_GROUP_ID, child.person_id, ch)
    # png_file: str
    print(str(image))
    display_jpeg(Image(image)) 

from PIL import Image, ImageDraw

# </snippet_persongroup_assign>

補足とまとめ

・FaceのPersonGroupに対するCRUD操作をJupyter-notebookで実行してみました。
・Faceは、単純なjpeg画像をソースとしてInference結果を出力しています。赤外線カメラや3Dカメラの出力をソースとしていません。多数のjpeg画像から特定の人物の顔を含むファイルを抽出する、とか、受付業務における補助的な役割に適しているのではなかろうか、と思います。
・LocalコンテナーでFaceを実行する際に、デフォルト(=何もしない状態)だと、こちらの「ストレージシナリオの設定」によるとメモリを選択したことになり、「Face コンテナーが停止または削除された場合、そのコンテナーのストレージ内のデータはすべて破棄されます。」とのことです。つまり、毎回PersonGroupを作成してトレーニングを実行する必要があります。Azureというシナリオを選択すると、データがクラウド又はLocalのMongoDBなどに保存できるようです。

その3 (付録)に続きます。