📝

本人確認における Amazon Rekognition の使用のチュートリアルを Lambda でやってみた

に公開

Using Amazon Rekognition for Identity Verification - Amazon Rekognition
上記チュートリアルを Lambda でやってみました。

前提

  • Lambda のランタイム: Python 3.13
  • Lambda 実行ロールの権限: AdministratorAccess
  • Lambda のタイムアウト: 10 秒
  • Lambda の実行方法: コンソールからのテスト実行
  • 使用する画像は S3 バケットに保存済み
    • 画像比較用に異なる画像を 2 枚
    • ただし映っている人物は 1 人

概要

  • Rekognition のコレクション作成
    • 検出した顔に関する情報を保存するコンテナ
  • 画像から顔の検出
    • 人物が一人であるかどうかをチェック
  • 2 つの画像の人物を比較
  • 画像の人物がコレクションに登録されているかをチェック
  • コレクションに人物情報を登録
  • コレクションの情報を DynamoDB に保存
    • 今回は保存するだけで用途はなし
  • 再度人物画像がコレクションに登録されているかをチェック

01. コレクションを作成する

  • collection-id-name を任意の名称に置換
    • 今回は test
  • region-name を使用リージョンコードに変換
    • 今回は ap-northeast-1
コード
import json
import boto3

collection_id = 'collection-id-name'
region = "region-name"

def lambda_handler(event, context):
    create_collection(collection_id, region)

def create_collection(collection_id, region):
    client = boto3.client('rekognition', region_name=region)

    # Create a collection
    print('Creating collection:' + collection_id)
    response = client.create_collection(CollectionId=collection_id, 
    Tags={"SampleKey1":"SampleValue1"})
    print('Collection ARN: ' + response['CollectionArn'])
    print('Status code: ' + str(response['StatusCode']))
    print('Done...')

実行後に以下のログが出力されます。

Creating collection:test
Collection ARN: aws:rekognition:ap-northeast-1:012345678901:collection/test
Status code: 200
Done...

02. DetectFaces オペレーションを呼び出す

  • photo-name は S3 バケットに保存済みの画像ファイル名に置換
  • region-name を使用リージョンコードに変換

チュートリアルではローカルファイルの画像を読み込んでいますが、Lambda で試したところ InvalidImageFormatException が発生しました。
そのため、S3 バケットから画像を読み取る処理に変更しています。

コード
import boto3
import json

photo = 'photo-name'
region = 'region-name'

def lambda_handler(event, context):
    face_count=detect_faces(photo, region)
    print("Faces detected: " + str(face_count))
    
    if face_count == 1:
        print("Image suitable for use in collection.")
    else:
        print("Please submit an image with only one face.")


def detect_faces(target_file, region):

    client=boto3.client('rekognition', region_name=region)
    response = client.detect_faces(
        Image={
        'S3Object': {
            'Bucket': 'your-bucket-name',
            'Name': target_file,
            }
        }, 
        Attributes=['ALL'])

    print('Detected faces for ' + photo)
    for faceDetail in response['FaceDetails']:
        print('The detected face is between ' + str(faceDetail['AgeRange']['Low'])
              + ' and ' + str(faceDetail['AgeRange']['High']) + ' years old')

        print('Here are the other attributes:')
        print(json.dumps(faceDetail, indent=4, sort_keys=True))

        # Access predictions for individual face details and print them
        print("Gender: " + str(faceDetail['Gender']))
        print("Smile: " + str(faceDetail['Smile']))
        print("Eyeglasses: " + str(faceDetail['Eyeglasses']))
        print("Emotions: " + str(faceDetail['Emotions'][0]))

    return len(response['FaceDetails'])

実行後に以下のログが出力されます。
※詳細なログも出力されますが割愛します。

Gender: {'Value': 'Male', 'Confidence': 99.70700073242188}
Smile: {'Value': True, 'Confidence': 64.96673583984375}
Eyeglasses: {'Value': True, 'Confidence': 99.91963958740234}
Emotions: {'Type': 'HAPPY', 'Confidence': 55.158203125}
Faces detected: 1
Image suitable for use in collection.

03. CompareFaces オペレーションを呼び出す

  • bucket-name は画像を保存した S3 バケット名に置換
  • source-file-name は S3 バケットに保存した画像ファイル名
  • target-file-name は S3 バケットに保存した source-file-name とは異なる画像ファイル名
  • region-name を使用リージョンコードに変換
コード
import boto3

bucket = 'bucket-name'
source_file = 'source-file-name'
target_file = 'target-file-name'
region = "region-name"

def lambda_handler(event, context):
    face_matches = compare_faces(bucket, source_file, target_file, region)
    print("Face matches: " + str(face_matches))

    if str(face_matches) == "1":
        print("Face match found.")
    else:
        print("No face match found.")

def compare_faces(bucket, sourceFile, targetFile, region):
    client = boto3.client('rekognition', region_name=region)
    response = client.compare_faces(SimilarityThreshold=99,
                                    SourceImage={'S3Object':{'Bucket':bucket,'Name':sourceFile}},
                                    TargetImage={'S3Object':{'Bucket':bucket,'Name':targetFile}})

    for faceMatch in response['FaceMatches']:
        position = faceMatch['Face']['BoundingBox']
        similarity = str(faceMatch['Similarity'])
        print('The face at ' +
              str(position['Left']) + ' ' +
              str(position['Top']) +
              ' matches with ' + similarity + '% confidence')

    # imageTarget.close()
    return len(response['FaceMatches'])

実行後に以下のログが出力されます。

The face at 0.381568044424057 0.02870430052280426 matches with 99.99736785888672% confidence
Face matches: 1
Face match found.

04. SearchFacesByImage オペレーションを呼び出す

  • collectionId は 01 で作成済みのコレクション名
  • region-name を使用リージョンコードに変換
  • photo-name は S3 バケットに保存済みのいずれかの画像ファイル名に置換
  • bucket-name は画像を保存した S3 バケット名に置換
コード
import boto3

collectionId = 'collection-id-name'
region = "region-name"
photo = 'photo-name'
threshold = 70
maxFaces = 1
bucket = 'bucket-name'

def lambda_handler(event, context):

    client = boto3.client('rekognition', region_name=region)
    response = client.search_faces_by_image(CollectionId=collectionId,
    Image={'S3Object':{'Bucket':bucket,'Name':photo }},
    FaceMatchThreshold=threshold, MaxFaces=maxFaces)
    print(response)

    faceMatches = response['FaceMatches']
    print(faceMatches)

    for match in faceMatches:
        print('FaceId:' + match['Face']['FaceId'])
        print('ImageId:' + match['Face']['ImageId'])
        print('Similarity: ' + "{:.2f}".format(match['Similarity']) + "%")
        print('Confidence: ' + str(match['Face']['Confidence']))

実行後に以下のログが出力されます。
この時点ではコレクションに画像が登録されていないのでレスポンスは空です。

[]

05. IndexFaces オペレーションを呼び出す

  • image は S3 バケットに保存済みのいずれかの画像ファイル名に置換
  • collectionId は 01 で作成済みのコレクション名
  • photo-name は任意の名称に置換 (ファイル名など)
  • region-name を使用リージョンコードに変換
  • bucket-name は画像を保存した S3 バケット名に置換
コード
import boto3

image = 'image-file-name'
collection_id = 'collection-id-name'
photo_name = 'desired-image-name'
region = "region-name"
bucket = 'bucket-name'

def lambda_handler(event, context):
    indexed_faces_count = add_faces_to_collection(image, photo_name, collection_id, region)
    print("Faces indexed count: " + str(indexed_faces_count))

def add_faces_to_collection(target_file, photo, collection_id, region):
    client = boto3.client('rekognition', region_name=region)
    response = client.index_faces(CollectionId=collection_id,
                                  Image={'S3Object':{'Bucket':bucket,'Name':photo}},
                                  ExternalImageId=photo,
                                  MaxFaces=1,
                                  QualityFilter="AUTO",
                                  DetectionAttributes=['ALL'])
    print(response)

    print('Results for ' + photo)
    print('Faces indexed:')
    for faceRecord in response['FaceRecords']:
        print('  Face ID: ' + faceRecord['Face']['FaceId'])
        print('  Location: {}'.format(faceRecord['Face']['BoundingBox']))
        print('  Image ID: {}'.format(faceRecord['Face']['ImageId']))
        print('  External Image ID: {}'.format(faceRecord['Face']['ExternalImageId']))
        print('  Confidence: {}'.format(faceRecord['Face']['Confidence']))

    print('Faces not indexed:')
    for unindexedFace in response['UnindexedFaces']:
        print(' Location: {}'.format(unindexedFace['FaceDetail']['BoundingBox']))
        print(' Reasons:')
        for reason in unindexedFace['Reasons']:
            print('   ' + reason)
    return len(response['FaceRecords'])

実行後に以下のログが出力されます。
※詳細なログも出力されますが割愛します。

Results for DSC_0001.png
Faces indexed:
Face ID: bbf242f9-15b1-4ef6-a37d-bc5e6bd11507
Location: {'Width': 0.4596697688102722, 'Height': 0.5054149627685547, 'Left': 0.3576127886772156, 'Top': 0.3911670446395874}
Image ID: ec9532ed-9d7b-3396-b018-3d9dc4e1e328
External Image ID: DSC_0001.png
Confidence: 99.99915313720703
Faces not indexed:
Faces indexed count: 1

06. イメージと FaceID のデータを S3 と DynamoDB に保存する

S3 へのアップロード

S3 への画像のアップロードはコンソールから行いました。
保存済みの画像以外で同じ人物が一人だけ映っている画像をアップロードしました。

DynamoDB テーブルの作成

  • database_name は任意の名称に置換
  • region-name を使用リージョンコードに変換
コード
import boto3

region = "ap-northeast-1"
database_name = 'database-name'

def lambda_handler(event, context):
    dynamodb_table = create_dynamodb_table(database_name, region)
    print("Table status:", dynamodb_table)

def create_dynamodb_table(table_name, region):
    dynamodb = boto3.client("dynamodb", region_name=region)

    table = dynamodb.create_table(
        TableName=table_name,   
        KeySchema=[{
                'AttributeName': 'FaceID', 'KeyType': 'HASH'  # Partition key  
                },],        
            AttributeDefinitions=[
            {
                'AttributeName': 'FaceID', 'AttributeType': 'S'  }, ],        
                ProvisionedThroughput={
            'ReadCapacityUnits': 10, 'WriteCapacityUnits': 10  }
    )
    print(table)
    return table

DynamoDB テーブルへの保存

  • bucket-name は画像を保存した S3 バケット名に置換
  • file-name および key-name はS3 バケットに保存済みのいずれかの画像ファイル名に置換
  • region-name を使用リージョンコードに変換
  • FACE-ID-HERE は 05 で取得した Face ID に置換
  • IMAGE-ID-HERE は 05 で取得した Image ID に置換
  • confidence-here は 05 で取得した Confidence に置換
コード
import boto3
from pprint import pprint
from decimal import Decimal
import json

bucket = "bucket-name"
file_name = "file-name"
key_name = "key-name"
region = "region-name"
# Get URL of file
file_url = "https://s3.amazonaws.com/{}/{}".format(bucket, key_name)
print(file_url)


def lambda_handler(event, context):
    dynamodb_resp = AddDBEntry(file_name, file_url, "FACE-ID-HERE",  
    "IMAGE-ID-HERE", confidence-here)
    print("Database entry successful.")
    pprint(dynamodb_resp, sort_dicts=False)

def AddDBEntry(file_name, file_url, face_id, image_id, confidence):
    dynamodb = boto3.resource('dynamodb', region_name=region)
    table = dynamodb.Table('test')
    response = table.put_item(
       Item={
            'ExternalImageID': file_name,
            'ImageURL': file_url,
            'FaceID': face_id,
            'ImageID': image_id, 
            'Confidence': json.loads(json.dumps(confidence), parse_float=Decimal)
       }
    )
    return response

実行後、DynamoDB テーブルに上記アイテムが保存されます。

07. SearchFacesByImage オペレーションを呼び出す

ここまでの手順でコレクションに画像を登録できているので、再度 04 のコードを実行します。
04 の時点ではレスポンスが空でしたが、コレクション登録後には以下のようなログが出力されます。

FaceId:bbf242f9-15b1-4ef6-a37d-bc5e6bd11507
ImageId:ec9532ed-9d7b-3396-b018-3d9dc4e1e328
Similarity: 100.00%
Confidence: 99.99919891357422

コレクションに登録された画像の人物と、比較用に使用した画像の人物が一致しているかどうかを確認できています。

ユースケース

Using Amazon Rekognition for Identity Verification - Amazon Rekognition

By making use of Amazon Rekognition’s face detection, face comparison, and collection management operations, you can create an application with an identity verification solution.

チュートリアルの説明通りですが、本人確認を顔認証で行う場合に使用できるソリューションです。

  • アプリなどから撮影画像などをアップロード
  • サーバー側でコレクションに登録されているか確認
    • 登録されている: 認証済みとして処理
    • 登録されていない: 未認証として処理

まとめ

今回は本人確認における Amazon Rekognition の使用のチュートリアルを Lambda でやってみました。
どなたかの参考になれば幸いです。

参考資料

Discussion