ハッカソン参加時の備忘録 ～ 第６回：【Python】AWS LambdaとAWS Rekoginitionを連携させてみよう ～

こんにちは。ITエンジニアのきゅうです。
前回、AWSのLambdaとAWSのS3（ストレージサービス）を連結させるサンプルを備忘録したので、今回は当方が良く使うAWSのAIサービスの一つであり、簡単に画像解析を行うことが出来るサービス『Rekoginition』と連結したいと思います。

今回作成するシステム構成図です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/7c64153a-d89e-581e-df58-a8148b63e814.png)

※前回の備忘録に記載した『S3』が登場しております。
　これはRekognitionに読み込ませる画像をS3などに格納しておく必要がある為です。
　ですので、今回の記事では以下の記事の設定がしてあることが前提となります。

https://qiita.com/kyuu2197/items/201d33437ba33665f840


# Amazon Rekoginitionについて
改めて、Rekognitionについておさらいします。
Amazon Rekognitionは、画像や動画の分析ができるAIクラウドサービスです。

2024年2月現在、主に以下の8つの機能が御座います。
※AWSは日々更新がかかっているため、将来的に機能拡張・縮小があるかもしれませんので、現時点での機能紹介になります。
<dl>
  <dt>画像分析（Amazon Rekognition Image）</dt>
  <dd>1. シーンや物体の検出</dd>
  <dd>2. 顔の検索や認証</dd>
  <dd>3. 顔の検出や分析</dd>
  <dd>4. 有名人の認識</dd>
  <dd>5. 安全でないコンテンツの検出</dd>
  <dd>6. テキストの検出</dd>
  <dd>7. カスタムラベル</dd>
  <dd>8. 個人用保護具（PPE）の検出</dd>

  <dt>動画分析（Amazon Rekognition Video）</dt>
  <dd>1. リアルタイムのストリーミングビデオイベントの分析</dd>
  <dd>　1.1. ラベル検出</dd>
  <dd>　1.2. 顔検索</dd>
  <dd>2. 保存されたビデオ分析</dd>
  <dd>　2.1. 物体、シーン、アクティビティ検出</dd>
  <dd>　2.2. コンテンツのモデレーション</dd>
  <dd>　2.3. テキスト検出</dd>
  <dd>　2.4. 有名人の認識</dd>
  <dd>　2.5. 顔検出と分析</dd>
  <dd>　2.6. 顔検索</dd>
  <dd>　2.7. 人物の動線の検出</dd>

  <dt>Amazon Rekognitionカスタムラベル</dt>
  <dd>1. ビジネスニーズに合わせた画像の物体やシーンの特定</dd>

  <dt>Amazon Rekognition Face Liveness</dt>
  <dd>1. カメラを出し抜くなりすましを検出</dd>
</dl>

上記の主な使い方は後で記載していきますので、まずはRekognitionへの接続をしていきましょう。

## Rekoginitionの利用料金について
Rekognitionの上記の内、『Image』と『Video』の料金について見ていきます。
<dl>
  <dt>画像分析（Amazon Rekognition Image）</dt>
  <dd>無料利用枠が適応されます。その間、1 か月あたり 5,000 画像を分析可能。</dd>
  <dd>それ以上、利用する場合は画像数やリージョンにより異なった料金がかかります。</dd>

  <dt>動画分析（Amazon Rekognition Video）</dt>
  <dd>無料利用枠が適応されます。その間、1 か月あたり 1,000分の動画を分析可能。</dd>
  <dd>それ以上、利用する場合は画像数やリージョンにより異なった料金がかかります（アーカイブ動画の場合1分につき0.13USD、ライブ動画の場合、1分につき0.15USD）。</dd>
</dl>

# Amazon Rekognitionに接続してみよう。
では、早速Rekognitionを使ってみましょう！！

## １．Rekognitionは事前設定不要
```
使用するサービス：Rekognition
使用する機能：―
```
ということで、下記がRekognitionのコンソール画面です。
S3と違って、『●●を作成』のようなボタンがありません。
そうなんです！！
「カスタムモデレーション」や「一括分析」を使わない限りは、事前にRekognitionで設定する必要はないんです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/c212bcad-524f-4c50-c8b0-78607708219d.png)

## ２．ロールの作成
```
使用するサービス：IAM
使用する機能：ロール
```
### 『ロールを作成』ボタンを押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/415e323c-e976-0edf-c6ab-ae2b3005a2ec.png)


### ユースケースで「Lambda」を選択
「信頼されたエンティティタイプ」で「AWS のサービス」を、
「ユースケース」で「Lambda」を選択し、「次へ」を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/554c6790-16ed-cb8e-6878-fdbf5c37d791.png)


### 許可ポリシーでRekognitionを選択
「許可ポリシー」の検索ボックスに「Rekoginition」と入力し、
「AmazonRekognitionFullAccess」と「AWSLambdaRekognitionReadOnlyAccessExecutionRole」、「AWSLambdaRekognitionWriteOnlyAccessExecutionRole」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/d83e6f1d-bb6d-89e2-e327-465b5900aa94.png)


### 許可ポリシーでS3を選択
「許可ポリシー」の検索ボックスから「Rekoginition」を消して「S3」と入力し、
「AmazonS3FullAccess」と「AWSLambdaS3ExecutionRole」を選択し。
「次へ」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/f9652658-c3f5-62b1-cbe8-944c7e49af46.png)


::: note info
S3の時も記載したが、基本的にハッカソンであれば「FullAccess」「Execution」が付くポリシーをチェックすれば問題ない。
※通常のプロジェクトでFullAccessとかつけてはダメです。。。
:::

::: note warn
ここではRekognitionのロールだけでなく、S3のロールも設定している。
理由としては、Rekognitionで分析する画像をS3に格納しておく想定だからだ。
なので、分析する画像がS3に格納しない場合、S3のロールは不要です。
:::



### ロール名を任意でつける
ロール名を任意でつけて、一番下の『ロールを作成』を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/1a08489e-d6b8-38ae-95cc-da4fc418c385.png)


## ３．関数を作成する
```
使用するサービス：Lambda
使用する機能：関数
```

### 関数の設定をする
Lambdaのコンソールから『関数の作成』をクリックし、
設定画面に画面キャプチャーのように登録する。
そして、一番下の「関数の作成」をクリックします。

::: note warn
デフォルトの実行ロールの変更では「既存のロールを使用する」を選択し、上で選択したロールを選びます。
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/03bd2eed-febd-9e6b-dae6-6d55052ac2bb.png)

### ４．S3に画像ファイルをアップロードしましょう。
事前にS3にバケットを作成し、画像ファイルをアップロードしましょう。
ここでは割愛しますが、当方が記載した以下の記事を参考にしていただけると幸いです。

https://qiita.com/kyuu2197/items/201d33437ba33665f840

## ５．Lambdaのソースをコーディングする
```
使用するサービス：Lambda
使用する機能：関数
```
### ソースコードのコピペ
```diff_python
-import json
-
-def lambda_handler(event, context):
-    # TODO implement
-    return {
-        'statusCode': 200,
-        'body': json.dumps('Hello from Lambda!')
-    }

+import json
+import boto3
+
+def lambda_handler(event, context):
+    
+    ### AWSサービスのインスタンス化 ###
+    rekognition = boto3.client('rekognition')
+    s3 = boto3.resource('s3')
+    
+    image = None
+    try:
+        bucket = 'test-s3-2197-bucket'
+        key = 'test3.png'
+        s3_object = s3.Object(bucket, key)
+        image = s3_object.get()['Body'].read()
+        
+        response = rekognition.detect_labels(Image={'Bytes': image})
+        lambda_response = {
+            "statusCode": 200,
+            "body": json.dumps(response)
+        }
+        
+        labels = [label['Name'] for label in response['Labels']]
+        print("Labels found:")
+        print(labels)
+    
+    except Exception as e:
+        error_message = "Couldn't analyze image: " + e.response['Error']['Message']
+        lambda_response = {
+           'statusCode': 400,
+           'body': {
+               "Error": e.response['Error']['Code'],
+               "ErrorMessage": error_message
+           }
+       }
+    
+    return {
+        'statusCode': 200,
+        'body': json.dumps('Hello from Lambda!')
+    }
```
↓↓↓　コピー用です　↓↓↓
```python
import json
import boto3

def lambda_handler(event, context):
    
    ### AWSサービスのインスタンス化 ###
    rekognition = boto3.client('rekognition')
    s3 = boto3.resource('s3')
    
    image = None
    try:
        bucket = 'test-s3-2197-bucket'
        key = 'test3.png'
        s3_object = s3.Object(bucket, key)
        image = s3_object.get()['Body'].read()
        
        response = rekognition.detect_labels(Image={'Bytes': image})
        lambda_response = {
            "statusCode": 200,
            "body": json.dumps(response)
        }
        
        labels = [label['Name'] for label in response['Labels']]
        print("Labels found:")
        print(labels)
    
    except Exception as e:
        error_message = "Couldn't analyze image: " + e.response['Error']['Message']
        lambda_response = {
           'statusCode': 400,
           'body': {
               "Error": e.response['Error']['Code'],
               "ErrorMessage": error_message
           }
       }
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

```

### テストの実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/e4be6c4f-954c-dcc8-ba58-8b7fc526463d.png)

### タイムアウトエラーが発生！！
多分、以下のようなエラーが発生します。
理由は、Lambdaのタイムアウト設定がデフォルト3秒になっているからです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/0698ea64-e408-3ef8-0f53-c90287e23358.png)

なので、タイムアウト設定を延長させましょう。
```
使用するサービス：Lambda
使用する機能：関数
タブ：設定
リスト：一般設定
⇒『基本設定を編集』が開ければOKです。
```
以下の画面より「タイムアウト」を1分に設定してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/a00bf326-ea34-d82b-4d75-a65fa747b070.png)

そして、再度実行します。
はい、今度は成功しました！！
『Label found』で画像のLabelが取れてきてますね！！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/57e3f434-79bc-67ce-a444-d81128e5da73.png)

# ソースコードの解説
それではソースコードの解説に入っていきたいと思いますが、
以下で話した内容は省きますので、「S3」や「boto3.client」、「boto3.resource」について知りたい方は以下の記事を参照ください。

https://qiita.com/kyuu2197/items/201d33437ba33665f840

今回は以下のような写真をRekognitionに読み込ませている。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/066d861f-fde5-195f-3c6d-76e8e1acadfc.png">

それを下記のソースがRekognitionのImageの内、『detect_labels』メソッドの呼び出しとなる。
```python
rekognition.detect_labels(Image={'Bytes': image})
```
Rekognitionのメソッドの種類については、他の備忘録でまとめたいと思います。

すると、以下のようなJSONデータがresponseデータに入ってくる。

```JSON
{'Labels': [{'Name': 'Face', 'Confidence': 99.99978637695312, 'Instances': [], 'Parents': [{'Name': 'Head'}, {'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Head', 'Confidence': 99.99978637695312, 'Instances': [], 'Parents': [{'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Person', 'Confidence': 99.99978637695312, 'Instances': [{'BoundingBox': {'Width': 0.44205889105796814, 'Height': 0.4994780719280243, 'Left': 0.556955099105835, 'Top': 0.0010741690639406443}, 'Confidence': 99.36341857910156}, {'BoundingBox': {'Width': 0.7352738976478577, 'Height': 0.49544477462768555, 'Left': 0.17851921916007996, 'Top': 0.5045552253723145}, 'Confidence': 98.16604614257812}, {'BoundingBox': {'Width': 0.5990684628486633, 'Height': 0.5018318295478821, 'Left': 0.021479398012161255, 'Top': 0.001611444284208119}, 'Confidence': 97.99974822998047}], 'Parents': [], 'Aliases': [{'Name': 'Human'}], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Photography', 'Confidence': 99.99978637695312, 'Instances': [], 'Parents': [], 'Aliases': [{'Name': 'Photo'}], 'Categories': [{'Name': 'Hobbies and Interests'}]}, {'Name': 'Portrait', 'Confidence': 99.99978637695312, 'Instances': [], 'Parents': [{'Name': 'Face'}, {'Name': 'Head'}, {'Name': 'Person'}, {'Name': 'Photography'}], 'Aliases': [], 'Categories': [{'Name': 'Hobbies and Interests'}]}, {'Name': 'Accessories', 'Confidence': 99.78221893310547, 'Instances': [], 'Parents': [], 'Aliases': [{'Name': 'Accessory'}], 'Categories': [{'Name': 'Apparel and Accessories'}]}, {'Name': 'Glasses', 'Confidence': 99.78221893310547, 'Instances': [{'BoundingBox': {'Width': 0.3125612139701843, 'Height': 0.15536221861839294, 'Left': 0.11858267337083817, 'Top': 0.15961448848247528}, 'Confidence': 98.89239501953125}], 'Parents': [{'Name': 'Accessories'}], 'Aliases': [], 'Categories': [{'Name': 'Apparel and Accessories'}]}, {'Name': 'Boy', 'Confidence': 99.36341857910156, 'Instances': [{'BoundingBox': {'Width': 0.44205889105796814, 'Height': 0.4994780719280243, 'Left': 0.556955099105835, 'Top': 0.0010741690639406443}, 'Confidence': 99.36341857910156}], 'Parents': [{'Name': 'Male'}, {'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Child', 'Confidence': 99.36341857910156, 'Instances': [{'BoundingBox': {'Width': 0.44205889105796814, 'Height': 0.4994780719280243, 'Left': 0.556955099105835, 'Top': 0.0010741690639406443}, 'Confidence': 99.36341857910156}], 'Parents': [{'Name': 'Person'}], 'Aliases': [{'Name': 'Kid'}], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Male', 'Confidence': 99.36341857910156, 'Instances': [{'BoundingBox': {'Width': 0.44205889105796814, 'Height': 0.4994780719280243, 'Left': 0.556955099105835, 'Top': 0.0010741690639406443}, 'Confidence': 99.36341857910156}, {'BoundingBox': {'Width': 0.5990684628486633, 'Height': 0.5018318295478821, 'Left': 0.021479398012161255, 'Top': 0.001611444284208119}, 'Confidence': 97.99974822998047}], 'Parents': [{'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Body Part', 'Confidence': 98.52674102783203, 'Instances': [], 'Parents': [], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Finger', 'Confidence': 98.52674102783203, 'Instances': [], 'Parents': [{'Name': 'Body Part'}, {'Name': 'Hand'}, {'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Hand', 'Confidence': 98.52674102783203, 'Instances': [], 'Parents': [{'Name': 'Body Part'}, {'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Happy', 'Confidence': 98.24759674072266, 'Instances': [], 'Parents': [{'Name': 'Face'}, {'Name': 'Head'}, {'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Expressions and Emotions'}]}, {'Name': 'Adult', 'Confidence': 98.16604614257812, 'Instances': [{'BoundingBox': {'Width': 0.7352738976478577, 'Height': 0.49544477462768555, 'Left': 0.17851921916007996, 'Top': 0.5045552253723145}, 'Confidence': 98.16604614257812}, {'BoundingBox': {'Width': 0.5990684628486633, 'Height': 0.5018318295478821, 'Left': 0.021479398012161255, 'Top': 0.001611444284208119}, 'Confidence': 97.99974822998047}], 'Parents': [{'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Female', 'Confidence': 98.16604614257812, 'Instances': [{'BoundingBox': {'Width': 0.7352738976478577, 'Height': 0.49544477462768555, 'Left': 0.17851921916007996, 'Top': 0.5045552253723145}, 'Confidence': 98.16604614257812}], 'Parents': [{'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Woman', 'Confidence': 98.16604614257812, 'Instances': [{'BoundingBox': {'Width': 0.7352738976478577, 'Height': 0.49544477462768555, 'Left': 0.17851921916007996, 'Top': 0.5045552253723145}, 'Confidence': 98.16604614257812}], 'Parents': [{'Name': 'Adult'}, {'Name': 'Female'}, {'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Man', 'Confidence': 97.99974822998047, 'Instances': [{'BoundingBox': {'Width': 0.5990684628486633, 'Height': 0.5018318295478821, 'Left': 0.021479398012161255, 'Top': 0.001611444284208119}, 'Confidence': 97.99974822998047}], 'Parents': [{'Name': 'Adult'}, {'Name': 'Male'}, {'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'People', 'Confidence': 93.36598205566406, 'Instances': [], 'Parents': [{'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Smile', 'Confidence': 88.35812377929688, 'Instances': [], 'Parents': [{'Name': 'Face'}, {'Name': 'Happy'}, {'Name': 'Head'}, {'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Expressions and Emotions'}]}, {'Name': 'Laughing', 'Confidence': 70.95403289794922, 'Instances': [], 'Parents': [{'Name': 'Face'}, {'Name': 'Happy'}, {'Name': 'Head'}, {'Name': 'Person'}], 'Aliases': [{'Name': 'Laugh'}], 'Categories': [{'Name': 'Expressions and Emotions'}]}, {'Name': 'Art', 'Confidence': 57.981483459472656, 'Instances': [], 'Parents': [], 'Aliases': [], 'Categories': [{'Name': 'Hobbies and Interests'}]}, {'Name': 'Collage', 'Confidence': 57.981483459472656, 'Instances': [], 'Parents': [{'Name': 'Art'}], 'Aliases': [], 'Categories': [{'Name': 'Hobbies and Interests'}]}, {'Name': 'Clothing', 'Confidence': 57.55129623413086, 'Instances': [], 'Parents': [], 'Aliases': [{'Name': 'Apparel'}], 'Categories': [{'Name': 'Apparel and Accessories'}]}, {'Name': 'T-Shirt', 'Confidence': 57.55129623413086, 'Instances': [], 'Parents': [{'Name': 'Clothing'}], 'Aliases': [], 'Categories': [{'Name': 'Apparel and Accessories'}]}, {'Name': 'Mouth', 'Confidence': 55.66556930541992, 'Instances': [], 'Parents': [{'Name': 'Body Part'}, {'Name': 'Person'}], 'Aliases': [{'Name': 'Lip'}], 'Categories': [{'Name': 'Person Description'}]}, {'Name': 'Teeth', 'Confidence': 55.66556930541992, 'Instances': [], 'Parents': [{'Name': 'Body Part'}, {'Name': 'Mouth'}, {'Name': 'Person'}], 'Aliases': [], 'Categories': [{'Name': 'Person Description'}]}], 'LabelModelVersion': '3.0', 'ResponseMetadata': {'RequestId': '9190209d-53af-44f2-99e4-7971e7a0c7ad', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': '9190209d-53af-44f2-99e4-7971e7a0c7ad', 'content-type': 'application/x-amz-json-1.1', 'content-length': '6655', 'date': 'Tue, 20 Feb 2024 14:20:55 GMT'}, 'RetryAttempts': 0}}
```
このJSONデータを
```python
labels = [label['Name'] for label in response['Labels']]
        print("Labels found:")
        print(labels)
```
が、JSONデータをループ分で回しながら、'Name'ラベルだけを抽出しているのである。


はい。
ということで、今回はRekognitionの概要と使い方を見てきました。
次はRekognitionのメソッドの種類をまとめていきたいと思います。

最後までお読み頂きありがとうございました。