Python で Microsoft Azure の FaceAPI とPCカメラをリアルタイム接続

# 福岡で開催された学生ハッカソンにて発表した作品です。

## 概要

- PCのカメラに映った映像をコマ送りでFaceAPIに送信  
- 判別結果とともにカメラの映像を出力するものとなっています。  
- 判別は性別、年齢、感情分析の結果を反映させています。  
- リアルタイムといいつつ、while文でその時その時の映像を  
写真として切り取っているため、コマ送りの出力結果となりました。
- 作品と言っておいて、僭越ですがこちらをかなり参考にし、
ミックスさせたものとなっています。

  - DATUM STUDIO さんの[Azure Computer Vision APIとFace APIをPythonから利用する][DATUM STUDIO]
  - qiita内の記事[Pythonで顔認識を爆速で自分のPCで実験してみる][qiita openCV]  



## ソースコード
```python:FaceApi.ipynb
write_file_name = './tmp.jpg'

import http.client, urllib.request, urllib.parse, urllib.error, base64
import json
import matplotlib.pyplot as plt

import cv2
cap = cv2.VideoCapture(0) # 内臓のカメラを取得

#### Request headers
#'Content-Type': APIに送るメディアのタイプ. 
#  'application/json'(URL指定の場合), 
#  'application/octet-stream' (Local ファイル転送の場合)
#'Ocp-Apim-Subscription-Key': APIキーを指定する
headers = {
#    'Content-Type': 'application/json',
    'Content-Type': 'application/octet-stream',
    'Ocp-Apim-Subscription-Key': '[APIキー]',
#APIキー 無料体験版のため、1分間あたりに20回のトランザクション制限
#当時、１週間の無料体験版でキーを２つ取得できた
}
 
#### Request parameters
# 取得したい情報について、パラメータを指定する
# 'returnFaceId': 入力した顔画像に付与されるIDを返すかどうか
# 'returnFaceLandmarks' : 目や口などの特徴となる部分の座標を返すかどうか
# 'returnFaceAttributes' :　認識した顔からわかる属性を返す
#   指定できるパラメータは以下で、コンマで分けて複数指定可能
#       age, gender, headPose, smile, facialHair, 
#       glasses, emotion, hair, makeup, occlusion, 
#       accessories, blur, exposure and noise
#   今回はage,gender,emotion
params = urllib.parse.urlencode({
    'returnFaceId': 'false',
    'returnFaceLandmarks': 'false',
    'returnFaceAttributes': 'age,gender,emotion'
})
 
#### Request body
# 入力したい画像の指定をする. 画像URLの指定, local ファイルの指定から選択
# 画像はJPEG, PNG, GIF, BMPに対応
# サイズの上限は4MB
# 認識可能な顔のサイズは 36x36 - 4096x4096 pixelsの範囲
# 最大64個の顔を認識可能
 
## URL 指定の場合以下のコメントアウトを外すし、image_urlを指定する
#image_url = 'https://XXXXX'
#body = { 'url': image_url }
#body = json.dumps(body)
 
## Local file指定の場合
# 以下の image_file_path に読み込むファイルのパスを指定する
# image_file_path = '0006601775396431-web.jpg'
# image_file = open(image_file_path,'rb')
# body = image_file.read()
# image_file.close()
 
#### API request
# 接続先リージョンによっては, 以下HTTPSConnection の "westus.api.cognitive.microsoft.com" 部分は変更する.
# この場合は「westus」なので北米西部リージョン
# なお "/face/v1.0/detect?%s" の部分が接続先APIの機能を指定している

while True:
    key = cv2.waitKey(1)
    # カメラはESCキーで終了できるように。
    if key == 27:
        print("stop!")
        break

    ret, frame = cap.read()
    cv2.imwrite(write_file_name, frame)
    with open(write_file_name, 'rb') as f:
        body = f.read()

# 僕の場合は'westcentralus'でのAPI取得でしたが、ここには自分がキーを取得したリージョンを入れてください
# DATUM STUDIO さんは「westus」なので北米西部リージョンとなっておりました。
    conn = http.client.HTTPSConnection('westcentralus.api.cognitive.microsoft.com')
    conn.request("POST", "/face/v1.0/detect?%s" % params, body, headers)
    response = conn.getresponse()
    data = json.loads(response.read())    #list型

    if len(data) > 0:
        for face in data:
            attr = face["faceAttributes"]
            rect = face['faceRectangle']
            left = rect['left']
            top = rect['top']
            right = left + rect['height']
            bottom = top + rect['width']
            text = attr['gender']+'/'+str(attr['age'])
            emots = attr['emotion']
            
            cv2.putText(
                    frame,
                    text,
                    (left, top - 10), # 位置を少し調整
                    cv2.FONT_HERSHEY_PLAIN,
                    2,
                    (0, 255,0),
                    2,
                    cv2.LINE_AA
                )
            
            i = 0
            for emot in emots:
                cv2.putText(
                        frame,
                        emot + ' : ' + str(emots[emot]),
                        (right + 10, top + i*40), # 位置を少し調整
                        cv2.FONT_HERSHEY_PLAIN,
                        2,
                        (225, 0, 225),
                        2,
                        cv2.LINE_AA
                    )
                i += 1
            
            if attr["gender"] == "male":
                cv2.rectangle(frame, (left, top), (right, bottom), (255, 0, 0), 2)    # male -> blue
            elif attr["gender"] == "female":
                cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 225), 2)    # female -> red

    conn.close()
    
    %matplotlib inline
    img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    plt.imshow(img)
    
    blur = cv2.GaussianBlur(frame, (0, 0), 1)
    cv2.imshow('camera capture', blur)
       
# 一旦画像削除の命令
cap.release()
# カメラが立ち上がっているので、全てのウィンドウを閉じる
cv2.destroyAllWindows()
        
```


## 結果　
### 年齢と性別の表示
![pictures.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414664/7130aded-7608-0b89-9c62-ca2d9dfebcb2.gif "乃木坂、時効警察、バナナマンの年齢と性別")

### 感情分析
![emopic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414664/c0b1f521-ed91-0d4b-2b7a-47deda2c2c9d.png "時効警察の二人の感情分析")



## 自分の寄与
自分がやったことは、[DATUM STUDIO][] さんの
`持っている画像ファイル`をFaceAPIに投げるという動作を、
[qiitaの記事][qiita openCV]を参考に`PCカメラの映像を随時`FaceAPIに投げるへ変更です。


## 感想

- まずは出来上がってよかった
- ただ、**もっとスラスラ**動くようにしたい
- 乃木坂の少女たちとバナナマンのおじさんたちの年齢はかなり精度の高い推定
- 時効警察はいま放送中のものから拝借したのだが、二人とも若すぎる推定結果
- 霧山修一朗は純度１００％の無表情

今回のハッカソで初めてPythonでのHello! Worldの先まで来ましたが、
これを機にPythonの勉強も始めようと思いました！



[DATUM STUDIO]:https://datumstudio.jp/blog/azure-computer-vision-api%E3%81%A8face-api%E3%82%92python%E3%81%8B%E3%82%89%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B
[qiita openCV]:https://qiita.com/TakayukiNJ/items/c6fea71a3dfc2a8a0b80