ラズパイ＋カメラ＋人感センサでお手軽「顔分析カメラ」をつくってみよう

# はじめに
この記事では、「人を感知したら自動で写真を撮り、画像から年齢や性別を解析してクラウド上で可視化する」という実践的なアプリケーションのつくりかたを解説します。
   
ショッピングモールでの客層の分析や、監視カメラなどに応用ができるはずです！

# 使用デバイス

* ラズパイ３
* ラズパイカメラ
* [人感センサ](http://akizukidenshi.com/catalog/g/gM-09627/)（M-09627：最大検知距離：７ｍ、検知角度：１２０度） 

![20170130_002425.jpg](https://qiita-image-store.s3.amazonaws.com/0/60470/9b833bc6-983e-84b3-1e9d-732c0eac8cb6.jpeg)


# 使用サービス

* [Microsoft Cognitive Services - Face API](https://www.microsoft.com/cognitive-services/en-us/face-api)
    * Face API画像から顔を検出・解析するのに使うAPIです
    * 64個の顔まで検出可能です
    * [Microsoft Cognitive ServicesのAPI](https://www.microsoft.com/cognitive-services/en-us/apis)はvision, speech, languageなどの領域のサービスを提供しています
    
<img width="1125" alt="スクリーンショット 2017-01-30 2.06.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/60470/3b8af224-6ef3-dd27-c3af-668053c4778f.png">
[公式ページ](https://www.microsoft.com/cognitive-services/en-us/face-api)より引用

* [kintone API](https://kintone.cybozu.com/jp/trial/)
    * 解析したデータを保存、可視化するのに使います
    * 登録していないかたは、30日間無料でトライアルできるので登録してください

kintoneとは↓↓
<img width="534" alt="スクリーンショット 2017-01-30 2.09.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/60470/d22dcfe2-f11b-019d-c3c9-75c7b04d3f4b.png">
[公式ページ](https://kintone.cybozu.com/jp/feature/)より引用

# 構成図
<img width="754" alt="概要図" src="https://qiita-image-store.s3.amazonaws.com/0/60470/8fb032dc-161b-90e3-2e5f-5d9d0cab0168.png">


①まず、人が来たことを人感センサで検知し、カメラで写真を撮ります
②その写真をクラウド上のMS Face APIに投げて、顔検出と年齢と性別を推定します
③受け取った年齢と性別のデータを、kintone上に投げてデータを保存します
④kintone上で可視化（グラフ化）します

# 準備
## 1. Microsoft Face APIキーの取得
まず、[ここ](https://www.microsoft.com/cognitive-services/en-US/sign-up?ReturnUrl=/cognitive-services/en-us/subscriptions)からMicrosoftアカウントまたはGithubアカウント（※）でログインしてください。

※Githubアカウントで入る場合は、[自分のGitHubページ](https://github.com/settings/profile)でメアドをpublicにする必要があります。
![image](https://qiita-image-store.s3.amazonaws.com/0/60470/6366f179-c233-7a67-7f17-39b6d21d556a.png)

ログインしたら、Face APIをサブスクリプションします。サブスクリプションするためには、事前にメールの認証が必要になります。「Send email verification」をクリックすると自分のメアドにMicrosoftからメールが来るので、認証しておきましょう。この後、Face APIを「Subscribe to new free trials」からサブスクリプションしてください。

![image](https://qiita-image-store.s3.amazonaws.com/0/60470/d6912650-82e0-62c5-0b9d-23034b65107b.png)

　 
サブスクリプションすると、Face APIキーが手に入ります。これを後で使うので、メモしておきましょう。
　![face_api_key.png](https://qiita-image-store.s3.amazonaws.com/0/60470/f8932220-e354-033d-00c5-cd7bdb1fcf87.png)

# 2. kintoneアプリの作成（アプリのドメイン、IDの設定とトークンの取得）

[kintone API](https://kintone.cybozu.com/jp/trial/)から無料登録し、ログインしてください。

次に、アプリを作成しましょう。右上の…（点三つ）をクリックして、「アプリの追加」をクリックします。

![kintone_app_create.png](https://qiita-image-store.s3.amazonaws.com/0/60470/33ba2448-0faf-470c-6ad1-394b8cb71f52.png)

「はじめから作成」を選びます。
<img width="929" alt="スクリーンショット 2017-02-03 16.01.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/60470/b556b240-e2c5-10b0-4bd7-28114dad48c9.png">

フォームには日時と数値、文字列（１行）を右部にドラッグアンドドロップし、フィールド名とフィールドコードを下記のように設定してください。
![kintone_app_fields.png](https://qiita-image-store.s3.amazonaws.com/0/60470/e6fe9f77-3c40-bd2b-c4ab-df2007696b2f.png)

![kintone_app_fields_name.png](https://qiita-image-store.s3.amazonaws.com/0/60470/1b169a76-e73a-0266-4ed8-37e1f1250dc2.png)

同じように他のフィールドも変更しましょう。

| フィールド名       |       フィールドコード |
|:-----------------:|:------------------:|
|日時              | datetime              |
|年齢              | age              |
|性別              | gender              |


設定タブをクリックし、APIトークンを選ぶと、APIトークンが表示されます。これもメモしておきます。

アクセス権限には「レコード追加」にもチェックしておきましょう。
![kintone_app_api_token.png](https://qiita-image-store.s3.amazonaws.com/0/60470/83c45fbc-210c-b7a3-f5b7-97f8e0d6ccfc.png)

ここまできたら、右の「アプリを公開」ボタンを押して、公開しましょう！！

<img width="809" alt="スクリーンショット 2017-02-03 16.03.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/60470/bec53e02-3a85-2b6b-dbba-8d8e1d38ee1f.png">

公開したアプリのURLを見ると以下のようになっています。

```
https://ドメイン名.cybozu.com/k/アプリID/
```

このうちドメイン名とアプリIDをメモしておきます。

## 3. コンフィグファイルms_api_key.yaml とkintone_conf.yamlに、上記情報を書き込む

confというフォルダを作り、その下に下記二つのファイルを新規に作って、先ほど取得した情報を入力してください。

コロンの後のスペースは一つあけておきましょう。

```ms_api_key.yaml
key: ここにFaceAPIキーを入力
```

```kintone_conf.yaml
domain: アプリのドメインを入力
id: アプリのIDを入力
token: アプリのトークンを入力
```

　 4. 回路作成
　 ![raspi-pir.png](https://qiita-image-store.s3.amazonaws.com/0/60470/d51b7854-255a-4e42-60e8-09035c01305f.png)

   上記図の人感センサと、今回使った人感センサ（M-09627）の赤（５V）と黒（GND）は配置が逆ですが、この図のとおりにつなげばOKです。

# pyyamlのインストール

```
sudo pip3 install pyyaml
```

# ソースコード

```py:face_api_kintone.py
#-*- coding: utf-8 -*-

"""
ラズパイに接続された人感センサから人を検知し、検出された場合は写真を撮影して
検出された人の年齢と性別を推定してkintoneにデータを送信するサンプルプログラムです。

使用デバイス：ラズパイ３、ラズパイカメラ、人感センサ（M-09627）
外部サービス：Microsoft Face API（写真から人検出）
　　　　　　　kintone API（推定結果の保存・ビジュアライズ）

準備：
   1. MS Face APIキーの取得
   2. kintoneアプリの作成（アプリのドメイン、IDの設定とトークンの取得）
　 3. config/配下にms_api_key.yaml とkintone_conf.yamlを新規作成し、上記情報を書き込む

"""

import urllib
import os
import sys
import subprocess
import json
from datetime import datetime
import requests
import yaml
from time import sleep
import RPi.GPIO as GPIO

PIN = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN, GPIO.IN)

BASE_DIR = os.path.dirname(__file__)

# ファイルからMS APIキーとkintone設定ファイルのの読み込み
ms_api_key = yaml.load(open(os.path.join(BASE_DIR, 'conf/ms_api_key.yaml')).read())
kintone_conf = yaml.load(open(os.path.join(BASE_DIR, 'conf/kintone_conf.yaml')).read())

# MS Face APIのヘッダ
face_api_headers = {
    'Content-Type': 'application/octet-stream',
    'Ocp-Apim-Subscription-Key': ms_api_key['key'],
}

# kintone APIのヘッダ
kintone_api_headers = {
    'Content-Type': 'application/json',
    'X-Cybozu-API-Token': kintone_conf['token'],
}

# MS Face APIのパラメータ
# 参考：https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236
face_api_params = urllib.parse.urlencode({
    'returnFaceId': 'false',
    'returnFaceLandmarks': 'false',
    'returnFaceAttributes': 'age,gender',
})

def send_face_attr_to_kintone(face_results):
    """
    検出されたすべての顔データをkintoneにPOSTする
    """

    # 顔の検出人数分だけループ
    for result in face_results:
        # 性別と年齢のみ取り出す
        gender = result['faceAttributes']['gender']
        age = result['faceAttributes']['age']

        record = {"datetime": {'value': datetime.now().strftime('%Y-%m-%dT%H:%M:%S+09:00')}, "age": {'value': age}, "gender": {'value': gender}}
        payload = {'app': kintone_conf['id'], 'record': record }

        # 1人分をkintoneへPOST
        response = requests.post('https://{}.cybozu.com/k/v1/record.json'.format(kintone_conf['domain']), data=json.dumps(payload), headers=kintone_api_headers)

        if response.ok:
            pass
        else:
            print('Error!')
            print(response.raise_for_status())

def detect_faces(filename):
    """
    Microsoft Face APIで画像から顔を検出する
    """

    face_image = open(filename, "r+b").read()

    # Miscosoft Face APIへ画像ファイルをPOST
    response = requests.post('https://westus.api.cognitive.microsoft.com/face/v1.0/detect?%s' % face_api_params, data=face_image, headers=face_api_headers)
    results = response.json()

    if response.ok:
        print('Result-->  {}'.format(results))
    else:
        print(response.raise_for_status())
    return results

def shutter_camera():
    """
    ラズパイカメラで撮影する
    """

    # cam.jpgというファイル名、640x480のサイズ、待ち時間5秒で撮影する
    cmd = "raspistill -o cam.jpg -h 640 -w 480 -t 5000"
    subprocess.call(cmd, shell=True)

try:
    while True:
        # 人間センサからデータを読みこむ（0: 検出なし, 1:検出あり）
        human_exists = int(GPIO.input(PIN) == GPIO.HIGH)

        if human_exists:
            print('Human exists!')

            print('Taking a picture in 5 seconds...')
            shutter_camera()
            print('Done.')

            # MS Face APIで顔検出
            print('Sending the image to MS face API...')
            results = detect_faces('cam.jpg')
            print('Done.')

            if len(results) > 0:
                # kintoneに送信
                print('Sending face attributes to kintone...')
                send_face_attr_to_kintone(results)
                print('Done.')
            else:
                print('No faces detected')
        else:
            print('No human')

        print('Wait 10 seconds...')
        print('------------------')
        sleep(10)

except KeyboardInterrupt:
    pass
except Exception as e:
    print(e)

GPIO.cleanup()

```

# 実行方法

```
pi@raspberrypi:~ $ python3 face_api_kintone.py
Human exists!
Taking a picture in 5 seconds...
Done.
Sending the image to MS face API...
Result-->  [{u'faceRectangle': {u'width': 185, u'top': 246, u'height': 185, u'left': 155}, u'faceAttributes': {u'gender': u'male', u'age': 27.4}}]
Done.
Sending face attributes to kintone...
Done.
Wait 10 seconds...
------------------
f^Cpi@raspberrypi:~ $
```

* プログラムを起動したら、人検出が始まるので人感センサを人にむけてください
* 人を検知したら、5秒後にラズパイカメラで撮影され、Face APIに送信されます
* そこで人の顔が写っていた場合、属性（年齢や性別）を取り出してkintone上に送信されます
* １０秒後スリープし、はじめにもどります

# kintone上で確認

* kintone上で正しく反映されているか、確認してみましょう。

![kintone_result.png](https://qiita-image-store.s3.amazonaws.com/0/60470/157ce4cf-d01b-aaeb-1847-d119d6e51dc1.png)

例えば横軸に年齢、縦軸にレコード数でグラフ化すると次のようになります。

![kintone_result_figure.png](https://qiita-image-store.s3.amazonaws.com/0/60470/7fd6f977-4f54-f8d0-3739-5846c85e042e.png)

（練習で[トランプファミリー](https://heavyeditorial.files.wordpress.com/2016/02/vanessa-haydon-trump-4-e1454992992948.jpg?quality=65&strip=all)をたくさん見せていたので、30代と70代と10代が多い結果となりました笑）

kintoneで10代、20代、、、80代というふうに幅を持たせたグラフってどうやってつくるんだろう。ラズパイ内のPythonプログラムで年齢層を判断するしかないのかな？知っている人がいたらおしえてください！！

# さいごに
* 人を感知したら写真を自動で撮り、画像から年齢や性別を解析してクラウド上で可視化するという実践的なアプリケーションを作成しました
* いろいろな場面で応用ができそうなので、ご自由に考えてみてください！