pythonとawsRekognitionを使って一瞬で顔認識システムを構築する！

#はじめに
この記事は、あくあたん工房 GWアドベントカレンダー２日目の記事です。
https://gw-advent.9wick.com/calendars/36

記念すべき人生初のアドベントカレンダーで、何を書こうか迷ったのですが、自分がどっぷりっと使って（浸かって）いるJavaScriptのことを書くのもなんだかな〜と思ったので、少し前にとあるデモ発表のために構築したAWSの顔認証APIの使い方を書いていこうと思います。
ただ、時間と知識がなかったため、変態的かつ非効率な手法を使用している箇所が多くありますが、どうかご了承ください。
#作るもの
AWSのRekognitionAPIとpythonを利用して、予め登録した顔が監視カメラ（今回はパソコンのwebカメラ）に写ったらリアルタイムでスマホに通知を送るサービスを作ります。
#実行環境
 - python(openCV)
    - カメラの映像を取得してS3にアップロードするのに使う。
 - スターサーバー
    -システムへの顔写真の登録、スマホへの通知部分
 - lambda
    - 顔認証APIを叩く処理全般
 - AWSRekognition
    - 今回の話題の中心のAWS提供の画像、映像分析用の機械学習API
  
# そもそもAWS Rekognitionとはなんぞや？
AWS RekognitonというのはAWSが提供してくれている画像分析や映像分析に特化した機械学習モデルです。天下のアマゾン様がすでに学習モデルを構築してくれていますので、学習データを用意してワチャワチャするといっためんどくさい作業は一切いりません。
AWSを使用すると物体の検出、シーン検出、
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/2c514096-544a-c90a-ce2d-30acbb48ef98.png)

顔認識、
![2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/0f7afd28-9c5e-8003-80d6-04bb30a3991a.jpeg)

顔分析（表情など）、
![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/43a2f407-9c5f-a3ab-4423-c86c6b48907f.jpeg)

動画内の物体の動きの追跡、
![4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/be95b160-d2ee-c098-2a1f-f1f6ff8dc6e4.jpeg)

安全でないコンテンツの検出、
![5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/2b5993af-3e32-8d0e-8f2c-6c285538c61c.jpeg)

有名人の認識、
![6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/0213e89e-12e8-9f3c-2b20-8332d4a13fab.jpeg)

画像内のテキスト検出、
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/e2722396-7a20-a04a-3b85-951c697a6ca7.png)


予め登録した顔写真と映像内の顔の照合（今回はこれを使用します）
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/a3e61ca7-10d3-227b-6115-170f932888c7.png)

などがちょこっとAPIを叩くだけで実現できます。

#準備
##まずはS3の用意
顔写真を保存する用と解析する動画を保存する用のS3バケットを作成します。
今回はexample.com.imgとexample.com.movという名前で準備します。
![Screenshot from 2019-04-27 04-48-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/7db9b47d-62ff-3a22-f57b-d80b9f2d832c.png)

##次はlambdaの用意
lambdaはpythonを使って組みます。
アップロードされた映像を処理するimg_func関数にはS3のexample.com.movバケットのファイルアップロードをトリガーとして設定します。
顔写真をシステムに登録するmov_func関数にはAPIGateWayをトリガーとして設定します。
![Screenshot from 2019-04-27 05-12-45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172855/062e8935-5cf9-0d07-45a8-29d6d04647e0.png)

##Rekognitionの照合用顔コレクションの用意
Rekognitonの顔認証で、映像と照合するための顔情報を保存するコレクションを作成します。
ここではfaceCollectionという名前のコレクションを作成します。
この作業はCLIからしかできないので注意が必要です。
#顔写真登録機能の実装
まずは顔写真を登録する部分の実装。
フロントはhtmlで作成します。

```html:add.html
<form method=”POST” action=”example.com”>
<input type=”file” name=”img”>
<input type=”submit” value=”送信”>
</form>
```
上のフォームをスターサーバーで用意したPHPプログラムに送信し、そこでセッションの生成、ユーザーごとのidの作成、画像のbase64エンコードの処理を行い、ユーザーのセッション情報とともに画像をlambdaのimg_func関数のGateWayAPIを叩きます。

img_funcではアップロードされた画像をアップロードしたユーザーのセッション情報と結びつけたファイル名でexample.com.imgに保存し、rekognitionの顔の解析APIを呼び出して顔認証用のコレクションに追加しています。

```python:img_func.py
import json
import boto3
import base64
import urllib.parse

def lambda_handler(event, context):
    s3=boto3.resource('s3')
    bucket=s3.Bucket('example.com.img')
    data_base=event['body']
    data_base1=data_base.split("&",1)[1]
    data_base2=data_base1.split(",",1)[1]
    data_base3=data_base2.replace('"','')
    imageBody=base64.b64decode(data_base3)
    path=event['body'].split("&",1)[0].replace('"','')
    bucket.put_object(
            Body=imageBody,
            Key=path
        )
        
    postdata=json.dumps(event["body"]).split("&",1)
    collectionId='faceCollection'
    client=boto3.client('rekognition')
    response=client.index_faces
    response=client.index_faces(CollectionId=collectionId,Image={'S3Object':{'Bucket':"exsample.com.img",'Name':path}},ExternalImageId=postdata[0].replace('"',''),DetectionAttributes=["DEFAULT"])
    return {
        'statusCode': 200,
        'body': json.dumps(response)
    }

```
これで顔写真登録機能の実装は完了です。
#カメラ映像の分析についてのあれこれ
今回は映像の撮影には自分のPCのカメラを使用することにしました。
AWSRekognitionではストリーミングで映像を送信しての処理にも対応しているのですが、動画を普通にアップロードして処理したときよりもコスト（実装コスト、金銭的なコスト）がかかってしまうので、10秒程度で切り分けた動画ファイルを随時アップロードして解析するようにしました。
また、PC側のカメラ映像の処理にはopenCVを使うと簡単だったので、pythonを使用しました。
#カメラ側の実装
まずはパソコン側でカメラの映像を取得し、動画ファイルに直してからboto3を使ってS3のexample.com.movにアップロードしています。

```python:camera.py
﻿# OpenCV のインポート
#s3ストレージにアップロードする
#pyコマンドを先頭につけてanacondaから実行する必要アリ
import cv2
import os
import time
import math
import boto3
import json
from numpy.random import *
from threading import Thread
#AWS関連処理
bucket_name="example.com.mov"
bucket_serv="hogehoge"
s3=boto3.resource('s3')
post=[]
cond=True
flag=False

#処理抜けタイミング計測function
def f():
    global flag
    while flag ==False:
        global cond
        time.sleep(10)
        cond=False
#POST時に実行するやつ
def postf():
    global flag
    global post
    global s3
    global bucket_serv
    global bucket_name
    while flag == False:
        if len(post) > 0:
            #この場合はアップロード前のファイルあり
            path=str(post.pop(0))+".mp4"
            s3.Bucket(bucket_name).upload_file(path,path)
            s3.Bucket(bucket_serv).upload_file(path,path,ExtraArgs={'ACL':'public-read'})
#カメラ初期処理
cap = cv2.VideoCapture(0)
fourcc = cv2.VideoWriter_fourcc(*"H264")
#マルチスレッド開始
thread = Thread(target=f)
thread.start()
thread2 = Thread(target=postf)
thread2.start()
while True:
    name=str(math.floor(time.time()))
    out=cv2.VideoWriter(name+".mp4",fourcc,20.0,(640,480))
    print(name);
    while cond:
        ret, frame = cap.read()
        cv2.imshow("camera input",frame)
        out.write(frame)
        k=cv2.waitKey(1)

        if k==27:
            flag=True
            break
    cond=True
    out.release()
    post.append(name)
    if flag==True:
        cap.release()
        cv2.destroyAllWindows()
        break
#in lambda from matect

```
見てわかるとおり、実行するとどんどんとパソコン内にいらない映像ファイルができていきます......
そのせいで、２時間程度のデモ発表の最中にパソコンが落ちるという悲劇がおきました......
#アップロードされた映像の分析

カメラからS3にアップロードされた映像はlambdaのfunc_mov関数でアップロードイベントを捕捉し、AWSRekognitonで解析にかけ,
コレクションに一致する人物が見つかったらその人物の情報とともにPHPサーバーに処理リクエストを送ります。
本来であればRekognitonからの解析完了の通知をAWS SQSというサービスで取得して、lambdaを起動しその解析の実行結果を取得するのですが、そもそもAWSを使うのが初めてで時間もなかったので、func_mov内のループ処理で、解析にかけたRekognitonの結果を呼び出し続け、エラーが帰ってくる限り延々と回し続けるというリソースの無駄遣いをして強行突破しました。

```python:func_mov.py
import json
import boto3
import sys
import os
import uuid
import urllib.request
import time
def lambda_handler(event, context):
    rek=boto3.client('rekognition')
    base_data=event["Records"][0]["s3"]["object"]
    base_name=base_data["key"].replace('"',"")
    rek_res = rek.start_face_search(Video={'S3Object':{'Bucket':"example.com.mov",'Name':base_name}},CollectionId='faceCollection'})


    index=list(range(0))
    dicts={}
    dictb={}
    dictp={}
    rek=boto3.client('rekognition')
    base_file=base_name
    base_data=rek_res["JobId"]
    time.sleep(20)
    base_name=json.dumps(base_data).replace('"',"")
    ress="IN_PROGRESS"    
    while "IN_PROGRESS" in ress:
        time.sleep(5)
        res=rek.get_face_search(JobId=base_name)
        ress=json.dumps(res)
        print(ress)
    data=json.dumps(res)
    data_sp=data.split('Timestamp')
    #personごとのあれ
    data_sp.pop(0)
    while len(data_sp)>0:
        data_arr=data_sp.pop(0)
        second=data_arr.split(":")[1].split(",")[0].replace(" ","")
        data_sub=data_arr.split('"Similarity":')
        data_sub.pop(0)
        while len(data_sub)>0:
            data_con=data_sub.pop(0)
            sec=second
            per=data_con.split(",")[0].replace(" ","")
            fid=data_con.split('"ExternalImageId":')[1].split(",")[0].replace('"','').replace(" ","")
            box=data_con.split('"BoundingBox": {')[1].split("}")[0]
            if fid in dictp:
                if float(dictp[fid])>float(per):
                    dicts[fid]=sec
                    dictb[fid]=box
                    dictp[fid]=per
            else:
                index.append(fid)
                dicts[fid]=sec
                dictb[fid]=box
                dictp[fid]=per
    txt="!"
    while len(index)>0:
        fid=index.pop(0)
        sec=dicts[fid]
        per=dictp[fid] 
        box=dictb[fid]
        txt=txt+"*"+fid+"#"+sec+"#"+per+"#"+box+"#"+base_file
        
        #ここで生成されたdataarrが処理用の内容
        #FaceIDがあるかで判定
    url_txt=urllib.parse.quote(txt)
    url_base="https://example.com/gete.php?n="
    url_all=url_base+url_txt
    with urllib.request.urlopen(url_all) as res:
        body = res.read()
        print(body)
    print(txt)
    return {
        'statusCode': 200,
        'body': data
    }

```
#ユーザーへの通知処理
今回はフロントをhtmlで組んでいるので、ユーザーの通知はwebNotificationを使用します。
webNotificationが予想以上に強いやつで、そのページのタブがブラウザ内に存在していてかつブラウザアプリが落ちていなければ、別のタブを見ていても、ブラウザ以外のアプリを使っていてもスマホで通知を表示してくれます。
処理の流れとしてはPHPサーバーにfunc_movから顔認証の結果が飛んでくるので、その情報からその顔写真を登録したユーザーを特定し結果をユーザーのセッションごとに用意したファイルに保存します。
この情報を取得するために、冒頭のadd.htmlで送信したあとに表示されるページで、ajaxを使用して定期的に自分の情報をPHPサーバーに取得しにいきます。
その際に新しい情報があればwebnotificationを出して、その情報を画面に表示します。
これですべて実装完了です。
#結論
- AWSRekognitionを使うと簡単に画像解析や映像解析ができる。
- 無茶な実装をもうしないですむように、これから色々と勉強しなければならない。