#8 PowerApps アプリ で撮影した画像を FaceAPI で感情分析してみました（累積分析）


## 概要
PowerApps のカメラで撮影した画像を API Management 経由で FaceAPI に転送し、その画像の分析結果「感情、性別、年齢」を PoweApps に表示するアプリの実装手順を数回に分け記載しています。また、並行して、その分析結果を CosmosDB に保存しておき、PowerApps からの累積情報取得リクエストにより累積クエリ結果「感情分布、性別分布」を PowerApps に返し、円グラフ表示する機能の実装手順も複数回に分け記載します。なお、PowerApp の画面作成については省略し、APIコール部分とその戻り値の部分に焦点をあてて記載しています。
本アプリの全体構成は下図となります。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/a8aa9ec9-c652-4a7f-6465-15a77105b29b.png)

今回（第８回目）の構成箇所は下図の部分となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/f5ecd34d-1ac0-48c6-56ce-7d19a9e4522d.png)


第８回目は、[第７回](https://qiita.com/turupon/items/a7c5459028b142ee6839) で作成した CosmosDBへのデータ登録ローカルプログラムを [第２回](https://qiita.com/turupon/items/6f771a14aa011052ac23) で作成した Azure Functions に追加します。

---

## 実行環境
macOS Big Sur 11.3
Python 3.8.3

---

## Azure Portal 上での CosmosDB のキーとエンドポイント情報（クレデンシャル情報）の定義
前回取得した CosmosDB のキーとエンドポイント情報（「COSDB_KEY」「COSDB_ENDPOINT」）を 第２回で作成した Azure Functions「iturufuncface」の「設定」 ー 「構成」 ー 「＋新しいアプリケーション設定」 で追加定義しておきます。あわせて、CosmosDB Container情報（「COSDB_DATABASE」「COSDB_CONTAINER」）も追加定義しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/fb78d821-855f-78c4-8a79-598646131841.png)


---

## Functions で実行されるコード
第２回で作成したコードプログラムを以下のように変更します（CosmosDBへのデータ登録部分の追加）。

```__init__.py
import logging
import azure.functions as func
import cognitive_face as CF
import numpy as np
import os
import json
import base64
import requests
from datetime import datetime
from azure.cosmos import exceptions, CosmosClient, PartitionKey

# FaceAPIの情報
KEY = os.environ['FACE_KEY']
ENDPOINT = os.environ['FACE_ENDPOINT']
ENDPOINTDETECT = os.environ['FACE_ENDPOINTDETECT']

# emotion定義
emobase = ['anger', 'contempt', 'disgust', 'fear', 'happiness', 'neutral', 'sadness', 'surprise']
emolist = ['怒', '侮', '嫌', '恐', '幸', '無', '悲', '驚']

# Azure CosmosDB Info
COSDB_KEY = os.environ['COSDB_KEY']
COSDB_ENDPOINT = os.environ['COSDB_ENDPOINT']
DatabaseName = os.environ['COSDB_DATABASE']
ContainerName = os.environ['COSDB_CONTAINER']


# 感情分析(Base64-Binary)
def face_emotion_base64(data64, name) :
    logging.info("face_emotion_base64 by iTuru")
    # data = base64.b64decode(data64.replace("data:image/png;base64,", ""))
    data = base64.b64decode(data64[22:])

    try:
        headers = {
            'Content-Type': 'application/octet-stream',
            'Ocp-Apim-Subscription-Key': KEY,
        }

        params = {
            'returnFaceId': 'false',
            'returnFaceLandmarks': 'false',
            'faceRectangle': 'false',
            'returnFaceAttributes': 'age,gender,emotion',
        }

        # FACE_APIでの感情分析取得
        response = requests.post(ENDPOINTDETECT, params=params, headers=headers, data=data)
        j=json.loads(response.text)
    
        # 分析する対象の画像に複数人いようが最初の１人のみを分析の対象とする
        # 年齢情報
        age_data = j[0]['faceAttributes']['age']
        logging.info(age_data)

        # 感情情報
        emotion_data = j[0]['faceAttributes']['emotion']
        logging.info(emotion_data)

        # 性別情報
        gender_data = j[0]['faceAttributes']['gender']
        logging.info(gender_data)

        # 感情分析結果からスコアのみを分別
        emotion = []
        for name in emobase:
            emotion.append(emotion_data[name])
        
        # スコアの高いものをその人の感情として決定する 
        # logging.info(emotion)
        num = np.argmax(emotion)
        emotion_weight = str(emotion[num]*100) + "%,  " + gender_data + ",  " + str(age_data)
        emotion_dict = {'emotion': emolist[num], 'data': emotion_weight}    # 辞書データの作成
        emotion_json = json.dumps(emotion_dict, ensure_ascii=False)         # Jsonエンコード

        # CosmosDBへの取得した分析情報の追加
        cosmosdb_create_item(age_data, gender_data, emolist[num], emotion[num])

        return emotion_json

    except Exception as e:
        logging.info(e)
        emotion_dict = {'emotion': "ERROR !!!", 'data': "--.-%"}            # 辞書データの作成
        emotion_json = json.dumps(emotion_dict, ensure_ascii=False)         # Jsonエンコード
        return emotion_json


# CosmosDBへのアイテム追加
def cosmosdb_create_item(age_data, gender_data, emotion_emo, emotion_data) :

    # CosmosDBへの接続
    client = CosmosClient(COSDB_ENDPOINT, COSDB_KEY)
    db = client.get_database_client(DatabaseName)
    container = db.get_container_client(ContainerName)

    # 現時刻取得しIDとして使用するために文字列に変換
    now_ts_str = str(datetime.now().timestamp())

    # 新規登録項目の作成
    newItem = {
        "id": now_ts_str,
        "age": age_data,
        "gender": gender_data,
        "emotion": emotion_emo,
        "data": emotion_data
    }

    # アイテムの追加
    item = container.create_item(newItem)
    logging.info(' --- create_item --- ')
    logging.info(item)


# main関数
def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    name = req.params.get('name')
    req_body = req.get_body()
    res_emotion = face_emotion_base64(req_body, name)

    return func.HttpResponse(
            res_emotion,
            mimetype = "application/json",
            status_code = 201
    )
```

---

## ローカルでコードを実行してみます
コードを実行する前に、ローカルで環境情報を取得します。以下のコマンドを実行します。

```
$ func azure functionapp fetch-app-settings iturufuncface
App Settings:
Loading APPINSIGHTS_INSTRUMENTATIONKEY = *****
Loading APPLICATIONINSIGHTS_CONNECTION_STRING = *****
Loading AzureWebJobsStorage = *****
Loading COSDB_CONTAINER = *****
Loading COSDB_DATABASE = *****
Loading COSDB_ENDPOINT = *****
Loading COSDB_KEY = *****
Loading FACE_ENDPOINTDETECT = *****
Loading FACE_KEY = *****
Loading FUNCTIONS_EXTENSION_VERSION = *****
Loading FUNCTIONS_WORKER_RUNTIME = *****
```

その後、コードを実行します。

```
$ func host start                                   
Found Python version 3.8.3 (python3).

Azure Functions Core Tools
Core Tools Version:       3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0  (64-bit)
Function Runtime Version: 3.0.15417.0

Functions:
	HttpTrigger: [GET,POST] http://localhost:7071/api/HttpTrigger

For detailed output, run func with --verbose flag.
[2021-05-16T05:50:27.421Z] Worker process started and initialized.
[2021-05-16T05:50:32.490Z] Host lock lease acquired by instance ID '0000000000000000000000002CF30641'.
```

---

## Functions へのアクセスプログラムの実行
結果確認のためのFunctionsへのアクセスプログラムは以下となります（第２回のものと同じ）。

```PostImage.py
import requests
import base64

url = "http://localhost:7071/api/HttpTrigger"
headers = {
    'Content-Type': 'application/octet-stream',
}

with open("001.png", "rb") as f:
    data = "data:image/png;base64," + base64.b64encode(f.read()).decode("UTF-8")

try:
    response = requests.post(url, headers=headers, data=data)
    print(response.text)
except Exception as e:
    print(e)
```

実行結果は以下となります（画像はローカルにあるものを使用）。

```
$ python PostImage.py
{"emotion": "幸", "data": "98.2%,  female,  18.0"}
```

ローカルFunctions側の実行結果は以下となります。

```
[2021-05-16T05:52:45.269Z]  --- create_item --- 
[2021-05-16T05:52:45.269Z] {'id': '1621403564.974882', 'age': 18.0, 'gender': 'female', 'emotion': '幸', 'data': 0.982, '_rid': '-MxGAMsRyx4vAAAAAAAAAA==', '_self': 'dbs/-MxGAA==/colls/-MxGAMsRyx4=/docs/-MxGAMsRyx4vAAAAAAAAAA==/', '_etag': '"e801233a-0000-2400-0000-60a4a7ad0000"', '_attachments': 'attachments/', '_ts': 1621403565}
[2021-05-19T05:52:45.527Z] Executed 'Functions.HttpTrigger' (Succeeded, Id=29c35e25-9865-4978-8bcc-4831711fe9cd, Duration=1865ms)
```

問題なくデータ登録できました。これで Functions からFaceAPI をコールし、その分析結果を CosmosDB に登録できることを確認できました。

---

## Azureへのデプロイ
ローカルで動作確認したコードをAzureにデプロイします。

```
$ func azure functionapp publish iturufuncface           
Getting site publishing info...
Creating archive for current directory...
Performing remote build for functions project.
	:
	中略
	:
Deployment successful.
Remote build succeeded!
Syncing triggers...
Functions in iturufuncface:
    HttpTrigger - [httpTrigger]
    	:
```

正常にデプロイできました。

---

## 次回について
次回[（第９回）](https://qiita.com/turupon/items/171de03b5734fe147b9e)は、[第７回](https://qiita.com/turupon/items/a7c5459028b142ee6839) で作成した CosmosDBからのデータ取得ローカルプログラムを、新たに作成する Azure Functions に実装させ、感情毎の件数と性別毎の件数を CosmosDB からArray型のJSON形式で取得できることを確認します。

---

## 参考情報
以下の情報を参考にさせていただきました。感謝申し上げます。
[Azure Cosmos DB - 基本操作（Python）](https://qiita.com/takmot/items/f8cfe499b9b98bec4031)
[PythonからAzure Comos DBのデータ操作](https://qiita.com/tamo_breaker/items/38eae9e5a24fade35a36)
[Azure Functions App × Python で Azure CosmosDB にアクセスしてみる](https://qiita.com/annie/items/2245dd548b9d8c427f77)
[Azure cosmosDB のアラート設定について](https://qiita.com/kewpie134134/items/2fd28f9e31d1888fdc96)