Dev AWSome Day 2018の復習④　～Amazon Rekognition編～

[前回](https://qiita.com/mine820/items/cbfd7913bccd784591ba)に続き、「[Dev AWSome Day 2018](https://aws.amazon.com/jp/aws-devday-tokyo-2018/dev-awsomeday/)」の復習として、「Amazon Rekognition」を勉強し直します。

今回ハンズオンで作成したサービスは、画像をアップロードし、AIで識別した結果を表示するものになります。
今回は、その裏側の処理であるAI部分の作成の説明になります。

処理の流れとしては、

1. S3に画像ファイルが登録されると、
2. それをLambdaが見つけ、
3. Rekognitionに渡し、
4. 結果をDynamoDBに登録する

といった感じになります。

※実は、Rekognitionは呼ぶだけになりますので、説明の中心はLambdaになります

# DynamoDBの設定
DynamoDB は、どのような規模のデータも、スループットも満たすことができるフルマネージド型のNoSQL データベースです。

今回は、AWS CLIを使用して、DynamoDBの設定を行います。
まずテーブルの作成を行います。

```text
> aws dynamodb create-table --cli-input-json file://./photos-table.json --region us-west-2
```

「photo-table.json」に設定されている内容で、DynamoDBにテーブルを作成します。
「photo-table.json」の内容は以下のようになっています。

```text
{
    "LocalSecondaryIndexes": [
        {
            "IndexName": "username-updatetime-index", 
            "Projection": {
                "ProjectionType": "ALL"
            }, 
            "KeySchema": [
                {
                    "KeyType": "HASH", 
                    "AttributeName": "username"
                }, 
                {
                    "KeyType": "RANGE", 
                    "AttributeName": "updatetime"
                }
            ]
        }
    ], 
    "AttributeDefinitions": [
        {
            "AttributeName": "objectkey", 
            "AttributeType": "S"
        }, 
        {
            "AttributeName": "updatetime", 
            "AttributeType": "S"
        }, 
        {
            "AttributeName": "username", 
            "AttributeType": "S"
        }
    ], 
    "ProvisionedThroughput": {
        "WriteCapacityUnits": 5, 
        "ReadCapacityUnits": 5
    }, 
    "TableName": "devawsome-photos", 
    "KeySchema": [
        {
            "KeyType": "HASH", 
            "AttributeName": "username"
        }, 
        {
            "KeyType": "RANGE", 
            "AttributeName": "objectkey"
        }
    ]
}
```

正常に作成されると、以下のように表示されます。

```text
{
    "TableDescription": {
        "TableArn": "arn:aws:dynamodb:us-west-2:212691234567:table/devawsome-photos",
        "LocalSecondaryIndexes": [
        ～ 略　～
        "ItemCount": 0,
        "CreationDateTime": 1523425086.359
    }
}
```

もちろん、マネージメントコンソールから作成することも可能です。

# Lambda関数の作成
## Lambda関数用のIAMロールの作成
今回作成するLambda関数は、S3、DynamoDB、Rekognition、および、内部的にCloudWatch Logsにアクセスします。
それらの設定をIAMロールに登録します。

作成の仕方はこちらを参考にしてもらうとして、アクセス権限ポリシーをアタッチする画面で、以下の許可を行います。

- AmazonS3ReadOnlyAccess
- AmazonDynamoDBFullAccess
- AmazonRekognitionReadOnlyAccess
- AWSLambdaBasicExecutionRole

## Lambda関数の作成

1. マネージメントコンソールを開く
2. 「Lambda」を選択<br/>![image.png](https://qiita-image-store.s3.amazonaws.com/0/130771/f692d1d7-e4bd-6862-4c61-c649ac5521f8.png)
3. 「関数の作成」ボタンをクリック
4. 「一から作成」を選択
5. 「名前」を入力し、「ロール」は既存のロールを選択、「既存のロール」に先ほど作成したIAMロールを選択
6. 「関数の作成」ボタンをクリック

続いて、AWS CLIからコードをデプロイします。
まず、コード（Pythonで記述）をzipにまとめます。

```text
> zip ImageLabel.zip lambda_function.py
```

次にデプロイします。

```text
aws lambda update-function-code \
  --function-name DevAWSomeImageLambda  \
  --zip-file fileb://ImageLabel.zip \
  --region us-west-2
```
※「DevAWSomeImageLambda」は作成したLambda関数の名前です

なお、デプロイしたコードは以下のようになっています。

```python
# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
# in compliance with the License. A copy of the License is located at
#
# https://aws.amazon.com/apache-2-0/
#
# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
from __future__ import print_function
import json
import urllib
import boto3
from datetime import datetime

s3 = boto3.client('s3')
rek = boto3.client('rekognition')
dynamodb = boto3.resource('dynamodb')

def lambda_handler(event, context):
    "Process upload event, get labels and update database"
    bucket = event['Records'][0]["s3"]["bucket"]["name"]
    key = event['Records'][0]["s3"]["object"]["key"]
    key = urllib.parse.unquote(key);
    print("Received event. Bucket: [%s], Key: [%s]" % (bucket, key))

    response = s3.head_object(Bucket=bucket, Key=key)
    username =  response['Metadata']['username'];
    description = response['Metadata']['description']
    print("username : %s" % username)
    print("description : %s" % description)
    
    response = rek.detect_labels(
        Image={
            'S3Object': {
                'Bucket': bucket,
                'Name': key
            }
        })
    all_labels = [label['Name'] for label in response['Labels']]
    csv_labels = ", ".join(all_labels)
    print("Detect_labels finished. Key: [%s], Labels: [%s]" % (key, csv_labels))
    
    table    = dynamodb.Table('devawsome-photos')
    response = table.put_item(
        Item={
            'username': username,
            'objectkey': key,
            'description': description,
            'labels': csv_labels,
            'updatetime': datetime.now().strftime('%Y%m%d%H%M%S')
        }
    )
    
    return True

# This is used for debugging, it will only execute when run locally
if __name__ == "__main__":
    # simulated sns event
    fake_sns_event = {
        "Records": [
            {
                "EventSource": "aws:sns",
                "EventVersion": "1.0",
                "EventSubscriptionArn": "...",
                "Sns": {
                    "Message": """{\"Records\":[{\"eventVersion\":\"2.0\",
                    \"eventSource\":\"aws:s3\",\"awsRegion\":\"us-west-2\",
                    \"eventTime\":\"...\",\"eventName\":\"ObjectCreated:Put\",
                    \"s3\":{\"bucket\":{\"name\":\"fake-bucket\"},
                    \"object\":{\"key\":\"photos/8d2567bc34013c97.png\"}}}]}""",
                    "MessageAttributes": {}
                }
            }
        ]
    }
    fake_context = []
    lambda_handler(fake_sns_event, fake_context)
```

デプロイが正常に終了すると、以下のような表示になります。

```text
{
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "CodeSha256": "DSR+YJAEAzDSnyeI/BTGFhzdXkoM3zAu1fhC5nqvSm0=",
    ～ 途中略 ～
    "Handler": "lambda_function.lambda_handler",
    "Runtime": "python3.6",
    "Description": ""
}
```

次に、タイムアウトの値を20秒に変更します。

```text
aws lambda update-function-configuration \
  --function-name DevAWSomeImageLambda  \
  --region us-west-2 \
  --timeout 20
```

## イベントの設定
最後に、S3バケットに画像が登録されたら、そのイベント処理としてLambdaを実行するようにS3バケットの設定を追加します。

1. マネージメントコンソールを開く
2. 「S3」を選択
3. 前回作成したバケットを選択
4. 「プロパティ」タブを選択
5. 「Events」を選択
6. 「通知の追加」を選択
7. 「名前」を入力し、「イベント」で「Put」のみを選択、「送信先」で「Lambda関数」を選択、「Lambda」で先ほど作成したLambda関数名を選択
8. 「保存」ボタンをクリック

# まとめ
肝心のRekognitionを使っているところは、Lambda関数のコード内にあります。
今回はLambda関数からの呼び出し方、および、Lambda関数のアクセス制限の設定方法がメインとなっていました。