Amazon Rekognitionで顔分析LINE BOT

[前回の記事](https://qiita.com/keeey999/items/050e4d499cbaf6ce260f)に引き続き、PythonとHerokuでLINE BOTを作ったメモ書きです。
今回は[Amazon Rekognition](https://aws.amazon.com/jp/rekognition/)を使って、顔（感情）分析をしてみます。
基本的に、前回AutoML VisionのAPIを呼んでいた箇所をAmazon RekognitionのAPIに変えただけになります。

できたものがこちら。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309745/682643f1-661a-db77-0525-e38945c9fd2a.jpeg" width=50%>


# 準備
PythonのBoto3というライブラリからAWSを操作するためにAPIキーが必要になります。
IAMユーザの作成やキーの取得については以下を参考にしました。

[Amazon AWSを使用してIAMユーザーを作成する方法](https://newtechnologylifestyle.net/amazon-aws-iam/)
[Python boto3 でAWSを自在に操ろう ~入門編~](https://qiita.com/kimihiro_n/items/f3ce86472152b2676004)

# S3に画像を保存
LINEから送られてきた画像データを一度S3に格納することで、Rekognitionの呼び出しが簡単になります。
APIキーとリージョンはHerokuで環境変数に入れておきます。

- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_DEFAULT_REGION


```python:main.py
# 画像メッセージの場合
@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    message_id = event.message.id
    message_content = line_bot_api.get_message_content(message_id)

    image_bin = BytesIO(message_content.content)
    image = image_bin.getvalue()

    # S3へ画像を保存
    s3 = boto3.client('s3')
    filename = message_id + '.jpg' # メッセージIDをファイル名とする
    s3.put_object(Bucket='backetを指定', Body=image,  Key=filename)
```

画像を残しておきたくない場合は`delete_object()`で削除できます。

```python:main.py
    # S3の画像を削除
    s3.delete_object(Bucket='backetを指定', Key=filename)
```


# Rekognitionを呼び出す
S3のバケットとキー（ファイル名）を指定して、Rekognitionの`detect_faces()`を呼び出します。
このとき、`Attributes`を`ALL`にすることで感情データ（Emotions）も取得することができます。
[SDKリファレンス](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/rekognition.html)

```python:main.py
    # Rekognition呼び出し
    rekognition = boto3.client('rekognition')
    response = rekognition.detect_faces(
        Image={
            'S3Object': {
                'Bucket': 'backetを指定',
                'Name': filename
            }
        },
        Attributes=[
            'ALL'
        ]
    )

    # 結果から感情データを取得
    message = ''
    cnt = 0
    for face_detail in response['FaceDetails']:
        cnt += 1
        message += str(cnt) + '人目\n'
        for emotion in face_detail['Emotions']:
            message += translation(emotion['Type']) + ':' + \
                str(round(emotion['Confidence'], 6)) + '\n'


def translation(type):
    if type == 'HAPPY':
        return '幸せ'
    elif type == 'SAD':
        return '悲しみ'
    elif type == 'ANGRY':
        return '怒り'
    elif type == 'SURPRISED':
        return '驚き'
    elif type == 'DISGUSTED':
        return '嫌悪'
    elif type == 'CALM':
        return '穏やか'
    elif type == 'CONFUSED':
        return '困惑'
    elif type == 'FEAR':
        return '恐れ'
```

レスポンスはこんな感じでいろいろなパラメータが返ってきます。
（1枚目の女性の写真の場合です）

```
{'FaceDetails': [{
    'BoundingBox': {
        'Width': 0.13417434692382812,
        'Height': 0.244236558675766,
        'Left': 0.4932348132133484,
        'Top': 0.16279612481594086,
        },
    'AgeRange': {'Low': 20, 'High': 32},
    'Smile': {'Value': True, 'Confidence': 99.76376342773438},
    'Eyeglasses': {'Value': False, 'Confidence': 99.79804992675781},
    'Sunglasses': {'Value': False, 'Confidence': 99.94234466552734},
    'Gender': {'Value': 'Female', 'Confidence': 99.739990234375},
    'Beard': {'Value': False, 'Confidence': 99.172607421875},
    'Mustache': {'Value': False, 'Confidence': 99.76605987548828},
    'EyesOpen': {'Value': True, 'Confidence': 79.16128540039062},
    'MouthOpen': {'Value': True, 'Confidence': 99.7750015258789},
    'Emotions': [
        {'Type': 'SURPRISED', 'Confidence': 0.05093928799033165},
        {'Type': 'HAPPY', 'Confidence': 99.83908081054688},
        {'Type': 'FEAR', 'Confidence': 0.02197244018316269},
        {'Type': 'CONFUSED', 'Confidence': 0.01973951980471611},
        {'Type': 'DISGUSTED', 'Confidence': 0.013059847988188267},
        {'Type': 'CALM', 'Confidence': 0.006850585341453552},
        {'Type': 'SAD', 'Confidence': 0.013594787567853928},
        {'Type': 'ANGRY', 'Confidence': 0.03477080538868904},
        ],
    'Landmarks': [
        {'Type': 'eyeLeft', 'X': 0.5280879139900208,
         'Y': 0.22927305102348328},
        {'Type': 'eyeRight', 'X': 0.5915847420692444,
         'Y': 0.24782206118106842},
        {'Type': 'mouthLeft', 'X': 0.5162054300308228,
         'Y': 0.3102501630783081},
        {'Type': 'mouthRight', 'X': 0.5685757398605347,
         'Y': 0.32595935463905334},
        {'Type': 'nose', 'X': 0.543215811252594,
         'Y': 0.2711125910282135},
        {'Type': 'leftEyeBrowLeft', 'X': 0.5093030333518982,
         'Y': 0.20783887803554535},
        {'Type': 'leftEyeBrowRight', 'X': 0.5432007908821106,
         'Y': 0.20587614178657532},
        {'Type': 'leftEyeBrowUp', 'X': 0.5266761779785156,
         'Y': 0.19909897446632385},
        {'Type': 'rightEyeBrowLeft', 'X': 0.5808103680610657,
         'Y': 0.21660329401493073},
        {'Type': 'rightEyeBrowRight', 'X': 0.6230998039245605,
         'Y': 0.24032928049564362},
        {'Type': 'rightEyeBrowUp', 'X': 0.6021548509597778,
         'Y': 0.21990607678890228},
        {'Type': 'leftEyeLeft', 'X': 0.5178995132446289,
         'Y': 0.22759923338890076},
        {'Type': 'leftEyeRight', 'X': 0.5400444865226746,
         'Y': 0.23417668044567108},
        {'Type': 'leftEyeUp', 'X': 0.5282144546508789,
         'Y': 0.22547268867492676},
        {'Type': 'leftEyeDown', 'X': 0.5273098349571228,
         'Y': 0.23307734727859497},
        {'Type': 'rightEyeLeft', 'X': 0.5778760313987732,
         'Y': 0.24511677026748657},
        {'Type': 'rightEyeRight', 'X': 0.6024900674819946,
         'Y': 0.25224098563194275},
        {'Type': 'rightEyeUp', 'X': 0.5909795761108398,
         'Y': 0.2437404841184616},
        {'Type': 'rightEyeDown', 'X': 0.5891347527503967,
         'Y': 0.2511424422264099},
        {'Type': 'noseLeft', 'X': 0.5332950949668884,
         'Y': 0.28240063786506653},
        {'Type': 'noseRight', 'X': 0.5571921467781067,
         'Y': 0.28958040475845337},
        {'Type': 'mouthUp', 'X': 0.540307343006134,
         'Y': 0.3037816286087036},
        {'Type': 'mouthDown', 'X': 0.5361402630805969,
         'Y': 0.3289361000061035},
        {'Type': 'leftPupil', 'X': 0.5280879139900208,
         'Y': 0.22927305102348328},
        {'Type': 'rightPupil', 'X': 0.5915847420692444,
         'Y': 0.24782206118106842},
        {'Type': 'upperJawlineLeft', 'X': 0.500860333442688,
         'Y': 0.2379489243030548},
        {'Type': 'midJawlineLeft', 'X': 0.4942063093185425,
         'Y': 0.32398536801338196},
        {'Type': 'chinBottom', 'X': 0.5307005047798157,
         'Y': 0.374202162027359},
        {'Type': 'midJawlineRight', 'X': 0.6082754135131836,
         'Y': 0.3568985164165497},
        {'Type': 'upperJawlineRight', 'X': 0.6429087519645691,
         'Y': 0.2788805365562439},
        ],
    'Pose': {'Roll': 10.41435718536377, 'Yaw': -26.832902908325195,
             'Pitch': 2.036668062210083},
    'Quality': {'Brightness': 68.89059448242188,
                'Sharpness': 94.08262634277344},
    'Confidence': 99.99998474121094,
    }], 'ResponseMetadata': {
    'RequestId': '8dde5ca4-1af5-4063-8ac4-bb2156d3e85c',
    'HTTPStatusCode': 200,
    'HTTPHeaders': {
        'content-type': 'application/x-amz-json-1.1',
        'date': 'Tue, 10 Sep 2019 21:14:47 GMT',
        'x-amzn-requestid': '8dde5ca4-1af5-4063-8ac4-bb2156d3e85c',
        'content-length': '3354',
        'connection': 'keep-alive',
        },
    'RetryAttempts': 0,
    }}
```