ラズパイとAzureのFaceAPIで赤ちゃんの表情を読み取ってみた


はじめに

先日、我が家に子供が生まれたので、見守るためのベビーカメラをラズパイで作ってみようと思いました。
ただ見守るだけではなく、AzureのFaceAPIを使って赤ちゃんの表情を読み取ることができれば、話すことができない赤ちゃんの気持ちが読み取れるのではないかと考えて、FaceAPIの結果から赤ちゃんの気持ちをLINE通知する装置を作ってみました。

最終的には、倒れて赤ちゃんにぶつかったら危ないしベビーカメラを置くほど家は広くなかったので片づけましたが、せっかくなので紹介だけしたいと思います。

装置の概要

全体構成

自宅で使う装置なので通信はwifiを使用しています。
定期的に写真を撮影して、FaceAPIの結果と温湿度に応じたLINE通知をしてくれます。
また、いい表情が取れたらGoogledriveに画像を蓄積するようにしています。

処理の概要

処理の流れは以下のようになっていてこれを5分周期で行っています。

  1. 写真を撮影
  2. 撮影した写真をFaceAPIで解析
  3. 「HAPPINESS(幸せ)」の写真が取れた時はGoogleDriveに写真を転送
  4. 解析結果の変化があったときにLINE通知

全てを紹介するのは大変なので、FaceAPIで解析する処理と、解析結果に応じたLINE通知する処理について紹介します。

FaceAPIで解析する処理

FaceAPIを使うにはAzureのアカウントを作って、あらかじめKEYとENDPOINTを取得して、ラズパイにパッケージをインストールしておく必要があります。
detect_with_stream()を実行すると画像の解析結果(emotion_dict)が、{'anger': 0.0, 'contempt': 0.0, 'disgust': 0.0, 'fear': 0.0, 'happiness': 1.0, 'neutral': 0.0, 'sadness': 0.0, 'surprise': 0.0}という形で取得できるので、その情報をソートして、値が最も大きい感情を戻り値で返します。

def emotion_check(filename):

    from azure.cognitiveservices.vision.face import FaceClient
    from msrest.authentication import CognitiveServicesCredentials

    emotion = EMOTION_NONE
    KEY = "XXXXXXXXXXXXXXXXXXXXX"
    ENDPOINT = "https://XXXXXXXXXXXXXXXXXXX.azure.com/"

    #Azureのアカウントに応じたKEYとENDPOINTを設定する
    face_client = FaceClient(ENDPOINT, CognitiveServicesCredentials(KEY))

    #感情(emotion)を返すようにAPIを実行
    filepath = '/var/local/photo/' + filename
    img = open(filepath, 'rb')
    params = ['age','emotion']
    detected_faces = face_client.face.detect_with_stream(img, return_face_attributes = params)

    if not detected_faces:
        print("face not detect")
    else:
        emotion_dict = detected_faces[0].as_dict()['face_attributes']['emotion']
        emotion_sort = sorted(emotion_dict.items(), key=lambda x:x[1], reverse=True)

        if emotion_sort[0][0] == "anger":
            emotion = EMOTION_ANGER
        elif emotion_sort[0][0] == "contempt":
            emotion = EMOTION_CONTEMPT
        elif emotion_sort[0][0] == "disgust":
            emotion = EMOTION_DISGUST
        elif emotion_sort[0][0] == "fear":
            emotion = EMOTION_FEAR
        elif emotion_sort[0][0] == "happiness":
            emotion = EMOTION_HAPPINESS
        elif emotion_sort[0][0] == "neutral":
            emotion = EMOTION_NEUTRUL
        elif emotion_sort[0][0] == "sadness":
            emotion = EMOTION_SADNESS
        elif emotion_sort[0][0] == "surrise":
            emotion = EMOTION_SURPRISE

    return emotion

解析結果に応じたLINE通知する処理

引数で受け取った感情と温湿度の情報に応じてメッセージを送信します。
温度は27度以下にするのがよいと、ひよこクラブに書いていたので、27度を閾値にしています。
FaceAPIで認識できる感情が「anger(怒り),contempt(軽蔑),disgust(嫌悪),fear(恐怖),happiness(幸福),neutral(平常),sadness(悲しみ),surprise(驚き)」なので、それぞれに対してそれっぽい文字列を割り当てて、LINEのメッセージにしています。
ソースの中に書かれているfile_id_get()はGoogleDrive上のファイルIDを取得するために作った独自の関数です。
また、now_env_get()は温湿度を取得するために作った独自の関数です。参考にする場合は注意してください。

def line_send(emotion):
    from linebot import LineBotApi
    from linebot.models import TextSendMessage

    #赤ちゃんラインのアクセストークン
    LINE_CHANNEL_ACCESS_TOKEN = "XXXXXXXXXXXXXXXXX"
    line_bot_api = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)

    if emotion == EMOTION_NONE:
        message = "顔を見つけてないよ。ちゃんと映るように角度を変えてね。"
    elif emotion == EMOTION_HAPPINESS:
        #対象のファイルのパスを取得
        file_id = file_id_get("beby_camera")
        message = "楽しい写真が取れたよ。見てみてね。 https://drive.google.com/file/d/" + file_id
    elif emotion == EMOTION_ANGER:
        message = "機嫌が悪いよ。なんでだかわかる?"
    elif emotion == EMOTION_CONTEMPT:
        message = "もっと遊んでほしいよ。"
    elif emotion == EMOTION_DISGUST:
        message = "嫌な気持ちだよ。なんでだかわかる?"
    elif emotion == EMOTION_FEAR:
        message = "怖い気持ちだよ。なんでだかわかる?"
    elif emotion == EMOTION_NEUTRUL:
        message = "ぼーっとしてるんだ。"
    elif emotion == EMOTION_SADNESS:
        message = "悲しい気持ちだよ。なんでだかわかる?"
    elif emotion == EMOTION_SURPRISE:
        message = "びっくりしてるよ。もっと色んな事を教えてね。"

    #温湿度もチェック
    temp,humi = now_env_get()
    if int(temp) > 27:
        message = message + "暑いから何とかしてよ~(温度" + temp + "C,湿度" + humi + "%)"
    else:
        message = message + "(温度" + temp + "C,湿度" + humi + "%)"

    #コマンドの実行結果を送信
    messages = TextSendMessage(text=message)
    line_bot_api.broadcast(messages)

※生まれたばかりの赤ちゃんは平常が多いので、自分でいろいろ試しました。

おわりに

クラウドサービスが流行っているので使ってみたいなと思っていたのですが、正直、個人で使う用途があまりないように感じていました。
AzureのFaceAPIのようなサービスであれば、よほど使わない限り無料で使えそうなので、個人でも少し遊べるかなと思いました。
固定がしっかりできれば便利なのかもしれませんが、だっこしたりおむつ交換したりしていると少し邪魔な時もあるので、しばらくは自力で表情を読みとることにしてます。