前回に続き、「Dev AWSome Day 2018」の復習として、「Amazon Rekognition」を勉強し直します。
今回ハンズオンで作成したサービスは、画像をアップロードし、AIで識別した結果を表示するものになります。
今回は、その裏側の処理であるAI部分の作成の説明になります。
処理の流れとしては、
- S3に画像ファイルが登録されると、
- それをLambdaが見つけ、
- Rekognitionに渡し、
- 結果をDynamoDBに登録する
といった感じになります。
※実は、Rekognitionは呼ぶだけになりますので、説明の中心はLambdaになります
DynamoDBの設定
DynamoDB は、どのような規模のデータも、スループットも満たすことができるフルマネージド型のNoSQL データベースです。
今回は、AWS CLIを使用して、DynamoDBの設定を行います。
まずテーブルの作成を行います。
> aws dynamodb create-table --cli-input-json file://./photos-table.json --region us-west-2
「photo-table.json」に設定されている内容で、DynamoDBにテーブルを作成します。
「photo-table.json」の内容は以下のようになっています。
{
"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"
}
]
}
正常に作成されると、以下のように表示されます。
{
"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関数の作成
- マネージメントコンソールを開く
- 「Lambda」を選択
- 「関数の作成」ボタンをクリック
- 「一から作成」を選択
- 「名前」を入力し、「ロール」は既存のロールを選択、「既存のロール」に先ほど作成したIAMロールを選択
- 「関数の作成」ボタンをクリック
続いて、AWS CLIからコードをデプロイします。
まず、コード(Pythonで記述)をzipにまとめます。
> zip ImageLabel.zip lambda_function.py
次にデプロイします。
aws lambda update-function-code \
--function-name DevAWSomeImageLambda \
--zip-file fileb://ImageLabel.zip \
--region us-west-2
※「DevAWSomeImageLambda」は作成したLambda関数の名前です
なお、デプロイしたコードは以下のようになっています。
# 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)
デプロイが正常に終了すると、以下のような表示になります。
{
"TracingConfig": {
"Mode": "PassThrough"
},
"CodeSha256": "DSR+YJAEAzDSnyeI/BTGFhzdXkoM3zAu1fhC5nqvSm0=",
~ 途中略 ~
"Handler": "lambda_function.lambda_handler",
"Runtime": "python3.6",
"Description": ""
}
次に、タイムアウトの値を20秒に変更します。
aws lambda update-function-configuration \
--function-name DevAWSomeImageLambda \
--region us-west-2 \
--timeout 20
イベントの設定
最後に、S3バケットに画像が登録されたら、そのイベント処理としてLambdaを実行するようにS3バケットの設定を追加します。
- マネージメントコンソールを開く
- 「S3」を選択
- 前回作成したバケットを選択
- 「プロパティ」タブを選択
- 「Events」を選択
- 「通知の追加」を選択
- 「名前」を入力し、「イベント」で「Put」のみを選択、「送信先」で「Lambda関数」を選択、「Lambda」で先ほど作成したLambda関数名を選択
- 「保存」ボタンをクリック
まとめ
肝心のRekognitionを使っているところは、Lambda関数のコード内にあります。
今回はLambda関数からの呼び出し方、および、Lambda関数のアクセス制限の設定方法がメインとなっていました。