StepFunctionsを使って画像内ラベル検出をしてみる

おはようございます、もきゅりんです。

ふとしたタイミングでStepFunctionsを触る機会があったので、巷でよくあるパターンと言われている構成をやってみました。

下図です。

architecture image

例えば、こんな写真をアップロードしてみると、

crown cat

DynamoDBにこんな感じの結果が挿入されるというシロモノです。

cat result

AWSってすげー。。何か自分、無能かつ無為に生きててゴメンって思わされました。

とりあえずStepFunctions(も)楽しいです。

やってみよう

前提条件

  • AWS CLIがインストールされていること(手動で作成でも可)
  • StepFunctionsへの興味関心

S3バケットとDynamoDBテーブルをCFnで作成します。

※ 注 すべてのリソースは同じAWSリージョン内に作成する必要があります。

やること

  1. S3Bucket,DynamoDBを作成
  2. Lambdaを準備
  3. ステートマシン作成
  4. S3​イベント発生時にステートマシンを実行させる
  5. テスト

1. S3Bucket, DynamoDBを作成

CFnで作成しちゃいます。

(コンソールから作成でも構いません。)

aws cloudformation deploy --template-file S3BucketDynamoDB.yml \
--stack-name demo-stepfunctions-resources
# S3BucketDynamoDB.yml

AWSTemplateFormatVersion: "2010-09-09"
Description: "Create DynamoDB Table & S3 Bucket"
Resources:
  DynamoTable:
    Type: "AWS::DynamoDB::Table"
    Properties:
      AttributeDefinitions:
        - AttributeName: "id"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "id"
          KeyType: "HASH"
      ProvisionedThroughput:
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"
      TableName: "ImageInfo"
  S3Backet:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

2. Lambdaを準備

やり方は他にも色々とあると思いますが、この稿では3つのLambdaを用意します。

  • S3のメタデータを取得するLambda
  • 画像のラベル抽出をするLambda
  • DynamoDBにPutするLambda

(例えば、ステートマシンから直接DynamoDBテーブルにPutすることもできます)

本来はそれぞれに必要最小限の権限のロールを設定するのが定石なのですが、本番利用ではないという言い訳をしながら、下記のようなポリシーをlambda-basic-exec-roleに追加しています。

  • AmazonS3ReadOnlyAccess
  • AmazonDynamoDBFullAccess
  • AmazonRekognitionReadOnlyAccess
  • TranslateReadOnly

Translateについては、画像のラベル検出結果が英語のため、ナンダコレ?となるのを避けるために日本語翻訳させるためです。

下記3つのLambdaをPython3.7で、一から作成します。

ロールは上記、無精なlambda-basic-exec-roleです。

S3からはイベント発生時間、画像のバイト数、クライアントIPアドレス、ファイル名を取得しています。

ラベル検出は主要な結果と信頼性のみ取得しています。

# GetS3Metadata
def lambda_handler(event, context):
    id = event['id']
    time = event['time']
    size = event['detail']['additionalEventData']['bytesTransferredIn']
    IP = event['detail']['sourceIPAddress']
    file_name = event['detail']['requestParameters']['key']

    s3_meta_data = {}
    s3_meta_data['id'] = id
    s3_meta_data['size'] = str(size)
    s3_meta_data['time'] = time
    s3_meta_data['IP'] = IP
    s3_meta_data['file_name'] = file_name

    return s3_meta_data
# GetImageLabels
import boto3
import copy

rekognition_client = boto3.client('rekognition')
translate_client = boto3.client(service_name='translate', use_ssl=True)


def lambda_handler(event, context):

    fileName = event['detail']['requestParameters']['key']
    bucket = event['detail']['requestParameters']['bucketName']

    # 画像のラベル検出
    response = rekognition_client.detect_labels(
        Image={'S3Object': {'Bucket': bucket, 'Name': fileName}})

    detect_dict = {}

    temp_d = {}
    for i, label in enumerate(response['Labels']):
        # ラベルを日本語に翻訳
        labelName = translate_client.translate_text(Text=label['Name'],
                                                    SourceLanguageCode="en", TargetLanguageCode="ja").get('TranslatedText')
        temp_d['Label'] = labelName
        temp_d['Confidence'] = str(label['Confidence'])
        detect_dict[i] = str(copy.deepcopy(temp_d))

    return detect_dict
# PutDynamoDB
import boto3
import json
from datetime import datetime

dynamodb_resource = boto3.resource('dynamodb')
table = dynamodb_resource.Table('ImageInfo')


def lambda_handler(event, context):

    id = event[0]['id']
    size = event[0]['size']
    IP = event[0]['IP']
    time = event[0]['time']
    file_name = event[0]['file_name']
    image_labeling = event[1]

    time_stamp = datetime.now().strftime("%Y%m%d%H%M%S")

    # テーブル挿入
    table_put_item_response = table.put_item(
        Item={
            'id': id,
            'size': size,
            'IP': IP,
            'time': time,
            'file_name': file_name,
            'create_time_stamp': time_stamp,
            'detection': image_labeling
        }
    )
    return table_put_item_response

3. ステートマシン作成

ようやくStepFunctionsのお出ましです。

ステートマシンを作成します。

ロールはAWSLambdaFullAccessで進めています。

作成内容はこの図の通りです。

sfn image

Resourceのarnについては、2で作成したLambdaをそれぞれ作成順に記入して下さい。

# DemoImageExtract.json
{
  "Comment": "Coordinate tasks for image analysis",
  "StartAt": "ImageAnalysis",
  "States": {
    "ImageAnalysis": {
      "Type": "Parallel",
      "Next": "PutDynamo",
      "Branches": [
        {
          "StartAt": "ImageExtractMetaData",
          "States": {
            "ImageExtractMetaData": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxx:function:GetS3Metadata",
              "OutputPath": "$",
              "End": true
            }
          }
        },
        {
          "StartAt": "ImageLabeling",
          "States": {
            "ImageLabeling": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:ap-northeast-1:yyyyyyyyyyy:function:GetImageLabels",
              "OutputPath": "$",
              "End": true
            }
          }
        }
      ]
    },
      "PutDynamo": {
        "Type": "Task",
        "Resource": "arn:aws:lambda:ap-northeast-1:zzzzzzzzzzzzz:function:PutDynamo",
        "End": true
      }
    }
  }

4. S3​イベント発生時にステートマシンを実行させる

Amazon CloudWatch Eventsを使用して、イベント発生時にStepFunctionsステートマシンを実行するために、Amazon S3​ イベント発生時にステートマシンの実行を開始するに記載されている通り、進めていきます。

AWS CloudTrail で[証跡の表示]、[証跡の作成] の順に選択します。

cloudtrail s3event1

cloudtrail s3event2

[証跡名] に、(名称は何でもいいですが)「S3Event」と入力しておきます。

cloudtrail s3event3

[バケット名] に、1で作成しておいたAmazonS3バケット名を入力し、[保存場所] で、[新しい S3 バケットを作成しますか] の横の [はい] を選択します。

[S3バケット] に、先に作成したAmazonS3バケットのアクションに関する情報を保存する新しいバケットの名前を(何でもよいので)適当に入力します。

cloudtrail s3event4

CloudWatchイベントルールを作成していきます。

CloudWatchイベントコンソールに移動し、[イベント] を選択して [ルールの作成] を選択します。

記載された通りに入力していきます。1で作成したバケット名を入力します。

cwe setting1

ターゲットを作成します。3で作成したステートマシンを選択します。

ロールについては、既存のものでも新規でも良いですが、既存のロールを選択した場合は、イベントを受けるステートマシン名を確認した方が良いと思います。

自動でロールを作成した場合、そのとき選んだステートマシンが宛先になっているためです。

cwe setting2

ルールの名前(判れば何でも可)を入力し、[State] で [Enabled] を選択してから、[Create rule] を選択します。

cwe setting3

テスト

S3バケットに何かアップロードしてみます。

かわいい蛇の写真。

snake

アップロードします。

s3 upload

DynamoDBのテーブルでは、こんな感じに登録されます。

dynamodb result

ちなみに、直接Rekognitionを使用した結果はこちら。

rekognition result

終わりに

アイディア次第でいろいろと面白い取り組みに利用できそうです。

今後も思いつきで取り組みたいと思います。

なお、自身のPython力の弱さのために、余分な時間を随分費やしたと痛感しました。。

不明点を懇切丁寧に教えてくれたMr.Kitano、感謝しかないです。ありがとうございました。

以上です、どなたかのお役に立てば幸いです。

参考

イメージ内のラベルの検出

AWS SDK for Python (Boto) を使用したテキストの翻訳

Amazon S3​ イベント発生時にステートマシンの実行を開始する

[やってみた]Step FunctionsのみでDynamo DBテーブルからアイテム取得/登録をしてみた #reinvent

Step Functions を使用した DynamoDB API の呼び出し

ぱくたそ

Pythonのcopyとdeepcopyについて