記事要約
機械学習によるアイドル顔識別のLINE BOTを作りました。アイドル分かる人は是非 友だち追加して アイドル画像を送りつけて遊んだりしてみてください。
むしろ識別対象のアイドルさん御本人にも使ってもらえたら最高
BOT概要
TensorFlowによるアイドル顔識別器の話 - 2016.9.28 TensorFlow勉強会 - Qiita
にまとめている、自作のアイドル顔識別器の 「入力した画像に対し、写っているのが『どのアイドル(人物)か』を機械学習により自動判定する」 という機能を実際に試すためのインタフェース。
トーク(1:1、Room, Group、いずれでも可)で画像を投稿されると、その画像に写っている人物の顔を識別してCarouselメッセージで結果を返す。
11月末現在の時点で、識別対象のアイドルは851人となっています。
顔識別API
元々 実際に学習結果を試すため画像を入力して結果を出力するインタフェースは TensorFlowによるディープラーニングで、アイドルの顔を識別する - すぎゃーんメモ あたりのときから作っていて、TensorFlowで学習したパラメータをtf.train.Saver
でcheckpointファイルとして出力しておき、それを使って復元したモデルで入力画像に対する識別結果を返すだけのJSON APIをFlask
で用意して使っている。
学習をするわけでなく結果を使って計算するだけならHerokuでもこれくらいは動かせる。
tf-face-recognizer/web.py at master · sugyan/tf-face-recognizer · GitHub
Twitter版
なのでBOTを作る場合はそのAPIに繋ぎ込むだけでよくて、まずはMentionに反応するTwitter BOTで作ってちょいちょい動かしてみていたのだけど、
- 画像が4つまでしか添付できない
- 140文字までしかテキストを入力できない
の制限のため、1画像から5つ以上の顔が識別されると切り抜いても投稿できないし、そもそも識別結果の人物名・グループ名を入力すると3〜4人で140文字に達してしまうなどの問題があった。
あとどうしてもpublicなtimelineに流れることになってしまい(まぁDMに対応すれば良かったのかもしれないけど)、間違った結果などが投稿されて御本人のエゴサ結果を汚してしまう、などの問題もあり。
LINE版
LINE Messaging API を使った Template Message では、Carousel を使うことで1メッセージで5件まで結果を投稿できるし、1つずつ別個にテキスト情報を付与できるので表示がより分かりやすくなる。 ついでにその人物のプロフィールページ(今は取得元のTwitterプロフィールページにしてる)があれば URI action で開いたりもできるし。
実装
今回もGo SDKを使って作っている。動かす場所はどこでも良かったけど とりあえずHerokuで。
顔識別応答に必要な機能は
- Image Message を受け取ったら、その画像データを取得し、
- 自作の顔識別APIにその画像をそのまま投げ、
- 結果のJSONをparseし、
- それぞれの検出された顔・識別結果を Carousel Column にセットし、
- 出来上がった Template Message を Messaging API に投げる
というだけではあるのだけど、Twitter BOTの場合とは画像の投稿まわりが異なるのでちょっと考えないといけない。
Twitter は Uploading Media API があって、そこにまずPOSTして そのmedia_id
をリクエストに載せれば画像投稿が出来るのだけど、LINE Messaging API の場合は画像系の投稿は「画像URL」をリクエストに載せることになるので、その画像のホスティングは自分でどうにかしないといけない。
今回の顔識別BOTの場合は、検出された顔領域を切り抜いたものをthumbnailImageUrl
として Carousel Column に載せたいけど、これらを静的配信するだけのためにS3 bucketとか作って管理したくないなぁ、ということで 動的に生成しつつキャッシュでどうにかする戦略で作った。
検出された領域の抽出
顔識別APIでは、入力した画像に対し検出された顔の位置座標と傾き角度を返す(これは Cloud Vision API のレスポンスをそのまま使っている)ので、「どの部分に写っている顔がどう識別されたか」を示すために、検出された領域をそれぞれ切り抜きたい。せっかく傾きも判っているのならばそれも補正して真っ直ぐにした状態で。
以前の記事 で、 ImageMagick の-distort SRT
オプションでの変換を使った切り抜き方は把握していたけど、せっかくだから今回はPure Goだけでもやってみよう ということで 画像のScale, Rotate, Translateが可能なライブラリを探して、github.com/disintegration/gift
が良さそうだったのでそれを使ってみた。(他にも良いのがあれば教えていただきたいところ…)
gift
の画像変換はちょっとクセがあって、回転の中心を指定したり座標の移動などはできなくて しかも回転後の画像がすべて収まるよう画像サイズが再計算されるので、
- まず傾き角度に合わせて画像全体を
Rotate
し、 - 元の画像で検出されていた顔領域座標を回転後の画像の座標に変換し、
- その回転後の座標で領域部分を
Crop
する
という感じに。回転行列とか使って座標をゴニョゴニョと変換する必要があった。
抽出画像のキャッシュ
ともかくこうして抽出した顔画像を返すURLを生成してthumbnailImageUrl
に渡すことでCarousel TemplateのMessageを送れるのだけど、このURLはMessageを受け取ったユーザがLINEで開くたびに取得しようとGETリクエストを送ってくることになる。
S3とかにアップロードして配信していればあまり問題ないけど、今回それを使わずBOTのWebアプリケーションから配信するようにするために、JPEGバイナリをRedisでキャッシュすることにした。thumbnailの画像は無駄に大きい必要もないので302x200
サイズに加工して(縦横1:1.51という比は固定のようで正方形で返すと上下が切り取られてしまうので左右を埋めて作った)、そうすると10KBにも満たない容量だしheroku-redis
のhobby-dev
プラン(Memory 25MB)でも余裕で1000枚分くらいはキャッシュできるはず。
で、普通に考えると一番最初に Template Message をPOSTした瞬間が一番アクセスされることになる。特にRoomやGroupで複数人が居る場合は、それぞれのユーザがトーク画面を開いていると投稿した瞬間に同時に複数クライアントからGETリクエストが来るはず。なので、送信するthumbnailImageUrl
の各画像JPEGバイナリは事前に生成してから投稿する。
そして、大抵のMessageは流れていくのでそれほど長期間保存しておく必要もないと思っていて、数時間〜数日保持してあれば問題なく。ただ遡ったときにまったく見えなくなっていると困るのでキャッシュが切れていたときは再び元画像を取得しなおして作れるように「元画像のmessage IDと、抽出すべき領域の情報」を復元できるURLにしておく。(任意のmessage IDを指定して画像取得されても困るので それは防ぐためにmessage IDは暗号化した文字列を使うようにしている)
Get content API での取得も、一定期間が経過すると削除されてしまって復元不可能になるけど、まぁそれくらい過去のものは諦めてください、ってことで。
これで、重い処理は極力避けつつも単一のアプリだけで画像の提供もできるBOTアプリを動かすことができた。
別機能 (推論フィードバック)
元々、自分で学習データを管理する用として Messaging APIを使ったLINE Botでアイドル顔画像管理 - すぎゃーんメモ という記事でも書いたとおり そういうBOTを作っていて、これはこれで独立した機能として上述の識別BOTに同居させられるな、と思い。
少し改良して
- 1:1トークで 識別対象アイドルの名前(の一部でもOK)をテキスト入力すると、分類器によってその人物と推論された顔画像を返す
- それらに対して「○」か「×」だけを選択できるようにして Postback 送信
というのを、自分だけでなく BOTを友だち追加した人が誰でもフィードバックを送ることができるようにした。これはRoomやGroupでは誰が送ったものか判別できないので1:1トークのみ対応の機能となる。
対象はあくまでもこちらの管理アプリが持っているデータに対してのみで、識別対象に入っていない限りは推論結果としても現れないのだけど、それでもフィードバックを送ってもらうことで学習データの増加と改善に役立つので、もし知っているアイドルが識別対象に入っているようであれば試していただいて、フィードバックを送っていただきたいです。
少しでも学習データの増加に繋がると嬉しい…。
Repository
余談
今回のBOTを作るにあたり、GoをずっとEmacsで書いていたのだけど、突如思い立って開発環境をAtomに乗り換えてみた。今この記事の下書きもAtomで Markdown Preview しながら書いている。 atomic-emacs の他、Goの開発用には go-plus を入れて、あとは適度に自分好みにkeymapを弄ったり。
ちょっとKarabinerにキーを奪われて上手く設定できなかったり Emacsと比較して dired-mode みたいなのが無くてファイル操作がしにくいな、とか 多少の不満はあるけれど、以外と馴染んで普通に使えるような気はしている。 他の言語(Java以外)での開発もこれでやっていくよう練習してみるつもり。