Raspiカメラで撮影した顔写真をAWS Rekognitionに判定させてみる

# はじめに

だいぶ夏本番になってきましたね
京都の夏は天然サウナじゃないかと感じてます･･･
さて先日、ふと「AWS Rekognition」を使ってみたくなったのと、ただ単純に使うだけでは面白くないと思ったので、Raspiカメラで撮影した顔写真をS3にアップして、Rekognitionに判定させてみよう！ってなったのがことの始まり

## ざっくりとした流れ

1. Raspiカメラで顔写真を撮影
2. S3にアップロード
3. S3へのPutを検地してLambdaを起動
4. RekognitionをSDKで呼び出して結果をCloudWatchLogsに出力

## Raspi側の処理

使うRaspiカメラは市販されているものを使用しました

![raspi_camera.jpg](https://qiita-image-store.s3.amazonaws.com/0/95875/5d5fbc66-311a-4520-fe64-901c2ce528fc.jpeg)

これをRaspiに接続して、外部カメラの設定を変更(許可してあげないとカメラを認識しない)

```
$ sudo raspi-config
```

「6 Enable Camera」を選択して、Enableにしてあげる

カメラ撮影するためのShellファイルを作成

```shoot.sh
raspistill -vf -w 480 -h 320 -o /home/raspi-image.jpg
```

s3cmdでs3に画像を送信するので「s3cmd」をインストール

```
$ sudo yum -y --enablerepo epel install s3cmd
```

Key情報を登録

```
$ s3cmd --configure
Access Key: xxxx
Secret Key: xxxx
```

バケット確認

```
$ s3cmd ls
```

「shoot.sh」にS3へ画像を送信する処理を追記

```shoot.sh
raspistill -vf -w 480 -h 320 -o /home/raspi-image.jpg

s3cmd put -r /home/raspi-image.jpg s3://rekognition-image-bucket
```

Shellファイルを実行して、画像がS3に送信されていればOK！

## AWS側の処理

### Lambda

Rekognitionにリクエストを送信するLambdaを作成する
権限はRekognitionのFull権限とS3のReadOnlyは必要かと思われる

ソースコードはNodeで書いていて、Zipに固めてアップロードしてます
中身はこんな感じ

```js:index.js
"use strict";

const AWS = require("aws-sdk");
const co = require("co");

const rekognition = new AWS.Rekognition();
const rekognitionApi = require("./lib/rekognition_api");

exports.handler = (event, context, callback) => {
  console.log(JSON.stringify(event));
  co(function *() {
    const bucketName = event["Records"][0]["s3"]["bucket"]["name"];
    const objectKey = event["Records"][0]["s3"]["object"]["key"];

    const s3ImageItem = {
      S3Object: {
        Bucket: bucketName,
        Name: objectKey
      }
    };

    // 画像のLabel情報を判定
    const labelParams = {
      Image: s3ImageItem
    };
    console.log(JSON.stringify(labelParams));
    const labels = yield rekognitionApi.detectLabels(rekognition, labelParams);
    console.log(JSON.stringify(labels));

    // 顔から年齢や性別を判定
    const faceParams = {
      Image: s3ImageItem,
      Attributes: ["ALL", "DEFAULT"]
    };
    console.log(JSON.stringify(faceParams));
    const faces = yield rekognitionApi.detectFaces(rekognition, faceParams);
    console.log(JSON.stringify(faces));

  }).then(() => {
    console.log("success");
  }).catch((err) => {
    console.log(err);
  });

};
```

```js:rekognition_api.js
"use strict";

class RekognitionApi {
  /**
   *
   * @param rekognition
   * @param params
   * @returns {*}
   */
  static detectLabels(rekognition, params) {
    console.log("detectLabels");
    return rekognition.detectLabels(params).promise();
  }

  /**
   *
   * @param rekognition
   * @param params
   * @returns {*}
   */
  static detectFaces(rekognition, params) {
    console.log("detectFaces");
    return rekognition.detectFaces(params).promise();
  }
}

module.exports = RekognitionApi;
```


### S3

S3にPut処理があったのを検地してLambdaファンクションをキックするので、S3のコンソール画面から
1. 画像が送信されてくるバケットを選択
2. プロパティタグを選択
3. 詳細設定のEventsを選択して、Lambdaをキックするように設定(下の画像は一例)

![s3_events.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/95795524-44fd-458d-6f02-ea63d3d6f3b2.png)

## 結果

さぁ画像をS3に送るぞ！
駄菓子菓子･･･
実際にRaspiカメラで撮影した画像は社内が写っていていろいろまずいという大人の事情が発生したので、ためしにマツコ・デラックスの画像で判定させてみた

実際に使った画像
![matuko.jpg](https://qiita-image-store.s3.amazonaws.com/0/95875/cf2ce0af-f49f-8c5a-fbde-152d3de611a2.jpeg)

判定結果

```rekognition.detectLabels
{ 
    "Labels": [ 
        { 
            "Name": "Human", 
            "Confidence": 99.30243682861328 
        }, 
        { 
            "Name": "People", 
            "Confidence": 99.30485534667969 
        }, 
        { 
            "Name": "Person",
            "Confidence": 99.30485534667969 
        }, 
        { 
            "Name": "Bowl", 
            "Confidence": 83.46495056152344
        }, 
        { 
            "Name": "Food", 
            "Confidence": 51.59776306152344 
        }, 
        { 
            "Name": "Furniture", 
            "Confidence": 50.9080810546875 
        },
        { 
            "Name": "Portrait",
            "Confidence": 50.520469665527344 
        }, 
        { 
            "Name": "Selfie", 
            "Confidence": 50.520469665527344 
        }
    ], 
    "OrientationCorrection": "ROTATE_0" 
}
```

```rekognition.detectFaces
{ 
    "FaceDetails": [ 
        { 
            "BoundingBox": { 
                "Width": 0.2286902219057083, 
                "Height": 0.30531322956085205, 
                "Left": 0.39798039197921753, 
                "Top": 0.17049960792064667 
            }, 
            "AgeRange": { 
                "Low": 35, 
                "High": 52 
            }, 
            "Smile": { 
                "Value": false, 
                "Confidence": 99.96104431152344 
            }, 
            "Eyeglasses": { 
                "Value": false, 
                "Confidence": 99.99803924560547 
            }, 
            "Sunglasses": { 
                "Value": false, 
                "Confidence": 99.96407318115234 
            }, 
            "Gender": { 
                "Value": "Male", 
                "Confidence": 98.87186431884766 
            }, 
            "Beard": { 
                "Value": false, 
                "Confidence": 99.96611785888672 
            }, 
            "Mustache": { 
                "Value": false, 
                "Confidence": 99.72440338134766 
            }, 
            "EyesOpen": {
                "Value": true, 
                "Confidence": 98.1362075805664 
            }, 
            "MouthOpen": {
                "Value": false, 
                "Confidence": 99.86360931396484 
            }, 
            "Emotions": [ 
                { 
                    "Type": "CALM", 
                    "Confidence": 79.443603515625 
                }, 
                { 
                    "Type": "SAD", 
                    "Confidence": 10.143173217773438 
                }, 
                { 
                    "Type": "ANGRY", 
                    "Confidence": 8.333391189575195 
                } 
            ], 
            "Landmarks": [ 
                { 
                    "Type": "eyeLeft", 
                    "X": 0.4735484719276428, 
                    "Y": 0.29314953088760376 
                }, 
                { 
                    "Type": "eyeRight", 
                    "X": 0.552726149559021, 
                    "Y": 0.2864242494106293 
                },
                { 
                    "Type": "nose", 
                    "X": 0.5234030485153198, 
                    "Y": 0.3416678309440613 
                }, 
                { 
                    "Type": "mouthLeft", 
                    "X": 0.4967532455921173, 
                    "Y": 0.40519285202026367 
                }, 
                { 
                    "Type": "mouthRight", 
                    "X": 0.5460912585258484, 
                    "Y": 0.4002365469932556 
                }, 
                { 
                    "Type": "leftPupil", 
                    "X": 0.4636710584163666, 
                    "Y": 0.291595458984375 
                }, 
                { 
                    "Type": "rightPupil", 
                    "X": 0.5477691292762756, 
                    "Y": 0.2862780690193176 
                }, 
                { 
                    "Type": "leftEyeBrowLeft", 
                    "X": 0.44416821002960205, 
                    "Y": 0.2564706802368164 
                }, 
                { 
                    "Type": "leftEyeBrowRight", 
                    "X": 0.4688655436038971, 
                    "Y": 0.2472878247499466 
                }, 
                { 
                    "Type": "leftEyeBrowUp", 
                    "X": 0.49349144101142883, 
                    "Y": 0.2572619318962097 
                }, 
                { 
                    "Type": "rightEyeBrowLeft", 
                    "X": 0.5358542799949646, 
                    "Y": 0.2516691982746124 
                }, 
                { 
                    "Type": "rightEyeBrowRight", 
                    "X": 0.5540421605110168, 
                    "Y": 0.24033693969249725 
                }, 
                { 
                    "Type": "rightEyeBrowUp", 
                    "X": 0.5732214450836182, 
                    "Y": 0.2406926304101944 
                }, 
                { 
                    "Type": "leftEyeLeft", 
                    "X": 0.45701533555984497, 
                    "Y": 0.29349184036254883 
                }, 
                { 
                    "Type": "leftEyeRight", 
                    "X": 0.48969772458076477, 
                    "Y": 0.293197363615036 
                }, 
                { 
                    "Type": "leftEyeUp", 
                    "X": 0.4736858308315277,
                    "Y": 0.286814421415329 
                }, 
                {
                    "Type": "leftEyeDown", 
                    "X": 0.47360309958457947, 
                    "Y": 0.2992895543575287 
                }, 
                { 
                    "Type": "rightEyeLeft", 
                    "X": 0.5374438166618347, 
                    "Y": 0.28980445861816406 
                },
                { 
                    "Type": "rightEyeRight", 
                    "X": 0.5670959949493408, 
                    "Y": 0.284206748008728 
                }, 
                { 
                    "Type": "rightEyeUp", 
                    "X": 0.5524247288703918, 
                    "Y": 0.28035080432891846 
                }, 
                { 
                    "Type": "rightEyeDown", 
                    "X": 0.5534836649894714, 
                    "Y": 0.2919163405895233 
                }, 
                {
                    "Type": "noseLeft", 
                    "X": 0.5067237019538879, 
                    "Y": 0.3605457544326782 
                }, 
                { 
                    "Type": "noseRight", 
                    "X": 0.5351842045783997, 
                    "Y": 0.35733121633529663 
                }, 
                { 
                    "Type": "mouthUp", 
                    "X": 0.5246664881706238, 
                    "Y": 0.38926947116851807 
                }, 
                {
                    "Type": "mouthDown", 
                    "X": 0.5240681767463684, 
                    "Y": 0.4240013659000397 
                    } 
                ], 
                "Pose": { 
                    "Roll": -4.440439224243164, 
                    "Yaw": 5.928189754486084, 
                    "Pitch": 7.750663757324219 
                }, 
                "Quality": { 
                    "Brightness": 52.19685363769531, 
                    "Sharpness": 98.55191040039062 
                }, 
                "Confidence": 99.98099517822266 
            } 
        ], 
    "OrientationCorrection": "ROTATE_0" 
}
```

よかった。ちゃんと男として判定されたw
ほかにも35～52歳と判定されていたり、「球体」っていうタグがついていたりと、そこそこ面白い結果になりましたw
また顔の部位の位置やどんな表情なのかまで判定してくれています

この結果の中から必要なAttributeを取り出して、Slackなりに通知するBotでも作ろうかな･･･

ではまた！