飯テロ禁止botをLINE + AWS Rekognitionで実装する

先週、JAWS-UG沖縄 画像認識サービスを使ってみようハンズオン / もくもく勉強会 2017年7月に参加してきました。

AWSの画像認識サービス(Rekognition)を使って感情ランキングアプリを作るハンズオンをやっておりました。
JAWS-UG青森で開催されたハンズオンの再演で、感情ランキングアプリを作りました。

Rekognition使って何か作れないかなあと考えてたら
そういやパリピ共がよくご飯の時間に美味そうなご飯の写真をうpしてくるので
それを撃退できるbotが作れたらいいなと思ってちょっと頑張ってみました。

ちょっとググってみたら似たようなことを既に実施してる人がいたのでこれを元にちょいっと変更したらできそうな感じ

実際に作ったもの

下記の画像を見てもらえればわかる通りご飯画像を投げるとアラートメッセージが飛んできます。

food alert

アーキテクチャ

全体の構成

Line DeveloperのサイトにWEBHOOK_URLを登録する項目があるので、そこにAPIGatewayのURLを登録する。
Linebotへの個別, もしくはLinebotが属するグループに対して発言なり画像を送信するとアップロードするとWebhook URLにイベントが通知されてAPIGatewayを経由して、Lambdaが処理を行う。
そのイベントが画像であればLambdaを介してRekognitionで解析をかける。
それがご飯であればアラートを上げるようにします。
Rekognition自体は日本のリージョンで展開されてないサービスですので、バージニア北部等のリージョンで利用します。
APIGateway, Lambdaもそれに合わせて今回はバージニア北部のリージョンで利用します。

Rekognition

Rekognitionに画像を投げると下記のような感じでラベル付けをしてくれます。

{
    "Labels": [
        {
            "Name": "Bowl",
            "Confidence": 97.36100769042969
        },
        {
            "Name": "Dish",
            "Confidence": 69.66221618652344
        },
        {
            "Name": "Food",
            "Confidence": 69.66221618652344
        }
    ]
}

今回のお題は「ご飯の画像であればアラートを上げる」ですので、
LabelsのNameのValueがFoodでConfidenceのvalueがある一定以上であればメッセージを出力できればOKです。

Line -> APIGateway

一部マスク・省略してますが、LINEからAPIGatewayに入ってくるパラメータはこんな感じです.

{ resource: '/',
path: '/',
httpMethod: 'POST',
headers: 
{ Accept: '*/*',
'Content-Type': 'application/json;charset=UTF-8',
Host: '********.execute-api.us-east-1.amazonaws.com',
'User-Agent': 'LineBotWebhook/1.0',
'X-Line-Signature': '*****' },
stageVariables: null,
requestContext: 
{ path: '/dev/',
accountId: '**********',
resourceId: '**********',
stage: 'dev',
requestId: '********-****-****-****-************',
identity: 
{ cognitoIdentityPoolId: null,
accountId: null,
cognitoIdentityId: null,
caller: null,
apiKey: '',
sourceIp: 'xxx.xxx.xxx.xxx',
accessKey: null,
cognitoAuthenticationType: null,
cognitoAuthenticationProvider: null,
userArn: null,
userAgent: 'LineBotWebhook/1.0',
user: null },
resourcePath: '/',
httpMethod: 'POST',
apiId: '******' },
body: '{"events":[{"type":"message","replyToken":"hogehoge","source":{"userId":"hogehoge","type":"user"},"timestamp":1111111111111,"message":{"type":"image","id":"1111111111111"}}]}',
isBase64Encoded: false }

Lineから送ったメッセージや画像、返信に必要なTokenなどの情報はすべてbodyの中に入っています.

{
    "events": [
        {
            "type": "message",
            "replyToken": "hogehoge",
            "source": {
                "userId": "hogehoge",
                "type": "user"
            },
            "timestamp": 1111111111111,
            "message": {
                "type": "image",
                "id": "1111111111111"
            }
        }
    ]
}

Serverless Framework

サーバーレスなアーキテクチャの作成・管理ができるフレームワーク.
以前にServerless Frameworkを触ってみた + 環境変数のアップロードまでやってみるという記事を書きました. こちらを利用します.

Serverless Frameworkはまだまだ勉強中なのですが、
LambdaやAPIGatewayの管理画面をぽちぽちせず、コードとして管理ができるので非常に便利です。

実際に作ってみた

ソースは下記で公開してます。

  • 328/serverless-practice - Github
    ServerlessFrameworkを使いこなしてる人はパッケージ入れた後にserverless.ymlのenvironmentだけ埋めてもらえればあとはデプロイして使えるはずです。

serverless.yml

  • 画像を扱うのでmemorySizeを少し大きめにとっている
  • rekognitionを利用するのでregionはus-east-1にしている
  • iamRoleStatementsはrekognitionのfull accessを許可にしている

今回は、Serverless FrameworkでAPIGatewayのデプロイもやってみました。と言ってもeventsに3行度書くだけで終わっちゃいましたが。

handler.js

この記事の冒頭で紹介した記事とほぼほぼ一緒なので恐縮ですが、
LINEBot.createに渡すパラメータはすべて環境変数から取得しています。(環境変数はserverless.ymlに記載)
個人的にif文がネストすると読みにくくなるのでforEachの直後にresponse.typeがメッセージでないものはすべてreturnしています。
for文のスキップはcontinueでいけるかと思ってたんですが、forEachはfunctionなのでreturnにしないとダメでした。

あとは、rekognitionが付与してくれるLabelに’Food’が入っていて, その閾値が'60’以上であれば
bot.replyTextMessageでアラートを流すという感じです.

Javascriptの書き方というかコードの書き方あまりわかってないので、この辺こうしたほうがいいとかあったらぜひ教えてくださいm(_ _)m


という感じで、Linebotを作ってみましたとさ。
作り終わって最後に気がついたんですが、僕のLineにはお友達がいないので使う機会はきっとこない…(笑)

冒頭に紹介した勉強会でハンズオン後にLTできる人を募集してて、
上記を時間内に作れたらLTやろうかなと思ったんですが、
時間に間に合わず… 残念orz