AWS RekognitionのDetectLabels APIを使ってみた

# はじめに
AWSの機械学習サービスであるAWS RekognitionのDetectLabels APIを使ってみました。
簡単に物やシーンの識別が出来るみたいなので簡単な画像抽出アプリを作成・使用してみました。

# AWS Rekognitionとは？
AWSが提供している機械学習サービスで、画像分析やビデオ分析等の画像認識が手軽に行うことができます。
具体的には、以下のようなAPIが提供されています。

* DetectLabels API(画像から物体やシーンを検出)
* DetectFaces API(画像から人間の顔の表情やパーツの配置を検出)
* CompareFaces API(2つの顔画像の類似率を算出)
* IndexFaces/SearchFacesByImage API(顔画像にインデックスを貼り付け、検索することが可能)

# DetectLabels APIとは？
> DetectLabels API を利用すると、画像から識別した車、ペット、家具など、数千もの物体にラベルを付け、信頼スコアを取得できる
> 信頼スコアは 0～100 の値で示され、識別結果が正しいかどうかの可能性を意味する
[AWS Rekognitoion Black belt](https://d1.awsstatic.com/webinars/jp/pdf/services/20170329_AWSBlackBelt2017-AmazonAI.pdf)より引用

上記の引用の通り、入力画像からラベリングを行えるAPIで、マネジメントコンソールからも以下の様にラベリングの結果を確認することができます。
3匹の猫がしっかり識別されていることがわかりますね！
![demo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/e121f0d7-1f46-648c-eff5-35bea6847e24.png)
※入力した猫画像は[bixabay](https://pixabay.com/ja/)より取得

DetectLabels APIへのリクエストは、上記の猫画像を入力例に以下の通りで、

```json
{
    "Image": {
        "Bytes": "(入力画像のバイト列)"
    }
}
```

レスポンスとしては、以下のJSONを返します。構造としては、ラベル情報の配列で、ラベル情報は以下の物を持っています。

* ラベル名
* ラベルの信頼度
* ラベルと識別した入力画像の範囲(0.0～1.0)
* そのラベルの親ラベル情報

```json
{
    "Labels": [
        {
            "Name": "Cat",
            "Confidence": 99.57831573486328,
            "Instances": [
                {
                    "BoundingBox": {
                        "Width": 0.369978129863739,
                        "Height": 0.7246906161308289,
                        "Left": 0.17922087013721466,
                        "Top": 0.06359343975782394
                    },
                    "Confidence": 92.53639221191406
                },
                {
                    "BoundingBox": {
                        "Width": 0.3405080735683441,
                        "Height": 0.7218159437179565,
                        "Left": 0.31681257486343384,
                        "Top": 0.14111439883708954
                    },
                    "Confidence": 90.89508056640625
                },
                {
                    "BoundingBox": {
                        "Width": 0.27936506271362305,
                        "Height": 0.7497209906578064,
                        "Left": 0.5879912376403809,
                        "Top": 0.10250711441040039
                    },
                    "Confidence": 90.0565414428711
                }
            ],
            "Parents": [
                {
                    "Name": "Mammal"
                },
                {
                    "Name": "Animal"
                },
                {
                    "Name": "Pet"
                }
            ]
        },
        {
            "Name": "Pet",
            "Confidence": 99.57831573486328,
            "Instances": [],
            "Parents": [
                {
                    "Name": "Animal"
                }
            ]
        },
        {
            "Name": "Kitten",
            "Confidence": 99.57831573486328,
            "Instances": [],
            "Parents": [
                {
                    "Name": "Mammal"
                },
                {
                    "Name": "Cat"
                },
                {
                    "Name": "Animal"
                },
                {
                    "Name": "Pet"
                }
            ]
        },
        {
            "Name": "Animal",
            "Confidence": 99.57831573486328,
            "Instances": [],
            "Parents": []
        },
        {
            "Name": "Mammal",
            "Confidence": 99.57831573486328,
            "Instances": [],
            "Parents": [
                {
                    "Name": "Animal"
                }
            ]
        }
    ],
    "LabelModelVersion": "2.0"
}
```


# 作ってみる
## 作るもの
S3バケットにアップロードした画像からラベリングを行い、画像内のラベリングされた範囲を抽出し、他のS3バケットにその抽出した画像をタグ「ラベル名」=「信頼度」を付与して出力する物を作ってみました。

## 構成図
![flow.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/1c31946d-ed73-c227-4fda-214380d9fb42.jpeg)
①.バケット「rekognition-test-20200530」に画像ファイルをアップロード
②.バケットのファイル作成イベントをトリガーにLambda「RecognitionTest」が起動
③.Lambda「RecognitionTest」はS3にアップロードした画像ファイルを入力にDetectLabel APIを呼び出す
④.Lambda「RecognitionTest」はDetectLabel APIのレスポンスを元に、
　1. ラベルに対象範囲指定がついている物に対して、その対象範囲をアップロードした画像から抽出
　2. 抽出した画像にS3のタグ「ラベル名」=「ラベルの信頼値」を付与
　3. バケット「rekognition-test-20200530-output」に出力

## 各AWSリソースの設定
### S3

| バケット名               | 設定 |
|:--------------------------------|:------------|
| rekognition-test-20200530       | ・バケットの作成<br>・S3にファイル作成イベント時のLambda「RecognitionTest」へのトリガー設定 |
| rekognition-test-20200530-output     | ・バケットの作成<br>・Lambda「RecognitionTest」のIAM Roleに書き込み権限を与えるバケットポリシー追加 |

### Lambda Layer
画像の抽出にPillowを使用しているため、[こちらのブログ](https://techblog.recochoku.jp/5659)を参考にLambda Layerを登録しました。
1.Amazon LinuxのEC2を起動
2.EC2上にPillowをインストール
3.Pillowをインストールしたフォルダをzip
4.zipファイルをダウンロードして、そのzipファイルをLambda Layerに登録
※ 今回の使ってみた記事ではPython2.7を使用しているため、pip install pillowでインストールしています。

### Lambda
ランタイム：python2.7

```python:lambda_function.py
# coding: utf-8
import json
import boto3
from PIL import Image
import uuid
from io import BytesIO


def lambda_handler(event, context):
    # イベント発生したS3とオブジェクト取得
    s3 = boto3.client('s3')
    # イベント発生したバケット名
    bucket = event['Records'][0]['s3']['bucket']['name']
    # イベント発生したオブジェクトキー
    photo = event['Records'][0]['s3']['object']['key']
    try:
        # S3イベント発生した画像ファイル取得
        target_file_byte_string = s3.get_object(Bucket=bucket, Key=event['Records'][0]['s3']['object']['key'])['Body'].read()
        target_img = Image.open(BytesIO(target_file_byte_string))
        # 画像ファイルの幅と高さ取得
        img_width, img_height = target_img.size
        # Rekognitionクライアント
        rekognition_client=boto3.client('rekognition')
        # DetectLabels API呼び出しとラベリング結果取得
        response = rekognition_client.detect_labels(Image={'S3Object':{'Bucket':bucket,'Name':photo}}, MaxLabels=10)
        for label in response['Labels']:
            # 範囲が指定されているラベルに対して、画像の抽出とS3への出力を行う
            for bounds in label['Instances']:
                box = bounds['BoundingBox']
                # 画像の抽出範囲を決定
                target_bounds = (box['Left'] * img_width, 
                                box['Top'] * img_height,
                                (box['Left'] + box['Width']) * img_width,
                                (box['Top'] + box['Height']) * img_height)
                # 画像の抽出
                img_crop = target_img.crop(target_bounds)
                imgByteArr = BytesIO()
                img_crop.save(imgByteArr, format=target_img.format)
                # S3のオブジェクトタグ指定
                tag = '{0}={1}'.format(label['Name'], str(label['Confidence']))
                # S3への出力
                s3.put_object(Key='{0}.jpg'.format(uuid.uuid1()), 
                            Bucket='rekognition-test-20200530-output', 
                            Body=imgByteArr.getvalue(),
                            Tagging=tag)
    except Exception as e:
        print(e)
    return True
```

# 使ってみた！
* 入力画像

![animal5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/663b4a19-4159-3a42-0c0c-24467628f002.jpeg)
※[bixabay](https://pixabay.com/ja/)より

上記の入力画像(ファイル名はanimal5.jpg)をS3にアップロードしてみます。
![demo2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/08b16683-24ab-461d-5b76-16d086f46b89.png)
アップロードが完了しました。
![demo3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/947c408a-829b-d8ef-61d9-8e12a99bb5ff.png)
少し待って、S3に抽出した画像が出力されました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/63a2d87c-ad1c-837d-e589-25bfe05db645.png)
画像のタグを1つ確認してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/4cf30055-32b2-bfe5-d2e3-d299070e9727.png)
しっかり、タグ付けされてますね。
S3に出力された画像の中身をそれぞれ確認してみます。

* 012a3206-a2fe-11ea-a36e-3245b8e10005.jpg
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/c5f98b18-6159-b20e-e2fb-3fbddd99eef1.png)

* 0157da26-a2fe-11ea-a36e-3245b8e10005.jpg
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/22167e86-5639-9ad5-e1f7-272bdfc3c24e.png)

* 01670c80-a2fe-11ea-a36e-3245b8e10005.jpg
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/a99f6447-b825-4ad1-8d02-c09787b4de66.png)

無事に抽出できました！

# 参考資料
[AWS Rekognitoion概要](https://aws.amazon.com/jp/rekognition/)
[AWS Rekognitoion Black belt](https://d1.awsstatic.com/webinars/jp/pdf/services/20170329_AWSBlackBelt2017-AmazonAI.pdf)