Fusic Tech Blog

Fusion of Society, IT and Culture

AWS Rekognitionの#search_faces_by_image サーバーレスな本人確認をローカルで
2021/11/26

AWS Rekognitionの#search_faces_by_image サーバーレスな本人確認をローカルで

やりたいこと

デバイスのカメラとブラウザを利用してユーザーの顔写真を撮影し、AWSのサーバーレス環境でその顔写真をAWS Rekognitionに問い合わせて本人確認をしたいです。具体的には、予め何人かの顔写真のプールがあって、その中に渡した顔写真と同じ顔があるかという問い合わせをします。

顔写真のプール(コレクション)は既にある状態から進めていきます。 顔のコレクションを作成したい場合はこちらのドキュメントを。(リンク

今回はS3に画像ファイルを置いておくことにしました。そして、それをLambdaで取得し(getObject)、Rekognitionにこの写真の人物が顔写真プールの中にあるかどうか問い合わせるというプロセスを実装していきます。 なぜ、わざわざ画像をAPI Gatewayに渡さずに、先にS3に置いておくかというと、API Gatewayは転送データ量に対しても料金が発生するからです(Amazon API Gateway の料金)。コスト最適化の対策です。

実装!

とりまSAMで取り急ぎご挨拶(Hello World)

そもそもLambdaが動くことをセットアップ、確認するために、とりまHello Worldします。
詳細はこちらのQiita記事をご参照ください→(AWS SAM ローカルでいろんな角度からhello worldする

curlでURL叩いてRekognitionする(パラメーターなし)

Lambda

まずはLambdaを編集していきます。 Aws::Rekognition::Client#search_faces_by_imageというメソッドを使います。

require 'json'
# SDK追加
require 'aws-sdk-s3'
require 'aws-sdk-rekognition'

def lambda_handler(event:, context:)
  # この辺は後でcurlでパラメータとして渡してあげたい
  collection_id = 'app-users' # Rekognitionの顔コレクションのid
  bucket        = 'face-images' # the bucket name without 's3://' 
  ok_photo_key   = 'user-code-1/kaku_2013.jpg' # OKになるはずの写真
  ng_key          = 'user-code-2/hyde.jpg' # NGになるはずの写真

  client = Aws::Rekognition::Client.new(
    region:                       ENV['AWS_REGION'],
    access_key_id:         ENV['AWS_ACCESS_KEY_ID'],
    secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  )

  # 顔認証用に送られた顔写真と同じ顔が、顔collectionの中でマッチするものを取得
  response = client.search_faces_by_image({
    collection_id: collection_id,
    face_match_threshold: 95,
    image: {
      s3_object: {
        bucket: bucket, 
        name: ok_key, # ng_keyにすればNGの時の動作を確認できる 
      }, 
    }, 
    max_faces: 5,
  })

    # ログ確認用
  puts response

  # responseの中にface_matchesというのがある(後述)
  face_ids = response.face_matches.map { |f| f.face.face_id }

  result = if face_ids.any?
             :success
           else
             :failure
           end

  {
    statusCode: 200,
    body: {
      message: result,
    }.to_json
  }
end

環境変数セット

Rekognitionのclientをnewするときのcredentials(ENV['AWS_ACCESS_KEY_ID']とか)の渡し方は色々あるようですが、今回はターミナルに変数をセットする方法に落ち着きました。sam local startをするターミナルで、AWSの認証情報をexportします。AWS_SECRET_ACCESS_KEYAWS_ACCESS_KEY_IDAWS_REGIONが必要です。(ドキュメント

exportしたら、printenvコマンドで環境変数を確認できます。

//  認証情報をexport
$ export AWS_SECRET_ACCESS_KEY=XXX AWS_ACCESS_KEY_ID=XXX AWS_REGION=ap-northeast-1

// 環境変数の確認
$ printenv

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  identify_user

  Sample SAM Template for identify_user

Globals:
  Function:
    Runtime: ruby2.7
    Timeout: 30

Resources:
  IdentifyUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: identify_user/
      Handler: app.lambda_handler
      Runtime: ruby2.7
      Events:
        IdentifyUser:
          Type: Api
          Properties:
            Path: /identify_user
            Method: post

curlする

さて、準備ができたのでcurlします。 うまくいくとLambdaからのレスポンスが返ってきます。

curl -X POST http://127.0.0.1:3000/identify_user

// response
{"message":"success"}%

レスポンス

Rekognitionの#search_faces_by_imageのレスポンスはこんな感じになっています。

{
  message:"
    {
      :searched_face_bounding_box=>{
        :width=>0.1847885549068451,
        :height=>0.3748999536037445,
        :left=>0.41051727533340454,
        :top=>0.26663076877593994
      },
      :searched_face_confidence=>99.99877166748047,
      :face_matches=>[{
        :similarity=>99.91470336914062,
        :face=>{
          :face_id=>"ee1b5c92-7201-4826-8813-07c1e8dd9da9",
          :bounding_box=>{
            :width=>0.1847890019416809,
            :height=>0.3749000132083893,
            :left=>0.41051700711250305,
            :top=>0.26663100719451904
          },
          :image_id=>"774f9136-2232-320c-9c3a-5c67f3ed6b7d",
          :external_image_id=>nil,
          :confidence=>99.9988021850586
        }
      }],
      :face_model_version=>"5.0"
    }
  "
}

顔写真プールの中から似ている顔写真を見つけた場合のレスポンスには、face_matchesというキーの中にデータが含まれていて、そこにsimilarity(類似度)やface_idの情報があります。今と8年前の自分の写真で試してみたのですが、顔一致率99.9%以上で返ってきてビックリしましたw

ちなみに、マッチするものが見つからない場合のresponseはこちら。face_matchesの値が空[]になっています。

{
  message: "
    {
      :searched_face_bounding_box=>{
      :width=>0.23569627106189728,
      :height=>0.4746306240558624,
      :left=>0.4260195195674896,
      :top=>0.15645968914031982
      },
      :searched_face_confidence=>99.99295806884766,
      :face_matches=>[],
      :face_model_version=>"5.0"
    }
  "
}

curlでパラメーターも渡しつつURL叩いてRekognitionする

次は1ステップ進んで、APIにパラメーターで渡して同じことをするというのをしたいと思います。

Lambda

require 'json'
require 'aws-sdk-s3'
require 'aws-sdk-rekognition'

def lambda_handler(event:, context:)
  params = JSON.parse(event['body'], symbolize_names: true)

  client = Aws::Rekognition::Client.new(
    region:            ENV['AWS_REGION'],
    access_key_id:     ENV['AWS_ACCESS_KEY_ID'],
    secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  )

  # 顔認証用に送られたimageと同じ顔がcollection_idの中でマッチするものを取得
  response = client.search_faces_by_image({
    collection_id: params[:collection_id],
    face_match_threshold: 95,
    image: {
      s3_object: {
        bucket: params[:bucket], 
        name: params[:ok_photo_key], 
      }, 
    }, 
    max_faces: 5,
  })

  puts response

  face_ids = response.face_matches.map { |f| f.face.face_id }

  result = if face_ids.any?
             :success
           else
             :failure
           end

  {
    statusCode: 200,
    body: {
      message: result,
    }.to_json
  }
end

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  identify_user

  Sample SAM Template for identify_user

Globals:
  Function:
    Timeout: 30
Resources:
  IdentifyUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: identify_user/
      Handler: app.lambda_handler
      Runtime: ruby2.7
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /identify_user
            Method: post

curl(パラメーターも渡す)

curlコマンドの--dataオプションでパラメータを渡してあげます。

curl -X POST --data '{"collection_id":"app-users","bucket":"face-images","ok_photo_key":"user-code-1/kaku_2013.jpg"' http://127.0.0.1:3005/identify_user

{"message":"success"}%  

レスポンス

Lambdaに仕込んでおいたputs responseのログがsam localのサーバーに出ています。

Mounting /Users/username/directory/sam-app/.aws-sam/build/IdentifyUserFunction as /var/task:ro,delegated inside runtime container START RequestId: 93230268-dbd1-451d-9cbb-118c0fff96f0 Version: $LATEST {:searched_face_bounding_box=>{:width=>0.18047305941581726, :height=>0.32837414741516113, :left=>0.3123670816421509, :top=>0.20935456454753876}, :searched_face_confidence=>99.99854278564453, :face_matches=>[{:similarity=>99.91470336914062, :face=>{:face_id=>"ee1b5c92-7201-4826-8813-07c1e8dd9da9", :bounding_box=>{:width=>0.1847890019416809, :height=>0.3749000132083893, :left=>0.41051700711250305, :top=>0.26663100719451904}, :image_id=>"774f9136-2232-320c-9c3a-5c67f3ed6b7d", :external_image_id=>nil, :confidence=>99.9988021850586}}], :face_model_version=>"5.0"} END RequestId: 93230268-dbd1-451d-9cbb-118c0fff96f0 REPORT RequestId: 93230268-dbd1-451d-9cbb-118c0fff96f0 Init Duration: 0.13 ms Duration: 1434.27 ms Billed Duration: 1500 ms Memory Size: 128 MB Max Memory Used: 128 MB No Content-Type given. Defaulting to 'application/json'. 2021-11-11 09:20:06 127.0.0.1 - - [11/Nov/2021 09:20:06] "POST /identify_user HTTP/1.1" 200 - Invoking app.lambda_handler (ruby2.7) Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-ruby2.7:rapid-1.22.0.

face_matchesの中に:similarity=>99.91470336914062と入っています。
無事にローカルで、サーバーレス環境で顔認識ができました!:party:

kakudaisuke

kakudaisuke

IoT