[python] LINEで画像を送るとAIが年齢や感情を分析するbotを作ってみた

# :star: 概要
- LINEから画像が送信されるとMessagingAPIを使ってwebhookで画像を受け取る
- MicrosoftのFaceAPIで画像に写っている顔の年齢や感情を分析し、その結果を返信する

# :star: 実際に作ったもの

- 下のボタン、もしくは`@qpy2732e`でID検索すると友達に追加できます

<a href="https://line.me/R/ti/p/%40qpy2732e"><img height="36" border="0" alt="友だち追加" src="https://scdn.line-apps.com/n/line_add_friends/btn/ja.png"></a>

- 画像を送るとこんな感じで分析結果を返してくれます

<img width="398" alt="スクリーンショット 2018-05-04 17.30.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/178077/d773a64c-f99c-c171-6df1-cc3de91d4c0e.png">

<img width="399" alt="スクリーンショット 2018-05-04 16.35.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/178077/16664795-141d-9836-81d8-aae8ceb9ba9e.png">


# :star: 事前に準備すること
- LINEのbotを用意する
- webhookを受け取るアプリを作る
- Herokuにデプロイする
- LINEとアプリを連携する
- Computer Vision API のAPIキーを取得する

上記については前に書いた記事 [[python] LINEの文字起こし君を作ってみた](https://qiita.com/mikan3rd/items/f0b6ff57dbcedc79a22d) と同じですので割愛します


# :pencil: Face API を使って画像の分析結果を取得する
- [Face API](https://azure.microsoft.com/ja-jp/services/cognitive-services/face/) を使うことで画像から顔を検出し、その分析結果を取得することができます
- FaceAPIへのリクエスト方法については [Computer Vision API を使って画像からテキストを取得する](https://qiita.com/mikan3rd/items/f0b6ff57dbcedc79a22d#pencil-computer-vision-api-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E7%94%BB%E5%83%8F%E3%81%8B%E3%82%89%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B) と同じです
- 複数の顔の分析結果も取得できるのですが、テキストで出力した時にどの顔の分析結果か伝えるのが難しかったたため、ここでは1人の場合のみ許容しています
- 基本的には取得した情報を文字列に整形しているだけです
- 百分率の場合はパーセントに直しています
- emotionについては数値が大きい順に並び替えています

```python

import requests

import settings

KEY1 = settings.KEY1

endpoint = 'https://eastasia.api.cognitive.microsoft.com/face/v1.0/detect'


output_list = [
    {'key': 'gender', 'label': '性別'},
    {'key': 'age', 'label': '年齢'},
    {'key': 'smile', 'label': '笑顔'},
    {'key': 'emotion', 'label': '感情'},
    {'key': 'makeup', 'label': '化粧'},
    {'key': 'accessories', 'label': '身につけているもの'},
]

emotion_label = {
    'anger': '怒り',
    'contempt': '軽蔑',
    'disgust': '嫌悪',
    'fear': '恐怖',
    'happiness': '幸せ',
    'neutral': '通常',
    'sadness': '悲しみ',
    'surprise': '驚き',
}

makeup_label = {
    'eyeMakeup': '目',
    'lipMakeup': '唇',
}

accessories_label = {
    'headwear': '帽子',
    'glasses': 'メガネ',
    'mask': 'マスク',
}


def get_face_info(image_url=None, image=None):
    if image_url is None and image is None:
        return '必要な情報が足りません'

    params = {
        'returnFaceId': True,
        'returnFaceLandmarks': False,
        'returnFaceAttributes': 'age,gender,smile,facialHair,emotion,makeup,accessories',
    }

    if image_url:
        headers = {
            'Ocp-Apim-Subscription-Key': KEY1,
            'Content-Type': 'application/json',
        }
        data = {'url': image_url}
        response = requests.post(
            endpoint,
            headers=headers,
            params=params,
            json=data
        )

    elif image is not None:
        headers = {
            'Ocp-Apim-Subscription-Key': KEY1,
            "Content-Type": "application/octet-stream"
        }
        response = requests.post(
            endpoint,
            headers=headers,
            params=params,
            data=image,
        )

    status = response.status_code
    data = response.json()

    if status != 200:
        error = ''
        if status == 429:
            error = '今月に使用できる回数を超過しました'

        else:
            error = 'エラーが発生しました'

        print(status, data)
        return error

    num = len(data)

    if num == 0:
        return '顔が検知できませんでした'

    if num > 1:
        return '%s人の顔が検出されました' % (num)

    face_info = data[0]['faceAttributes']
    text_list = []

    for option in output_list:
        key = option['key']
        info = face_info.get(key)

        if info is None:
            continue

        output = ''

        if key == 'gender':
            output = '男性' if info == 'male' else '女性'

        elif key == 'age':
            output = '%s歳' % (info)

        elif key == 'smile':
            output = str(round(info * 100, 1)) + '%'

        elif key == 'emotion':
            info_list = sorted(info.items(), key=lambda x: x[1], reverse=True)
            tmp_list = []
            for info in info_list:
                tmp = '%s %s' % (
                    emotion_label[info[0]], round(info[1] * 100, 1))
                tmp_list.append(tmp + '%')
            output = '\n'.join(tmp_list)

        elif key == 'makeup':
            tmp_list = []
            for k, v in info.items():
                tmp = '%s %s' % (makeup_label[k], 'あり' if v else 'なし')
                tmp_list.append(tmp)
            output = '\n'.join(tmp_list)

        elif key == 'accessories':
            if len(info) == 0:
                output = 'なし'

            else:
                tmp_list = [accessories_label[label['type']] for label in info]
                output = ' '.join(tmp_list)

        text_list.append('[%s]\n%s' % (option['label'], output))

    text = '\n\n'.join(text_list)
    print(text)

    return text


if __name__ == "__main__":
    get_face_info()

```

- `index.py`でこの関数を呼び出すように修正しましょう

# :star: 完成!!
- 以上でLINEで画像を送ると年齢や感情を分析するbotができました

# :star: 関連
- [[python] LINEで画像を送ると商品検索するbotを作った](https://qiita.com/mikan3rd/items/0e1bfe46e00cf434a126)