背景
S3への画像アップロードをトリガーにRekognitionで物体検出するハンズオンをコンソールで実施しました。画像データをどうRekognitionに渡すかで、パフォーマンスが大きく変わる設計ポイントがありました。
1. S3Object参照ならLambdaは画像データを扱わない
response = rekognition.detect_labels(
Image={
"S3Object": {
"Bucket": bucket,
"Name": key,
}
},
MaxLabels=10,
MinConfidence=70,
)
Rekognitionのdetect_labelsは、画像データそのものをバイト列で渡す方式(Bytes)と、S3のバケット名・キーを渡す方式(S3Object)の2種類に対応しています。S3Object方式では、Rekognitionが自らS3にアクセスして画像を取得するため、Lambda関数のメモリに画像データを一切載せる必要がありません。大きな画像や大量の画像を処理する際のメモリ効率が大きく変わります。
2. ただしRekognitionがS3にアクセスするための権限はLambda側のロールが必要
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-rekognition-upload/*"
}
「Rekognitionが自分でS3にアクセスする」といっても、実際にはLambda関数のIAMロールを借りてS3から画像を取得する仕組みです。そのため、Lambdaのロールにs3:GetObject権限がないと、Rekognition呼び出し自体は成功してもS3アクセスの段階で403エラーになります。
3. 必要な権限は「S3読み取り」と「Rekognition呼び出し」の2種類
S3ReadForRekognition(インラインポリシー): s3:GetObject
AmazonRekognitionReadOnlyAccess(マネージドポリシー): rekognition:DetectLabels など
コンソールでは、S3用のインラインポリシーとRekognition用のマネージドポリシーを別々にアタッチする必要があります。AccessDeniedExceptionが出た場合、この2つのうちどちらが欠けているかを切り分けて確認するのが定石です。
4. 日本語ファイル名はunquote_plusが必須
key = urllib.parse.unquote_plus(record["s3"]["object"]["key"])
S3イベントのキー名はURLエンコードされて渡されるため、日本語や空白を含むファイル名を扱う場合はデコードが必要です。これはS3トリガー系のLambda全般に共通する定番パターンです。
5. MinConfidenceで信頼度の低いラベルを除外する
response = rekognition.detect_labels(
Image={"S3Object": {"Bucket": bucket, "Name": key}},
MaxLabels=10,
MinConfidence=70,
)
MinConfidence=70を指定すると、信頼度70%未満のラベルは結果に含まれません。誤検出の少ない結果だけを扱いたい場合は高めに、全ラベルを確認したい場合は0にして再デプロイします。
まとめ
S3Object参照によってLambdaのメモリに画像データを載せずに済む設計と、それでも必要になるS3読み取り権限、この2点がRekognition連携の勘所でした。IAM権限を1つずつ手動で追加していく過程を通じて、SAMのS3ReadPolicyやRekognitionDetectOnlyPolicyが何を自動化しているかが実感できます。