Amazon Rekognitionで顔認証してみた

#はじめに
re:Invent 2016でRekognitionというAIを活用した画像認識サービスが発表されました。
ということで、今回はこのサービスを使用して顔認証システムを作ってみました。

#Rekognitionとは
[ここ](https://console.aws.amazon.com/rekognition/home?region=us-east-1#/)から「Try Demo」で試すことができます。
開発する時には、様々な言語をサポートしているので、得意なもので書きましょう。
残念ながら、東京リージョンにはまだありませんのでご注意ください。
できることは大きく分けて以下の4つです。

####1.物体とシーンの検出
「この写真には○○が写ってる」や「こんな場面で撮られたんでは」などなどをラベル付けしてくれます。
例えばこんな感じ。
![image](https://qiita-image-store.s3.amazonaws.com/0/153290/8f98ebaa-6eb0-1657-2f0e-99d8268995cd.png)
朝食っぽい画像をアップロードしてみました。
ちゃんとラベルに「Breakfast」がありますね。
ラベルの隣の数値は信頼度です。「97%朝食の画像っぽい」ということを判定してくれているようです。

####2.顔分析
画像内の顔の位置を特定して、その人の感情を判定してくれます。
例えば
![image](https://qiita-image-store.s3.amazonaws.com/0/153290/7e32279e-9ea0-e582-4d36-c95ef86a8e50.png)
こちらの画像のタイトルは「[腕を組んで怒る彼女](https://www.pakutaso.com/20161120322post-9542.html)」でした。
ちゃんと顔の位置を判断してくれていて、ラベルも「not smailing」や「angry」などタイトル通りの結果を返してくれています。
性別も判定してくれるようですね。

####3.顔照合
同じ人が写っている2枚の画像を比較して、同一人物かどうかを判定してくれます。
またまた例えば
![image](https://qiita-image-store.s3.amazonaws.com/0/153290/f1dcb131-0c9e-cbd2-69d1-4f563de4bfb6.png)
こんな風に顔を抽出して同一人物か判定してくれます。
（ちなみに写真は「日本一インターネットで顔写真が使われているフリー素材モデル」さんです。）

####4.顔認識
あらかじめ作っておいたコレクションから、判定してほしい画像と同一人物を探してくれます。
顔照合が「1:1」だったのに対し、こちらは「1:多」で使用できます。
今回作ったものはこれを利用しています。

#今回作ったもの
今回作成した顔認証システムについてご紹介します。
JavaScriptで実装しました。大まかな構成は以下の図です。

![Rekognition構成図.png](https://qiita-image-store.s3.amazonaws.com/0/153290/0b3c3396-365e-f618-fc63-a98ed3ab2dca.png)

■画像の登録
①S3バケットに画像をアップロードする
②それをトリガーにLambdaが動く
③LambdaがRekognitionのコレクションに画像を登録する

```Javascript
// 一部抜粋
exports.handler = function(event, context) {
    console.log("strat import");
    //pathを取得
    var key = event.Records[0].s3.object.key;
    var ids = key.split('/');
    var params = {
      CollectionId: "コレクション名", 
      DetectionAttributes: [
      ], 
      ExternalImageId: ids[ids.length-1], 
      Image: {
       S3Object: {
        Bucket: "バケット名", 
        Name: key
       }
      }
     };
     rekognition.indexFaces(params, function(err, data) {
       if (err) console.log(err, err.stack); 
       else     console.log(data);           
     });
}
```

■画像の削除
①S3バケット内の画像を削除する
②それをトリガーにLambdaが動く
③LambdaがRekognitionのコレクションの画像から削除対象を検索し、削除する

```Javascript
// 一部抜粋
exports.handler = (event, context, callback) => {
    //共通変数
    var collectionId = 'コレクション名'
    //pathを取得
    var key = event.Records[0].s3.object.key;
    var ids = key.split('/');
    var externalImageId = ids[ids.length-1];
    // 検索用
    function isThisName(element, index, array){
        if(element == null || element.ExternalImageId!=externalImageId){
            var param = {};
            param.elementExternalImageId=element.ExternalImageId;
            param.externalImageId=externalImageId;
            param.message = 'error:Image not found';
            return false;
        }
        return true;
    }
    // コレクションの顔一覧から名前を指定して探し出してDelete
    var params = {
      CollectionId: collectionId, 
      MaxResults: 20
    };
    rekognition.listFaces(params, function(err, data) {
        if (err) console.log(err, err.stack);
        else{     
            try{
                var deleteId = data.Faces.find(isThisName).FaceId;
                console.log('Face name detected! : ' + externalImageId); // コンソールに見つけた画像のIDを出力
                var params = {
                  CollectionId: collectionId, 
                  FaceIds: [
                    deleteId
                  ]
                };
                rekognition.deleteFaces(params, function(err, data) {
                  if (err) console.log(err, err.stack); 
                  else     console.log(data + ' delete completed'); //正常に削除された場合 
                });
            }catch(err){
                console.log(err);
            }
        }
    });
};
```
↑ここまでが顔認証するための下準備的な感じ↑

■顔認証
①画像をAPI GatewayにPOSTする
②それを（略）
③LambdaがRekognitionのコレクションから類似率の高い画像を探してくる
④類似率と類似率の高い画像の名前を返す

```Javascript
// 一部抜粋
exports.handler = (event, context, callback) => {
    var response = {};
    response.result = "ERROR";// デフォルトはエラー
    response.matchedFaces = null;
    response.stackTrace = null;
    
    console.log(JSON.stringify(event));
    console.log(JSON.stringify(event.base64Image));
    var bufferImage = new Buffer(event.base64Image, 'base64');
    var params = {
        CollectionId: "コレクション名", 
        FaceMatchThreshold: 0, 
        Image: {
            Bytes: bufferImage
        }, 
        MaxFaces: 5
    };
    
    rekognition.searchFacesByImage(params, function(err, data) {
        if (err){
            console.log(err, err.stack);
            response.stackTrace = err.stack;
            callback( null , response);
        }
        else{
          if(data == null || data.FaceMatches == null || data.FaceMatches.length <= 0){
            response.result = "NG";
            response.matchedFaces = null;
            callback( null , response);// マッチする顔画像がなかった場合
          }else{
            response.result = "OK";
            response.matchedFaces = data.FaceMatches;
            callback( null , response);// マッチする顔画像があった場合
          }
        }
    });
};
```

そして顔認証を実行した結果がこちら

```JSON
{
    "result": "OK",
    "matchedFaces": [
        {
            "Similarity": 99.94152069091797,
            "Face": {
                "FaceId": "852daf16-fe77-5867-a5b2-d1474ca69297",
                "BoundingBox": {
                    "Width": 0.6410260200500488,
                    "Height": 0.48076900839805603,
                    "Left": 0.19658100605010986,
                    "Top": 0.25641000270843506
                },
                "ImageId": "fa85cbbe-78ce-5e45-bd35-22aee327f427",
                "ExternalImageId": "類似率の高い画像のタイトル",
                "Confidence": 99.99440002441406
            }
        }
    ],
    "stackTrace": null
}
```
>result：同一人物っぽい画像があったらOKなかったらNGを返しています
Similarity：同一人物である確度は何％かどうか（今回は99%）
ExternalImageId：コレクション内の類似度の高い画像のタイトル

だいたいこれらの値を見れば、POSTした画像の人がどの写真の人と、どのくらいの確率で同一人物かが分かります。

#おわりに
ということで、今回はRekognitionを使って顔認証をしてみました。
AIとか顔認証とか、ちょっとハードルが高そうだと思っていましたが、いよいよ身近になってきたなぁという感じがします。
ドラ○もんの誕生もそう遠くないかもしれませんね。