深層学習で麻雀牌をリアルタイムに物体検出してみた

[IoTLT Advent Calendar 2018](https://qiita.com/advent-calendar/2018/iotlt_neo) 13日目の記事です！

# はじめに
麻雀牌をリアルタイムで検出するデモを、GoogleAIYKitの一つであるvision kitを用いて作りました

色々書いていたら長くなってしまったので、先にデモ動画を。
※音が出ます


<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">IoTLT Advent Calendar 13日目用動画 <a href="https://t.co/9dmzzcn5ao">pic.twitter.com/9dmzzcn5ao</a></p>&mdash; Rio (@Rio46512178) <a href="https://twitter.com/Rio46512178/status/1072883272181399555?ref_src=twsrc%5Etfw">2018年12月12日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

・・・微妙すぎん？って声が今にも聞こえてきます。
（作者としてはかなり苦労して作ったので愛くるしさを感じますが）

以下は、[GoogleDevFest2018で喋った内容](https://www.slideshare.net/_Rio_/gdg2018-vision-kit-mahjong)のデモコンテンツの開発記録です。

# モチベーション
- 麻雀が好き（下手の横好きですが）
- 機械学習モデルをモバイルデバイス上で実行するエッジ推論に興味があった

# vision kit？ [^1]
今回はvision kitという、人やモノを認識するスマートカメラを自作できるキットを使いました。
(組み立て前)
<img src="https://qiita-image-store.s3.amazonaws.com/0/177134/71a0c5cf-ed9d-36b3-b706-5f928e1a157a.jpeg" width=30%>
(組み立て後)
段ボールでできてる。オモチャ感が可愛い
<img src="https://qiita-image-store.s3.amazonaws.com/0/177134/05475cce-59a4-b0b6-3614-d054ca900109.jpeg" width=30%>
内部にはRaspberry Pi Zero WHとvision bonnet
<img src="https://qiita-image-store.s3.amazonaws.com/0/177134/f5460817-bb9c-2189-2580-dc54fc7cc9e5.jpeg" width=30%>

**クラウドに接続することなく、vision kit上でニューラルネットワークモデル等の複雑なモデルを動かしてモノを認識できる**というのが最大の特徴です。
チュートリアルに沿っていけばRasPiとカメラやLED、ボタンとの接続は簡単でした。
（過去にRasPiで温湿度センサーをドロドロに溶かした苦い思い出が・・・）
組み立てには1.5時間程度かかると公式チュートリアルに記載がありましたが、本当にその位で終わりました。

標準では以下の機械学習モデルが用意されています。

- 1000種類のモノを認識するモデル
- 顔を検出し、「悲しみ」や「笑い」など表情を評価するモデル
- 人、犬、猫を識別するモデル


## vision bonnet？
vision kitには**Raspberry Pi Zero WH**と**vision bonnet**という基盤が付属されています。

vision bonnetとは、

- ニューラル ネットワークを実行できる低電力視覚処理ユニット Intel® Movidius™ MA2450チップが搭載されている
- 毎秒最大30フレームのスピードで実行可能

ニューラルネットワーク等の複雑なモデルの推論向きの基盤。
(「Pi Zero Wの処理を60倍高速にする」ともネットに書いてあったので、いつか速度検証してみたい)


## エッジ推論？
今回わざわざvision kitを選んだ理由のひとつとして、エッジ推論に興味があったからです。
（~~iosアプリには手が出せなかった~~）

### エッジ推論が注目され始めた背景  
- IoT時代の到来により、膨大なデータがエッジ側で生成されるようになっている
- 機械学習モデルを作るハードルは下がり、次にモバイル・エッジデバイス上で機械学習を動かすニーズが増えている  

### メリット  
クラウドとの通信が必要な場合と比べると、下記のメリットがあります。  

- 遅延が少ない
- オフライン環境でも動く
- データがデバイス上に留まる
- 電力効率が良い
- センサーデータを直接扱える

### 制約
ただし、当然制約もあります。  

- 計算リソースが限られている
- メモリも潤沢ではない
- バッテリーを気にしなければならない  
- 強力なマシンで動作する複雑なモデルと比べると精度が低くなりがち  
- 実装の難易度が高い  

### 研究も進んでいる
エッジ推論のニーズは増えてきています。それを表すように、様々な研究が進んでいます。

- モデルの圧縮
    - Quantization(量子化)
    - Distillation(蒸留)
    - Pruning(枝刈り)
    - ・・・
- エッジへの機械学習モデルのデプロイを実現してくれるサービス
    - TensorFlow Lite
    - ML kit for Firebase
    - TensorFlow.js
    - ・・・

前置きが長くなりましたが、制作フローをご紹介します。


## 全体の流れ
1. Vision Kitの組み立て・チュートリアル
2. 学習データの準備
3. 簡単な前処理
4. 学習（MLEngine）
5. Vision Bonnetへのコンパイル
6. 推論


##### 1.Vision Kitの組み立て・チュートリアル
公式チュートリアル[^2]に沿って組み立てていけば、カメラ・ボタン・LED等との接続は簡単でした。
（所要時間1.5時間と記載がありましたが、ハマることなく本当に1時間20分程度で終わりました）


##### 2.学習データの準備
1牌あたり1枚撮影して、それらの牌をランダムに組み合わせて学習データとしました。
10,000枚自動生成して、8,000枚を学習データ、2,000枚をバリデーションデータとしています。
<img src="https://qiita-image-store.s3.amazonaws.com/0/177134/9a6ed015-0772-eb87-d5ee-4e2a528478ca.png" width=20%> ----＞ <img src="https://qiita-image-store.s3.amazonaws.com/0/177134/5c8432b4-578d-6b94-b159-b175fb48fa5a.jpeg" width=20%>


##### 3.簡単な前処理
下記フィルタをそれぞれ範囲は小さくかけてオーグメンテーションをしました。

- ガウシアンフィルタ
- 中央値フィルタ
- ヒストグラム平坦化

##### 4.学習
GooglMLEngineを使いました。
vision kit上で動くようにサポートされているモデルは下記だけです。今回はmobilenet ssd v1[^3]を用いました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/177134/7231c112-628c-d41b-2cd8-ccd43465d20b.png" width=80%>

TensorFlow用のTFRecord形式に変換するために、label_map.pbtxtというラベルのインデックス情報を持っている辞書ファイルと、学習/バリデーションデータの画像ファイル名一覧の.txtファイルを作成する必要があります。

余談ですが、最初はiPhoneで撮影したものをランダムに組み合わせて生成していたのですが、検出精度が悪く、まずデータを疑いました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/177134/eea2715f-22fd-645a-8502-d8ca3311afd7.png" width=80%>
左：iPhoneで撮影した牌をランダム生成した画像(学習データ)　←綺麗すぎる
右：実際にRasPiカメラで撮影した画像(テストデータ)　

RasPiカメラの画質とiPhoneの画質の違いが精度に影響してるのかな～と思ったので、最終的にはRasPiで牌の写真を撮影し、それを学習データとしています。

##### 6.vision bonnetへのコンパイル
学習済みの深層学習モデルは重いので、アプリやモバイルデバイス上などで実行するためには、モデルサイズを小さくする必要があります。TensorFlowモデルからの変換であればTensorFlowLite[^4]があります。

今回はvision bonnet上で動くようにコンパイルする必要があるのですが、vision kitの公式チュートリアルからコンパイラがダウンロードできるので、それを使って下記コマンドを打つだけです。  

```python
# bonnet_model_compiler_2018_04_26.tgzをダウンロード、解凍
./bonnet_model_compiler.par \
 --frozen_graph_path=../frozen_inference_graph.pb \
 --output_graph_path=../mahjong_detector.binaryproto \
 --input_tensor_name="Preprocessor/sub" \
 --output_tensor_names="concat,concat_1" \
 --input_tensor_size=256
```


##### 7.推論
あとは実行するだけ。



# おわりに
今回は初めてエッジ推論に挑戦しました。
お世辞にもよい結果とは言えないのですが、過去にkerasで組んだSSDモデルではもう少し精度は良かったので、学習不足かモデルが小さいからか、データの前処理がイケていなかったか。。。改善余地はまだありそうです。
あと、得意な牌とそうでない牌はありそうで、角度を変えても四萬や四筒、七索あたりは検出できているように見えます。

何にせよ、ピコピコ鳴りながら永遠に牌の存在しいないところで"白(haku)"を検出し続ける愛くるしいオモチャが出来ました。
引き続き、(今度はiosアプリで)開発挑戦しているので今後をお楽しみに！


[^1]: https://robotstart.info/2018/07/26/google-aiy-vision-kit.html
[^2]: https://aiyprojects.withgoogle.com/vision/
[^3]: https://qiita.com/tanakataiki/items/41509e1b0f4a9dcd01b1, 
 https://arxiv.org/pdf/1704.04861.pdf
[^4]: https://www.tensorflow.org/lite/