画像から人物の性別、年齢を推測するLine botを作ってみた

# 概要

lineを使って、送信した画像から
性別、年齢予想、表情分析を実施するbotを作成しました。

こんな感じ
<img width="642" alt="スクリーンショット 2020-01-23 22.38.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255289/c27d5302-5a33-6206-8d68-13430b879865.png">
<img width="650" alt="スクリーンショット 2020-01-23 22.40.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255289/e3cadc6f-a9a7-675b-14cd-87c4d0e509b5.png">



画像を送ると、分析した結果が返ってくる(3人まで同時に写っててOK)

ソースコードは[こちら](https://github.com/vesono/photo_recog_line_bot/blob/master/lambda_function.py)
（ほぼべた書きでとりあえず作ってみたら、満足してしまい
リファクタリングする気力がなくなってしまった）

# 使用言語、技術

- Python3.6
- AWS API Gateway
- AWS Lambda
- AWS S3
- AWS DynamoDB
- AWS rekognition

# 構成

こんな感じの構成
![コメント 2020-01-24 093650.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255289/6d9845b7-d2c9-dbab-a93a-76b804231f58.jpeg)

# 引っかかったところ

## s3の署名付きurlを使って画像を送ろうとしたら送れない

line botに画像を送る際には画像のurlが必要なので、
以下のようなコードで画像をリプライしようとしたらエラーになった

```py
s3_url = generate_presigned_url(
             ClientMethod='get_object',
             Params={
                 'Bucket': BUCKET,
                 'Key': KEY
             },
             ExpiresIn=60,
             HttpMethod='GET'
         )
line_bot_api.reply_message(
    reply_token=event.reply_token,
    messages=ImageSendMessage(
        original_content_url=s3_url,
        preview_image_url=s3_url
    )
)
```

```
LineBotApiError: status_code=400, request_id=xxxxx-xxx-xxx-xxx-xxxxxxxxxx, error_response=
{
    "details": [
        {
            "message": "Length must be between 0 and 1000",
            "property": "messages[0].originalContentUrl"
        },
        {
            "message": "Length must be between 0 and 1000",
            "property": "messages[0].previewImageUrl"
        }
    ],
    "message": "The request body has 2 error(s)"
}
```

url長すぎって怒られた・・・

s3の署名付きurlを発行するとurl長すぎて送れないので
新たにパブリックなs3バケットを作成し、 
送信する画像をread-onlyでs3に入れてurlを取得することにした

```py
# アクセス権を付与してput
client.put_object(ACL='public-read', Bucket=bucketname, Body=image, Key=key)
# urlは文字列結合で作成
s3_pub_url = 'https://' + bucketname + '.s3-ap-northeast-1.amazonaws.com/' + key
line_bot_api.reply_message(
    reply_token=event.reply_token,
    messages=ImageSendMessage(
        original_content_url=s3_pub_url,
        preview_image_url=s3_pub_url
    )
)
```

s3のパブリックアクセスは使ったことがなく、
アクセス権限の云々を見よう見まねで色々いじっていたら時間がかかってしまった


## lambdaでPILモジュールが読み込めない

BoundingBoxを画像上に描画するために、
PILモジュールをインポートしようとしたらエラーになった

```
Unable to import module 'lambda_function': cannot import name '_imaging'
```

ローカルでは動くのに、lambdaだとPILモジュールがインポートできなくてエラーになる
Google先生に聞いてみると、どうもPillowのライブラリはosに依存する部分があるらしい
なので、lambdaが動く環境(= Amazon Linux) にインストールしたPillowライブラリが必要らしい

参考サイト：
https://michimani.net/post/aws-use-pillow-in-lambda/

上記の記事ではdockerを使ってライブラリを作成していますが、
~~なんか面倒くさそうだったので~~、cloud9を利用してライブラリを作成することにした

- cloud9の環境作成
- lambdaのインポート
- 対象のフォルダにPillowライブラリをインストール  
`python3 -m pip install Pillow -t ./`
- 対象のフォルダをダウンロードして、s3に上げてデプロイ
- cloud9環境は削除

お金をなるべくかけたくない精神なので、
cloud9の環境はPillowライブラリを作成するためのみに使用した

cloud9からダウンロードするとzipで圧縮されているが、
フォルダごとの圧縮なので、そのままlambdaをデプロイすると関数が呼び出せなくてエラーになる(1敗)

# 現状の課題、やりたいことなど

- リファクタリング
    - コードがほぼべた書きの状態。<br>
      せめてDBにアクセスする処理くらいは切り離したい

- AWSの権限回りの整理
  - ~~色々考えるのが面倒くさく~~、lambdaのiamロールに必要のない権限がついてしまっている<br>必要のない権限は持たないように整理したい

- DynamoDBの設計
  - 今までRDBしか触ったことがなかったので、イマイチ使い方、メリットなどがわかっていない。<br>使い方もRDBっぽくなってしまっているので、知識を増やして改めて設計を考えてみたい