貴様の幸せ度合いを数えろ ～ハピネススカウターを実際に作ってみた～


# 幸せ戦闘力を計測するAWS×LINE Bot で作ってみた

## はじめに

### 開発に至るまでの経緯

実際に作ろうか否かの話は今年、2021年4月にありました。
ことの発端は[こちらの記事](https://qiita.com/keeey999/items/7ce7c4bc342ca2ec7319)です。
作ろうと思いましたが当時はLINE Bot の知見がなく作るにもどこから作れば良いか分かりませんでした。

ですが、今年はCloudTech と LINE Developers とのコラボイベントを2回開催して
その中でLINE Botの作り方をおおむね理解できたのでそれじゃあ挑戦してみようという風になったわけです。

ちなみに今回のLINE Botは実際に友達の結婚式があるということもあり
タイミングも良かったので作ってみたということもあります。(むしろ、これが主な要因？)

### 何を作ったか - 2行で説明

Amazon Rekognition(AWSのサービス)で表情を分析して
表情から幸せ度合いを測定するLINE Botを作りました。

### 完成品の動作

犬の幸せ戦闘力
![Screenshot_20211217-163445.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/8340ac46-b6ac-b973-9a70-7a9b2cf74cd1.png)

山田の幸せ戦闘力
![Screenshot_20211217-163423.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/3105739d-be53-357c-008f-2ce24dde23c3.png)

れい先生の幸せ戦闘力
![Screenshot_20211217-163551.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/4c478853-a17a-fff4-d589-8cd051572c83.png)

猫の幸せ戦闘力
![Screenshot_20211217-163518.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/4e262ff5-b82f-af8f-c52c-7ec91540df2a.png)

戦闘力の度合い
れい先生 >>>>>>>>>>>> 超えられない壁 >>>>>>>>>>>> 山田 > 犬 > 猫

ちなみに顔と認識されない画像については顔を認識できないので「顔がありません！」と表示します。
![Screenshot_20211217-163733.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/8296cbf0-e1b5-fbec-f608-0fac750b5020.png)


具体的な作り方と利用サービスの説明は以下

## Amazon Rekognitionとは

> 機械学習を使用して画像と動画の分析を自動化する

[Amazon Rekognition](https://aws.amazon.com/jp/rekognition/)

要するに機械的に画像や動画を分析して分析結果を返すというスグレモノ
今回はこのAmazon Rekognitionの機能のうち表情判定を利用

## さっそく作ってみる

## LINE のアカウントで MessagingAPI の Bot を作成

### ログイン

[LINE Developer Console](https://developers.line.biz/ja/) にログインします。

![console_login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/f2823baa-c249-a971-7018-d1f2c3c64937.png)

「LINE アカウントにログイン」をクリック

![console_login_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/a9271ff7-d2c3-2593-3f29-00f4020ba319.png)

LINE のユーザ ID とパスワードを入力して「ログイン」をクリック

![console_login_3-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/19d0de15-bb8e-0a1b-d89c-0dd02ac0b9ae.png)

※既にログイン済みの方は以下のように表示されます。例

![console_login_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/e5011ec5-9b9c-de77-5b36-b1fd44eeda49.png)

### プロバイダの作成

ログインに成功しましたら「プロバイダ」を作成します。
作成をクリック

![provider_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/789b93c6-a338-ea37-d446-7589ba2e8f34.png)

新規のプロバイダ名には「cloud_tech」と入力して作成をクリック

![provider_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/518ad2fc-d64f-ed98-561c-066d62e323cd.png)

作成が完了すると以下の画面に遷移するので「Messaging API 」をクリック

![provider_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/96711c18-9b51-5f8b-6a8a-162d0e388b5e.png)

### 新規チャネルの作成

プロバイダの管理画面から「Messaging API 」をクリック 後はチャネルを作成します。

チャネルは以下のように設定します。

| 設定項目       | 値                                |
| :------------- | :-------------------------------- |
| チャネルの種類 | Messaging API                     |
| プロバイダー   | cloud_tech                     |
| チャネル名     | marriage-bot                        |
| チャネルの説明 | ハピネススカウター！！ |
| 大業種         | ウェブサービス                    |
| 小業種         | ウェブサービス(その他)            |

![line_dc_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/72b87c03-f720-83de-4ccc-b6ccc302bdfb.png)

![line_dc_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/c5a94fc5-b6cb-76d6-6f9b-ea45eddcd193.png)

規約同意にチェックを入れて「作成」をクリック

![line_dc_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/d464e354-37e9-6cfd-b2b0-2224a744f478.png)

作成内容を聞かれるので確認して「OK」をクリック

![line_dc_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/e09b1ca1-e6dc-1b69-b00b-5d5bbf213136.png)

情報利用に関する同意について聞かれるので「同意する」をクリック

![line_dc_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/716096b8-365f-de59-1794-2ced143623f2.png)

※「同意する」をクリック後にサーバエラーが出る場合がありましたが、私の場合はチャネル名に「LINE」という文字を入れていた為エラーが出てしまいました。他の名前を入力することで解決しました。

![line_dc_6_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/cd38dedd-d688-2ae6-dc18-efa563f7eb04.png)

チャネルが完成すると画面遷移して以下の画面が表示されます。

![line_dc_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/084e0efe-5ba9-bde2-32ae-e4c8a961804e.png)

### 「チャネルアクセストークン」をコピー

外部から LINE を操作する為の「チャネルアクセストークン」を発行します。

「Messaging API 設定」をクリック

![line_dc_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/3f2688db-bbae-6f67-0dba-1c4bd9de10e2.png)

発行ボタンをクリック

![line_dc_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/15ebb967-b39c-fd60-1a63-cce6b2d10f7d.png)

長い文字列が表示されるので長い文字列のすぐ右にあるコピーボタンをクリック

![line_dc_9-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/5bc23aff-b0bc-5b0c-2f0b-8e1dcd07e11c.png)


チャネルアクセストークンをメモ帳に貼り付け

### 応答メッセージと挨拶メッセージをオフにする

Messaging API には友達追加時やメッセージを送ったときにメッセージを返信するという機能があります。
この機能は今回利用しないのでオフにします。

「Messaging API 設定」から「編集」をクリック
するとLINE Official Account Manager の画面に遷移

![line_dc_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/0df646a5-2fd2-125c-d3d8-98c8e3f03805.png)

デフォルトでは以下のようにオンになっていますので

![line_dc_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/58c06612-cebe-540f-2c42-eee2cb4415c0.png)

それぞれオフにしましょう。

![line_dc_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/86568590-a872-e0b2-4efa-64bff363e10c.png)

オフにできましたらLINE Official Account Manager の画面 の画面を閉じます。
LINE Developers の画面で画面を更新して応答メッセージとあいさつメッセージが無効になっていることを確認しましょう。

![line_dc_13-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/0d802ab4-c565-51bd-5335-e990f01a91c7.png)

ここまでで一旦、LINE側の準備は完了です。API GatewayでAPIのエンドポイントを作成した後に
LINE Bot の設定にWebhookの設定を行う必要がある為、LINE Developersの画面は閉じないでおきましょう。

## AWS の構成

### 構成図

少々複雑に見えるかもしれませんが以下の構成図が今回のLINE Botの構成です。

![aws.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/bba14923-771c-a464-5ae9-31cd87710e3a.png)


## 事前準備

コマンドを実行して必要なファイルをローカルにコピーします。

```bash 
git clone https://github.com/ymd65536/happiness_scouter.git
cd marriage-bot
python -m pip install LINE-bot-sdk -t .
cd ..
cd reko_api
python -m pip install LINE-bot-sdk -t .
```

次に
marriage-bot フォルダを圧縮します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/fe92bb87-6e02-554a-689d-40f9bcdc32f7.png)

圧縮に成功したら`marriage-bot.zip`として保存します。

reko_api フォルダを圧縮します。こちらも全選択で圧縮して保存します。圧縮後の名前は`reko_api.zip`となるようにしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/63163f52-bfd6-6f79-536b-bff805bcb089.png)

圧縮時にフォルダの階層を二重にしないところがポイントです。

NGな例

``` 
reko_api.zip
  |- reko_api
   |- reko_api
    |- lambda_function.py
```

## 画像を受信する機能を構成する

### 受信用Lambda - S3バケットの作成

バケット名が `marry-bot-image` となるようにS3バケットを作成する。

S3のバケット名は全世界でユニークである必要がある為、オリジナルの名前を付けるようにしてください。
なお、このバケット名は受信用Lambdaの作成時に環境変数に入れて利用しますのでどこかに記録しておいてください。

AWSマネジメントコンソールからS3 マネジメントコンソール を開きます。バケット作成をクリック
以下のように設定します。

![aws_s3_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/d4a9b007-3fe7-cd00-b3fb-7ef464e1eccd.png)
![aws_s3_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/dabc3791-6ea9-084c-958b-40cb1397655f.png)
![aws_s3_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/1d06c49b-fc08-04c0-f711-172e95609a72.png)
![aws_s3_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/0ec3cdde-cce5-76a5-774c-03d13fc6f290.png)
![aws_s3_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/d1a5734a-15d4-e05a-99ca-8b50890f10b5.png)
![aws_s3_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/c5125798-89f8-1add-682a-de9c5f44e92b.png)
![aws_s3_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/19d139ba-d0a2-af81-93c3-78979d9778d4.png)

最後に作成をクリック

### 受信用Lambda - DynamoDBテーブルの作成

LINE からメッセージを保存するテーブルの名前が`LineMessages`となるようにDynamoDBでテーブルを作成します。
DynamoDB テーブルに以下のようなKey-Value 形式のデータが入る想定です。
※自身のユーザIDなど機密情報が含まれている為、一部伏せています。

```json
{
 "image_id": "15197815743669",
 "user_id": "<画像送り主のユーザID>",
 "destination": "<Destination>",
 "events": [
  {
   "mode": "active",
   "replyToken": "<reply token>",
   "source": {
    "type": "user",
    "userId": "<画像送り主のユーザID>"
   },
   "type": "message",
   "message": {
    "type": "image",
    "contentProvider": {
     "type": "line"
    },
    "id": "15197815743669"
   },
   "timestamp": 1638707995302
  }
 ]
}
```

AWSマネジメントコンソールからAmazon DynamoDB マネジメントコンソールを開きます。
テーブルの作成をクリック

以下のように設定してください。

パーティションキー：image_id
ソートキー：user_id

![dynamodb_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/b61a27c6-87a9-50d0-bcd1-15cdca491d39.png)

ここはデフォルト設定で問題ありません。

![dynamodb_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/89af869c-bce4-6333-db8e-3d536d92db40.png)

キー：bot
バリュー：db

![dynamodb_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/fa197c27-e40f-67a8-19a6-0f8a35d6d4ff.png)

### 受信用Lambda - IAMポリシーの作成

以下のポリシーを満たすIAMポリシー、`marriage-bot-iam`を作成する為
AWSマネジメントコンソールからIAM マネジメントコンソールを開きます。

今回利用するポリシーは主に3つです。

- S3 PutObject
- CloudWatch logs (PutLogEvents)
- DynamoDB PutItem

Lambdaの関数名は`marriage-bot` としました。
※XXXXXXXXXXX はご自身のAWSアカウントIDに置き換えてください。

ちなみにDynamoDBのテーブル名は`LineMessages`としています。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::marry-bot-image/*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-1:XXXXXXXXXXX:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:XXXXXXXXXXX:log-group:/aws/lambda/marriage-bot:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "dynamodb:PutItem",
            "Resource":"arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXX:table/LineMessages"
        }
    ]
}
```

ポリシーをクリック

![iam_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/3554ab03-6203-3fc6-6658-47032b17e041.jpeg)

ポリシーを作成をクリック

![iam_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/a6dc0bd7-8cb6-ba4d-bac3-0994906628e7.jpeg)

JSON タブをクリック
![iam_3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/bb92d93d-d4e2-804b-f0e3-cc94b4a96bc3.jpeg)

既に記載されているJSONを削除して作成したIAMポリシーをコピペします。
コピペが終わり、エラーが無ければ次のステップへ

![iam_4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/cb069277-9d60-7fcc-53ce-9f5dd721e3a1.jpeg)

キーとバリューを入力します。

キー：bot
値：iam

![iam_5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/6a47304a-0222-fdca-4c0b-e8d346e88bc0.jpeg)

### 受信用Lambda - IAMロールの作成

続いてIAMポリシーからIAMロールを定義する。
IAM マネジメントコンソールからロールをクリック
ロールを作成をクリック

![iam_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/d096a197-1734-8c1a-2931-c94084832d9a.png)

AWSサービスを指定して一般的なユースケースからLambda を選択
次のステップをクリック

![iam_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/6e060e45-16e9-3fcb-b6bf-a27aae104bcd.png)

検索ボックスでポリシーを検索
![iam_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/33ff99bd-8733-74b2-d542-6845d7185b70.png)

先ほど作成したIAMポリシーにチェック
次へをクリック

キーと値をセット
![iam_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/4c2cbe09-da27-0cd1-398d-9e5531c5288f.png)

ロール名を入力、ポリシーを確認してロールの作成をクリック
![iam_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/1c075fe9-4681-35e1-68e6-6821838a3db1.png)

### 受信用Lambdaの作成

Lambda のダッシュボードから関数の作成をクリック
既存のロール`marriage-bot-role`を利用したLambda関数`marriage-bot`を作成します。

一から関数を作成
![lambda_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/ac73e2ae-79eb-8f04-073f-935918689ca5.png)

基本的な情報は以下のように設定してください。

|項目|値|
|:---|:--|
|関数名|marriage-bot|
|ランタイム|Python3.9|
|アーキテクチャ|x86|
|実行ロール|既存のロールを使用する|
|既存のロール|marriage-bot-role|

![lambda_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/90e9e68d-d269-2a7a-02e5-c75dd65c1da1.png)

詳細設定にある二つのチェックボックスが外れていることを確認して関数の作成をクリックします。
![lambda_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/4e346d61-c369-1fb4-fe40-74d130a60485.png)

事前準備で作成したzipファイルをアップロードします。
アップロード元をクリックして`marriage-bot.zip` を指定後
保存をクリックしてアップロードします。
![lambda_4-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/ae691fa1-c8fe-ca6d-fecc-871e8d256203.png)

次にLambda内で利用する環境変数を定義します。
設定から環境変数をクリック
![lambda_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/a5d79814-9f6a-d0fb-98d1-bde66c28b4fa.png)

編集をクリック

![lambda_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/3a8f0204-bf8b-c86b-b7fd-2ca1828b9e69.png)

このLambda 関数で定義する環境変数

|変数名|値|
|:---|:--|
|S3_BUCKET_NAME|marry-bot-image|
|LINE_CHANNEL_ACCESS_TOKEN|LINE Developers Console からコピーしたトークン|
|DYNAMODB_TABLE_NAME|LineMessages|

環境変数の追加をクリックして3つの環境変数を定義します。
![lambda_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/9281803a-20de-0897-7f39-ef4557cc3b59.png)

最後にタイムアウト時間が15秒となるようにLambdaの一般設定からタイムアウト秒を設定します。

一般設定をクリック後、編集をクリック
![lambda_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/1085647e-0669-e841-06ce-c935f8273f20.png)

タイムアウトを15秒に設定して保存をクリック
![lambda_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/4e25ee0f-2a05-8753-f226-7308450a3e46.png)

### API Gateway の作成

Lambda関数`marriage-bot`をWeb APIとして利用できるよう
APIエンドポイントURLを作成します。

AWS マネジメント コンソールからAPI Gateway を開き
APIタイプからプライベートではないほうのREST APIを構築します。
構築をクリック
![api_gateway_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/4e2ae967-679c-5122-3de4-c0c0dc5f447b.png)

プロトコルの選択して新しいAPIを作成します。
新しいAPIおよび名前と説明は以下のように設定してください。

API名：marriage
説明：貴様の幸せ戦闘力を数えろ！
エンドポイントタイプ：リージョン

![api_gateway_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/f2d791f3-e730-02da-795f-83c21e5440a4.png)

Messaging APIのWebhook から利用できるようにエンドポイントを作成します。

まずはメソッドを作成する為にアクションからメソッドの作成をクリック
プルダウンからPOSTを選択

![api_gateway_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/fa26e849-5984-05b9-bd53-fc7e039f9618.png)

POSTリクエスト時に起動するLambda関数がmarriage-bot になるように設定します。

POSTリクエストの設定は以下のように設定してください。

|項目|値|
|:---|:---|
|統合タイプ|Lambda関数|
|Lambdaプロキシの利用|チェックを入れる|
|Lambdaリージョン|ap-northeast-1|
|Lambda関数|marriage-bot|
|デフォルトタイムアウトの使用|チェックを入れる|

![api_gateway_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/7ac87a49-77ae-55bf-61d7-4cc680095653.png)

保存をクリックするとLambdaに権限を追加するか聞かれるのでOKをクリック

次にAPI GatewayのエンドポイントURLをMessagingAPIで利用できるようにAPIをデプロイします。

![api_gateway_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/d859dc4c-79f5-fa99-a141-0ed6b6bf5cfb.png)

デプロイされるステージ名を新しいステージ名に設定します。
他の項目は以下のように設定してください。

|項目|値|
|:---|:---|
|ステージ名|bot|
|ステージの説明|貴様の幸せ戦闘力を数えろ|
|デプロイメントの説明|ハピネススカウター|

![api_gateway_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/2dc4a640-2bb1-a3b7-7a24-dc028b297867.png)

![api_gateway_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/b26e25e2-1330-c954-7374-b1853da3c7c2.png)

設定が終わりましたらデプロイをクリック

画面が遷移してURLの呼び出しが現れますので最後にをURLをコピーしてメモしておきます。

### API GatewayのURLをLINE の Webhookに登録

先ほどコピーしたAPI Gateway で作成したエンドポイントURLをMessagingAPIのWebhookにコピーします。

LINE Developers Console に戻ってWebhook を設定します。編集をクリック
![line_dc_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/58e525fc-50b7-9bbd-f1ad-55fb43abd9fa.png)

API Gateway で作成したエンドポイントURL を貼り付けて更新をクリック

![line_dc_13-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/97fa4fc8-0231-130b-587d-66f48ddd3cd7.png)

画面上に反映されましたら検証をクリックして疎通を確認します。

![line_dc_13-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/da5053ab-f990-530c-91b3-491cf0c22252.png)

問題無ければ、成功というダイアログが表示されます。OKをクリック

![line_dc_13-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/0d4e53e6-4450-533f-f3d5-880295d89fc0.png)

最後にWebhook をオンにします。
![line_dc_13-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/ec86b17d-1f35-ae3e-d378-21be5c5dc61c.png)

## 画像処理の結果を返す機能を構成する

### 返信用DynamoDBテーブルの作成

Amazon Rekognition のデータを保存できるようにDynamoDBでテーブル、`RekoEmotions`を作成します。

DynamoDB テーブルに以下のようなKey-Value 形式のデータが入ります。
※自身のユーザIDなど機密情報が含まれている為、一部伏せています。

```json
{
 "object_id": "<image_id>_<画像送り主のユーザID>.jpg",
 "SAD": "38.500",
 "HAPPY": "549466.500",
 "DISGUSTED": "66.000",
 "CONFUSED": "132.000",
 "CALM": "22.000",
 "user_name": "Kento.Yamada",
 "ANGRY": "55.000",
 "FEAR": "104.500",
 "SURPRISED": "110.000"
}
```

AWSマネジメントコンソールからAmazon DynamoDB マネジメントコンソールを開きます。テーブルの作成をクリック

以下のように設定してください。

テーブル名：LineMessages
パーティションキー：image_id
ソートキー：空白のままでOK
設定：デフォルト設定

![dynamodb_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/100ab853-7e56-5cf2-d4f4-3e155aaef256.png)
![dynamodb_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/1da81f5a-6a5c-6282-f109-534ec02ed5ba.png)

キー：bot
バリュー：db
![dynamodb_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/3a32ef9a-0358-76a3-03b6-e1e181d9cda7.png)

### 返信用LambdaのIAMポリシーの作成

以下のポリシーを満たすIAMポリシー、`reko-api-iam`を作成する為
AWSマネジメントコンソールからIAM マネジメントコンソールを開きます。

利用ポリシーは主に3つ

- S3 GetObject
- CloudWatch logs (PutLogEvents)
- DynamoDB PutItem
- Amazon Rekognition

Lambdaの関数名は`reko_api` としました。
※XXXXXXXXXXX はご自身のAWSアカウントIDに置き換えてください。
※S3バケット名は`marry-bot-image`としていますが、世界で一意である必要がある為、ご自身の環境で読み替えてください。

DynamoDBのテーブル名は`RekoEmotions`としています。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "dynamodb:PutItem",
            "Resource": "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXX:table/RekoEmotions"
        },
        {
            "Sid": "GetImagesMetadata",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::marry-bot-image/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "rekognition:DetectFaces"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-1:XXXXXXXXXXX:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:XXXXXXXXXXX:log-group:/aws/lambda/reko_api:*"
            ]
        }
    ]
}
```

ポリシーをクリック
![iam_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/52a0aa14-d670-a2a4-a499-3e2bad4540cd.jpeg)

ポリシーを作成をクリック
![iam_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/98d2f094-0726-9164-00fe-0d123b3b48d1.jpeg)

JSON タブをクリック
![iam_3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/59baba0d-780e-12c8-42fc-91fc30e888a2.jpeg)

既に記載されているJSONを削除して作成したIAMポリシーをコピペします。
コピペが終わり、エラーが無ければ次のステップへ

![iam_4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/5b85b12d-aa4d-6c48-ae07-5debe7866285.jpeg)

キーとバリューを入力します。
キー：bot
値：iam
![iam_5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/deeda3ef-b199-db1f-ffa9-927ffab397ec.jpeg)

ポリシーの確認画面で名前と説明を入力してポリシーの作成をクリック
![iam_6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/65bfaa3c-7b49-ffb3-784f-986f2233a63d.jpeg)

### 返信用LambdaのIAMロールの作成

続いてIAMポリシーからIAMロール、`reko-api-role`を定義する。
IAM マネジメントコンソールからロールをクリックしてロールを作成をクリック
![iam_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/117e57e6-e053-3e66-68b3-a0b7824c80a4.png)

AWSサービスを指定して一般的なユースケースからLambda を選択
次のステップをクリック

![iam_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/fa3c0b60-3b7e-792e-2c34-fb28dae653fe.png)

検索ボックスでポリシーを検索
先ほど作成したIAMポリシーにチェック
次のステップをクリック

![iam_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/a8b39013-4e69-030c-dd18-5acdd510b07d.png)

キーとバリューを入力
![iam_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/caa7e743-4200-59ce-e89c-23f7b6169705.png)

ロール名を入力してロールの作成をクリック
![iam_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/06cf1e54-98ff-486d-c376-e4af7a19f8a7.png)

### 返信用Lambdaの作成

Lambda のダッシュボードから関数の作成をクリック
既存のロール`reko-api-role`を利用したLambda関数`reko_api`を作成します。

一から関数を作成
![lambda_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/c42955a8-d961-8517-1175-48924394453f.png)

基本的な情報は以下のように設定してください。

|項目|値|
|:---|:--|
|関数名|reko_api|
|ランタイム|Python3.9|
|アーキテクチャ|x86|
|実行ロール|既存のロールを使用する|
|既存のロール|reko-api-role|

![lambda_2_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/2a554712-c58a-72cf-72de-dbf1f5d048d2.png)

詳細設定にある二つのチェックボックスが外れていることを確認して関数の作成をクリックします。
![lambda_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/3995bb61-7a7b-d056-a063-a327c03b9595.png)

事前準備で作成したzipファイルをアップロードします。
アップロード元をクリックして`reko_api.zip` を指定後
保存をクリックしてアップロードします。

![lambda_4-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/8bd715e7-1667-5ad2-1e51-9515c018f21a.png)

次にLambda内で利用する環境変数を定義します。
設定から環境変数をクリック

![lambda_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/5308c8c7-a87d-4184-a964-fb294233f943.png)

編集をクリック
![lambda_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/d892c893-af5b-8bca-ba6c-9058218722d5.png)

次にLambda内で利用する環境変数を定義します。
設定から環境変数をクリック後、編集をクリック

|変数名|値|
|:---|:--|
|S3_BUCKET_NAME|marry-bot-image|
|LINE_CHANNEL_ACCESS_TOKEN|LINE Developers Console からコピーしたトークン|
|DYNAMODB_TABLE_NAME|LineMessages|

環境変数の追加をクリックして3つの環境変数を定義します。

|変数名|値|
|:---|:--|
|IMAGE_URL|幸せ戦闘力が50万を超えたときに表示する画像|
|LINE_CHANNEL_ACCESS_TOKEN|LINE Developers Console からコピーしたトークン|
|DYNAMODB_TABLE_NAME|RekoEmotions|

![lambda_7-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/9d3bc933-a0f7-96ea-2de3-753d808c7a97.png)

最後にタイムアウト時間が15秒となるようにLambdaの一般設定からタイムアウト秒を設定します。

一般設定をクリック後、編集をクリック

![lambda_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/dc250939-0f67-9d0f-e084-d3f09a0bff0f.png)

タイムアウトを15秒に設定して保存をクリック
![lambda_9-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/2ceab982-2fa4-3385-821a-44146cb737ee.png)

### S3イベントを設定

返信用LambdaにはS3のバケットに画像がPUTされたら発火するようにトリガーをセットします。

コード画面を開くため、コードをクリック
![lambda_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/55877b21-2ad7-b2a8-625c-ead1ed4b3cad.png)

関数の概要が表示されるので関数の概要をクリックしてトリガーを追加をクリック
![lambda_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/3ceccf2f-d29f-9ad9-a129-a9e798c5cec1.png)

検索ボックスにS3と入力してトリガーをセットします。
![lambda_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/150dd2ea-e0bd-1226-e594-cb31b47d0908.png)

バケット名、イベントタイプ、プレフィックス、サフィックスを以下のように設定してください。
![lambda_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/31baa816-bbef-ea12-021b-e28979c89aee.png)

|項目|値|
|:---|:--|
|バケット名|marrige-bucket|
|イベントタイプ|PUT|
|プレフィックス|空白のまま|
|サフィックス|空白のまま|

![lambda_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/61849d96-7d37-5c0a-4882-860dc1853dbd.png)

最後に追加をクリック

## 返信時の流れ - ざっと

1. LINEを経由して特定のS3バケットに画像がPUTされる
2. ユーザID、オブジェクトID、LINEのメッセージをDynamoDBに保存
3. 返信用Lambda が発火
4. S3バケットに入った画像のメタ情報を取り出す
5. Amazon Rekognition 起動
6. 画像処理を実行
7. 処理結果をJSONで取得
8. データを加工
9. FlexMessageで返信
10. 処理結果をDynamoDBに保存

## 反省点

### 受信用DynamoDBのパーティションの切り方

パーティションを切る時にimage_idでパーティションを切ってしまうと
ソートキーの意味がなくなってしまうので受信用DynamoDBのソートキーとパーティションキーは逆にすべきだった。

### 返信用DynamoDBのパーティションの切り方

Rekognition の結果をどう格納するかというところで
最初はEmotions キーに表情分析の結果を配列として格納していましたが
それではPartiQLで分析できないということ。
※要は分析できるようにテーブルを作らなかったというところが良くない。

**データ分析ができるようにデータを作る!!これ大事!!**

### イベントの活用方法

今回はユーザIDを画像の名前に付加してそれをS3のPUTイベント発生時に取得するようにしていましたが
参考にさせていただいた記事ではおそらくDynamoDBのイベントを使っているモノと思われるので
今回の記事と参考記事では若干、作りが異なると思われる。

**Lambdaイベントを使う時はスクリプトの実行順序を入念に検討する必要がある。**

## Bot作成時のトラブルシューティング方法

結論を言うと、CloudWatch の ログを読むことです。
だいたいの答えはここにあります。

## Amazon Rekognition の結果を分析してみる

Amazon Rekognition は 表情分析の結果だけなく他にもいろんな情報を返します。

具体的には以下

```json
{
    "FaceDetails": [
        {
            "BoundingBox": {
                "Width": 0.55982905626297,
                "Height": 0.31907957792282104,
                "Left": 0.19411040842533112,
                "Top": 0.155109241604805
            },
            "AgeRange": {
                "Low": 20,
                "High": 32
            },
            "Smile": {
                "Value": false,
                "Confidence": 98.34297943115234
            },
            "Eyeglasses": {
                "Value": false,
                "Confidence": 65.72138977050781
            },
            "Sunglasses": {
                "Value": false,
                "Confidence": 96.26079559326172
            },
            "Gender": {
                "Value": "Female",
                "Confidence": 98.86897277832031
            },
            "Beard": {
                "Value": false,
                "Confidence": 99.22696685791016
            },
            "Mustache": {
                "Value": false,
                "Confidence": 99.65571594238281
            },
            "EyesOpen": {
                "Value": true,
                "Confidence": 96.83961486816406
            },
            "MouthOpen": {
                "Value": false,
                "Confidence": 89.64883422851562
            },
            "Emotions": [
                {
                    "Type": "CALM",
                    "Confidence": 85.87602233886719
                },
                {
                    "Type": "SAD",
                    "Confidence": 11.985572814941406
                },
                {
                    "Type": "CONFUSED",
                    "Confidence": 1.33014976978302
                },
                {
                    "Type": "HAPPY",
                    "Confidence": 0.22139669954776764
                },
                {
                    "Type": "ANGRY",
                    "Confidence": 0.21962696313858032
                },
                {
                    "Type": "SURPRISED",
                    "Confidence": 0.1779804825782776
                },
                {
                    "Type": "DISGUSTED",
                    "Confidence": 0.11067485809326172
                },
                {
                    "Type": "FEAR",
                    "Confidence": 0.07857893407344818
                }
            ],
            "Landmarks": [
                {
                    "Type": "eyeLeft",
                    "X": 0.3114166259765625,
                    "Y": 0.27229952812194824
                },
                {
                    "Type": "eyeRight",
                    "X": 0.556735634803772,
                    "Y": 0.2768763303756714
                },
                {
                    "Type": "mouthLeft",
                    "X": 0.3393491804599762,
                    "Y": 0.395893394947052
                },
                {
                    "Type": "mouthRight",
                    "X": 0.5452344417572021,
                    "Y": 0.4002780616283417
                },
                {
                    "Type": "nose",
                    "X": 0.3942665755748749,
                    "Y": 0.35237061977386475
                },
                {
                    "Type": "leftEyeBrowLeft",
                    "X": 0.23418769240379333,
                    "Y": 0.2390945702791214
                },
                {
                    "Type": "leftEyeBrowRight",
                    "X": 0.34563449025154114,
                    "Y": 0.24140697717666626
                },
                {
                    "Type": "leftEyeBrowUp",
                    "X": 0.28236690163612366,
                    "Y": 0.2317887395620346
                },
                {
                    "Type": "rightEyeBrowLeft",
                    "X": 0.48571792244911194,
                    "Y": 0.24332554638385773
                },
                {
                    "Type": "rightEyeBrowRight",
                    "X": 0.659654438495636,
                    "Y": 0.2459377497434616
                },
                {
                    "Type": "rightEyeBrowUp",
                    "X": 0.5644543766975403,
                    "Y": 0.23580995202064514
                },
                {
                    "Type": "leftEyeLeft",
                    "X": 0.27321746945381165,
                    "Y": 0.26966771483421326
                },
                {
                    "Type": "leftEyeRight",
                    "X": 0.36017027497291565,
                    "Y": 0.2745400667190552
                },
                {
                    "Type": "leftEyeUp",
                    "X": 0.30802878737449646,
                    "Y": 0.26683565974235535
                },
                {
                    "Type": "leftEyeDown",
                    "X": 0.3124621510505676,
                    "Y": 0.27796489000320435
                },
                {
                    "Type": "rightEyeLeft",
                    "X": 0.5081146359443665,
                    "Y": 0.27715158462524414
                },
                {
                    "Type": "rightEyeRight",
                    "X": 0.6056190133094788,
                    "Y": 0.27561256289482117
                },
                {
                    "Type": "rightEyeUp",
                    "X": 0.5542343258857727,
                    "Y": 0.2711951732635498
                },
                {
                    "Type": "rightEyeDown",
                    "X": 0.5555209517478943,
                    "Y": 0.28240805864334106
                },
                {
                    "Type": "noseLeft",
                    "X": 0.374962717294693,
                    "Y": 0.3577878475189209
                },
                {
                    "Type": "noseRight",
                    "X": 0.46588602662086487,
                    "Y": 0.35922765731811523
                },
                {
                    "Type": "mouthUp",
                    "X": 0.4184502959251404,
                    "Y": 0.3886415660381317
                },
                {
                    "Type": "mouthDown",
                    "X": 0.426589697599411,
                    "Y": 0.4241403341293335
                },
                {
                    "Type": "leftPupil",
                    "X": 0.3114166259765625,
                    "Y": 0.27229952812194824
                },
                {
                    "Type": "rightPupil",
                    "X": 0.556735634803772,
                    "Y": 0.2768763303756714
                },
                {
                    "Type": "upperJawlineLeft",
                    "X": 0.23471041023731232,
                    "Y": 0.2555982172489166
                },
                {
                    "Type": "midJawlineLeft",
                    "X": 0.2832850217819214,
                    "Y": 0.39067912101745605
                },
                {
                    "Type": "chinBottom",
                    "X": 0.4491232633590698,
                    "Y": 0.48228609561920166
                },
                {
                    "Type": "midJawlineRight",
                    "X": 0.7167062759399414,
                    "Y": 0.3968362808227539
                },
                {
                    "Type": "upperJawlineRight",
                    "X": 0.7680227160453796,
                    "Y": 0.26324328780174255
                }
            ],
            "Pose": {
                "Roll": -0.2124393731355667,
                "Yaw": -12.579103469848633,
                "Pitch": -5.298882961273193
            },
            "Quality": {
                "Brightness": 94.21043395996094,
                "Sharpness": 94.08262634277344
            },
            "Confidence": 99.99595642089844
        }
    ],
    "ResponseMetadata": {
        "RequestId": "3ae31d19-fc05-4d9d-84ae-b0d7dede9752",
        "HTTPStatusCode": 200,
        "HTTPHeaders": {
            "x-amzn-requestid": "3ae31d19-fc05-4d9d-84ae-b0d7dede9752",
            "content-type": "application/x-amz-json-1.1",
            "content-length": "3357",
            "date": "Thu, 16 Dec 2021 14:51:12 GMT"
        },
        "RetryAttempts": 0
    }
}
```

上記のように
推定年齢、笑顔かどうか、メガネ着用有無、サングラス着用有無、見た目上の性別、ひげと口ひげ、目や口が開いているかなど様々なデータを取ることができます。

ただし、顔からわかることでしか判断をしない為、正確でないこともあります。
例えば、ジェンダーレスモデルは見かけ上の性別と異なるケースが多いが為に判断を誤ることがあります。
今回は実在するジェンダーレスモデルを分析してみましたが、女性と認知されました。

人は見かけによらずというのはAWSというか機械も例外ではないということですね。
※あれ、そうすると男性と判定された一般男性の私も誤認された可能性があるのかも。

## 独り言

最初の印象が全てなどと語る人がいる。確かに第一印象が全てというのはあるかもしれない。
しかし、それは捉え方によって変わるモノだと思う。
例えば、幸せなニュースだと言われていても他の人にとっては不幸なニュースに聞こえるかもしれないし憤りを感じる人だっている。
それはニュースを見た時に何に注目して何を考えて何を思うのかで変わるのだと思います。

人種、性別、門地、価値観、イデオロギー 人の数だけ見方がある。
千差万別の見方があるということは千差万別の印象があるものと考えています。

また、多くの人に愛された著名人の不倫や自殺などもそうですが、我々が普段見ている彼ら彼女らは本当の姿ではないかもしれないです。

印象や見せかけではなく本質を見極められる人間になりたいものです。

## おわり