androidでobject detectionする 失敗編

[カメの甲羅をobject detection apiで認識してみる on win10](https://qiita.com/ikegam1/items/2b13ecf18354bbb6d42c)
の続き？・・みたいなもんになりますかね。

静止画でそこそこ認識するようになりました。
次は動画でやりたいと思いました。
で、このままwin+USBカメラでやろうかなと思ったのですが、カメラ付いてるデバイスでやっちゃえばいいんじゃね？と思いまして。

## まずはデモアプリ

[【TensorFlow】Androidのカメラを使ってどこでも物体認識！](https://www.sejuku.net/blog/53465)

Tensorflow LiteかTensorflow Mobileか迷ったのですが、Liteだと使えるアルゴリズムに制限があるみたいで、今回のモデル使えないんじゃないかと思い、Mobileにしました。

https://www.tensorflow.org/mobile/tflite/demo_android
こちらにインストールして使えるデモアプリがあるみたいです。
ダウンロードしてインストールするだけ。
でもやっぱりめんどいのでスキップします。

## Android Studioでビルド

そんなわけでいきなりビルドします。
なお、使ってるPCにandroid studio入ってなかったので、こちらから落として入れます。
https://developer.android.com/studio/?hl=ja
versionは3.2.1でした。

さてandroid studioが入ったのでこちらをビルドしてみます。
https://www.tensorflow.org/mobile/android_build

なんか、TensorFlow Lite使えって書いてありますけど、華麗にスルーしときます。
```git clone https://github.com/tensorflow/tensorflow```して、
tensorflow/examples/androidをandroid studioで開きます。

で最初のbuildで失敗するけど、https://www.sejuku.net/blog/53465 の手順で無事進めました。

>（5）「1:Project」をクリックし、「Gradle Scripts」→「build.gradle (Project: android)」の順にダブルクリックします。
>（6）「buildscript」の「repositories」に、「google()」を追加します。
>（7）「def nativeBuildSystem = ‘bazel’」を「def nativeBuildSystem = ‘none’」に修正します。
>（8）「dependencies」の「compile」を「implementation」に修正します。
>（9）「File」→「Save All」の順にクリックします。
>（10）「File」→「Settings」の順にクリックします。
>（11）「Appearance & Behavior」→「System Settings」の順にダブルクリックし、「Android SDK」をクリックして、「Android 6.0 (Marshmallow)」にチェックをつけ、「OK」をクリックします。
>（12）「OK」をクリックします。
>（13）「Accept」を選択し、「Next」をクリックします。
>（14）「Finish」をクリックします。
>（15）「Try Again」をクリックします。
>（16）「Install Build Tools xx.x.x and sync project」と表示されたときは、「Install Build Tools xx.x.x and sync project」をクリックします。
>（17）「Finish」をクリックします。
>（18）「Update」をクリックします。
>（19）「Update Build Tools version and sync project」と表示されたときは、「Update Build Tools version and sync project」をクリックします。
>（20）「Run」→「Run ‘android’」の順にクリックします。
>（21）以下の画面が表示されたら、開発者モードとUSBデバッグをオンにしたAndroidスマホを、USBケーブルでパソコンと接続します。
>（22）Androidスマホで、USBデバッグを許可します。
>（23）パソコンの「Select Deployment Target」画面にAndroidスマホの名前が表示されたら、「OK」をクリックします。
>

ちなみにminSdkVersionが21って書いてあったけど、入ってなかったからこのVersionも入れておきました。必要だったかどうかわからないけど。

直したら```Build > Rebuild Project```


## サンプルアプリ

- TF Classify
- TF Detect
- TF Speech
- TF Stylize

という４つがあるみたい。

TF Detectが枠付きの物体検知です。
ためしにスマホに入れて動かしてみたらこんな感じ。
Classifyが画像の分類までやるやつ？Speechは試してないけど音声認識系じゃないかと予想、Stylizeは撮ってる画像を模様風にしてくれるやつ？

<img src="https://qiita-image-store.s3.amazonaws.com/0/308709/03164a87-d166-2adc-5055-bf29cf1bb22c.png" width=50% />

<img src="https://qiita-image-store.s3.amazonaws.com/0/308709/21a5144e-4ac0-94e7-d63a-5a88af1f9130.png" width=50% />

さて、これを自分で作ったモデルを使ってやらなきゃいけない。
examples\android\assets\ssd_mobilenet_v1_android_export.pbを使ってると思うんだけど、これ置き換えたらそのまま使えるんだろうか。あとlabelと。

（・・しかしandroid studio重たい。。良いPCほしい。。）

## ここにきてTensorflow Lite

なんかやっぱしmobilenetv1のモデル使ってやった方が汎用性高いんじゃないかな、って思ったので学習しなおしてます。
で、それならとLiteに乗り換え。
落としてきたレポジトリにLiteのも入ってたし。

[Android Demo App](https://www.tensorflow.org/lite/demo_android)

そんなわけで、```tensorflow/lite/java/demo```をandroid studioで開きます。
Bazelとかわかんないので、android studioでBuildしますよ。

>The build process downloads the quantized Mobilenet TensorFlow Lite model, and unzips it into the assets directory: tensorflow/lite/java/demo/app/src/main/assets/.

モデルも一旦はあるやつをDLして使います。ちなみにLite用に最適化されててけっこう小さい。https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip

なんか詳しいことは[ここ](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/java/demo/README.md)みろや、って書いてありますね。

>You'll need at least SDK version 23.
>Bazel requires Android Build Tools 26.0.1 or higher.

とか。

## 自分用の学習モデルを作る手順


[TF Lite Developer Guide](https://www.tensorflow.org/lite/devguide)

まずは、学習させたい画像を用意して学習させてってやって自分もモデルを作ります。
https://qiita.com/ikegam1/items/2b13ecf18354bbb6d42c
モデルからfrozen graphs(*.pb)というファイルを作る。
次は*.tfliteっていうtensorflow Lite用に最適化されたファイルを生成しないといけないらしい。

.pbを作るのはobject detection api使う時のexport_inference_graph.pyで作ってあるので↓こういうのを実行すればそれでOK？~~tfile_convert.pyは```tensorflow\lite\python```にあった。~~←結局、こっちは使ってない

```cmd
tflite_convert \
  --output_file=mobilenet_v1_1.0_224.tflite \
  --graph_def_file=frozen_inference_graph.pb \
  --input_arrays=input \
  --output_arrays=MobilenetV1/Predictions/Reshape_1
```

しかし、こんな感じのエラーで進まない。。

```
  File "c:\users\koichi\appdata\local\programs\python\python37\lib\imp.py", line 342, in load_dynamic
    return _load(spec)
ImportError: DLL load failed: ダイナミック リンク ライブラリ (DLL) 初期化ルーチ ンの実行に失敗しました。
```

で、お次はhttps://www.tensorflow.org/lite/convert/python_api を参考にしつつ、自分でスクリプト用意してやってみたりするも・・・

```Python:my_tflite_convert.py
import tensorflow as tf

graph_def_file = "frozen_inference_graph.pb"
input_arrays = ["input"]
output_arrays = ["MobilenetV1/Predictions/Reshape_1"]

converter = tf.lite.TFLiteConverter.from_frozen_graph(
  graph_def_file, input_arrays, output_arrays)
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)
```

いろいろパラメータいじったりしたものの最終的にはこんなエラー。

```
$ python my_tflite_convert.py
Traceback (most recent call last):
  File "my_tflite_convert.py", line 12, in <module>
    tflite_model = converter.convert()
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\tensorflow\contrib\lite\python\lite.py", line 453, in convert
    **converter_kwargs)
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\tensorflow\contrib\lite\python\convert.py", line 342, in toco_convert_impl
    input_data.SerializeToString())
  File "C:\Users\user\AppData\Local\Programs\Python\Python35\lib\site-packages\tensorflow\contrib\lite\python\convert.py", line 135, in toco_convert_protos
    (stdout, stderr))
RuntimeError: TOCO failed see console for info.
b'Traceback (most recent call last):\r\n  File "c:\\users\\user\\appdata\\local\\programs\\python\\python35\\lib\\site-packages\\tensorflow\\contrib\\lite\\toco\\python\\tensorflow_wrap_toco.py", line 18, in swig_import_helper\r\n    fp, pathname, description = imp.find_module(\'_tensorflow_wrap_toco\', [dirname(__file__)])\r\n  File "c:\\users\\user\\appdata\\local\\programs\\python\\python35\\lib\\imp.py", line 296, in find_module\r\n    raise ImportError(_ERR_MSG.format(name), name=name)\r\nImportError: No module named \'_tensorflow_wrap_toco\'\r\n\r\nDuring handling of the above exception, another exception occurred:\r\n\r\nTraceback (most recent call last):\r\n  File "c:\\users\\user\\appdata\\local\\programs\\python\\python35\\lib\\runpy.py", line 193, in _run_module_as_main\r\n    "__main__", mod_spec)\r\n  File "c:\\users\\user\\appdata\\local\\programs\\python\\python35\\lib\\runpy.py", line 85, in _run_code\r\n    exec(code, run_globals)\r\n  File "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python35\\Scripts\\toco_from_protos.exe\\__main__.py", line 5, in <module>\r\n  File "c:\\users\\user\\appdata\\local\\programs\\python\\python35\\lib\\site-packages\\tensorflow\\contrib\\lite\\toco\\python\\toco_from_protos.py", line 22, in <module>\r\n    from tensorflow.contrib.lite.toco.python import tensorflow_wrap_toco\r\n  File "c:\\users\\user\\appdata\\local\\programs\\python\\python35\\lib\\site-packages\\tensorflow\\contrib\\lite\\toco\\python\\tensorflow_wrap_toco.py", line 28, in <module>\r\n    _tensorflow_wrap_toco = swig_import_helper()\r\n  File "c:\\users\\user\\appdata\\local\\programs\\python\\python35\\lib\\site-packages\\tensorflow\\contrib\\lite\\toco\\python\\tensorflow_wrap_toco.py", line 20, in swig_import_helper\r\n    import _tensorflow_wrap_toco\r\nImportError: No module named \'_tensorflow_wrap_toco\'\r\n'
None
```

アカン。・・一回export_inference_graph.pyを見てみ・・

・・・

・・・

・・あれ？

なんだこのexport_tflite_ssd_graph.pyって。

```Python:export_tflite_ssd_graph.py
Example Usage (in which we change the NMS iou_threshold to be 0.5 and
NMS score_threshold to be 0.0):
python object_detection/export_tflite_ssd_graph \
    --pipeline_config_path path/to/ssd_mobilenet.config \
    --trained_checkpoint_prefix path/to/model.ckpt \
    --output_directory path/to/exported_model_directory
    --config_override " \
            model{ \
            ssd{ \
              post_processing { \
                batch_non_max_suppression { \
                        score_threshold: 0.0 \
                        iou_threshold: 0.5 \
                } \
             } \
          } \
       } \
       "
```

よし、叩いてみよう。

```cmd
python export_tflite_ssd_graph.py --pipeline_config_path data\ssd_mobilenet_v1_kame.config --trained_checkpoint_prefix data\train\model.ckpt-8032 --output_directory output_tflite
2019-02-17 16:25:37.619262: I tensorflow/tools/graph_transforms/transform_graph.cc:317] Applying strip_unused_nodes
```

なんかinformation的なの出たから失敗したかと思ったけど・・

```
ls -l output_tflite
total 83520
-rw-r--r-- 1 user 197121 22326651 Feb 17 16:25 tflite_graph.pb
-rw-r--r-- 1 user 197121 63193119 Feb 17 16:25 tflite_graph.pbtxt
```

なんか出来てる！
よし、これ使ってみるわ。
ただ、これ使ってもすんなりとtfliteへのconvertがうまくいかない。
むずいなー。・・一回windows諦めてlinux行っちゃいます。

```
$ uname -a
Linux wordpresspro-1-vm 4.9.0-8-amd64 #1 SMP Debian 4.9.110-3+deb9u6 (2018-10-08) x86_64 GNU/Linux
$ python --version
Python 3.6.6
$ pip --version
pip 18.1 from /home/takesiba_masrao/alexa/venv/lib/python3.6/site-packages/pip (python 3.6)
$ pip install protobuf keras numpy tensorflow==1.12.0
...
$ pip list | grep tensor
tensorboard         1.12.2
tensorflow          1.12.0
$ git clone https://github.com/tensorflow/tensorflow
$ mkdir -p models/mobilenet_v1_0.5_224
$ cd !$
$ wget http://download.tensorflow.org/models/mobilenet_v1_2018_02_22/mobilenet_v1_0.5_224.tgz
$ tar zxvf mobilenet_v1_0.5_224.tgz
./
./mobilenet_v1_0.5_224_info.txt
./mobilenet_v1_0.5_224.ckpt.index
./mobilenet_v1_0.5_224.ckpt.meta
./mobilenet_v1_0.5_224.ckpt.data-00000-of-00001
./mobilenet_v1_0.5_224.tflite
./mobilenet_v1_0.5_224_eval.pbtxt
./mobilenet_v1_0.5_224_frozen.pb
```

これでおおよそ使えるようになってるはずなので、試しに落としてきた学習済モデルでtflite_convertしてみる

```
$ tflite_convert \
  --output_file=./mobilenet_v1_0.5_224_test.tflite \
  --graph_def_file=mobilenet_v1_0.5_224_frozen.pb \
  --input_arrays=input \
  --output_arrays=MobilenetV1/Predictions/Reshape_1
2019-02-17 14:33:13.349865: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
$ ls -l *.tflite
-rw-r--r-- 1 takesiba_masrao takesiba_masrao 5319432 Feb 17 14:33 mobilenet_v1_0.5_224_test.tflite
-rw-r----- 1 takesiba_masrao takesiba_masrao 5319064 Feb 23  2018 mobilenet_v1_0.5_224.tflite
```

なんかInfo的なのは表示されたものの、たいだい元と同じようなのができた。じゃあ今度はローカルのやつを上げて実行してみよう。

```
$ tflite_convert \
  --output_file=./mobilenet_v1_0.5_224_my.tflite \
  --graph_def_file=output_tflite/tflite_graph.pb  \
  --input_arrays=normalized_input_image_tensor \
  --output_arrays='TFLite_Detection_PostProcess' \
  --inference_type=FLOAT \
  --inference_input_type=QUANTIZED_UINT8 \
  --input_shapes=1,227,227,3 \
  --mean_values=300 \
  --std_dev_values=300 \
  –-allow_custom_ops 

・・・・

tensorflow.lite.python.convert.ConverterError: TOCO failed. See console for info.
2019-02-17 17:33:01.889905: I tensorflow/lite/toco/import_tensorflow.cc:1324] Converting unsupported operation: TFLite_Detection_PostProcess
```

あかん。。わからんけどtocoってやつを直で実行する方法もあるらしいのでそちらを試す。
( https://github.com/tensorflow/models/issues/5019 )
tocoってなんかかわいらしい名前だけど、なんかの略だろうか。わからん。とりあえずTensorflowのConverterっぽい。TensOrflow COnverterではないよね、たぶん違う。

```bash
$ toco \
 --graph_def_file=output_tflite/tflite_graph.pb \
 --output_file=mobilenet_v1_0.5_224_my.tflite  \
 --input_format=TENSORFLOW_GRAPHDEF  \
 --input_shapes=1,224,224,3  \
 --output_format=TFLITE  \
 --input_arrays=normalized_input_image_tensor  \
 --output_arrays='TFLite_Detection_PostProcess'  \
 --inference_type=FLOAT  \
 --change_concat_input_ranges=false  \
 --allow_custom_ops \
```

でまあなんとかできた。
Tensorflow Liteで使うのに画像でかくない？とは思うけどどうなんだろ？224x224でトレーニングしとけばよかったな。まあ、一度試す。

- tensorflow\lite\java\demo\app\src\main\assetsに作ったファイル(*.tflite)置く
- build.gradleの```preBuild.dependsOn unzipModelFloat```は無効にしておいてmobilenetv1の学習済みモデルをDLしないようにする
- ImageClassifierFloatMobileNet.javaに直書きされてるモデルとラベルのファイル名置く
- ラベルファイル作って置く

こうやってビルドしてスマホに入れて実行・・ってやったのですが、自前のモデルに切り替えた途端にすぐ落ちる・・デバッグしようかとも思ったんだけど、android studioが重すぎて一回諦めました。シュミレーターとかまともに使えん。ただ、モデルが原因なのはほぼ間違いない。
ちなみにDLしてきたモデルは普通に使えてたけど、誤検知はけっこう多かった。
静止画を撮ってどうこうするアプリの方が現実的かなー。

ちょっと心が折れたので今回はここまで。。
失敗編としてみたけど、成功編があるかどうかは私にもわかりません。
というか他の環境でやりそう。


追記

改めてエラーを拾っておいた

```
E/AndroidRuntime: FATAL EXCEPTION: CameraBackground
    Process: android.example.com.tflitecamerademo, PID: 31045
    java.lang.IllegalStateException: Internal error: Unexpected failure when preparing tensor allocations: tensorflow/lite/kernels/detection_postprocess.cc:159 NumOutputs(node) != 4 (1 != 4)Node number 63 (TFLite_Detection_PostProcess) failed to prepare.
    
        at org.tensorflow.lite.NativeInterpreterWrapper.allocateTensors(Native Method)
        at org.tensorflow.lite.NativeInterpreterWrapper.init(NativeInterpreterWrapper.java:86)
        at org.tensorflow.lite.NativeInterpreterWrapper.<init>(NativeInterpreterWrapper.java:60)
        at org.tensorflow.lite.Interpreter.<init>(Interpreter.java:224)
        at com.example.android.tflitecamerademo.ImageClassifier.<init>(ImageClassifier.java:103)
        at com.example.android.tflitecamerademo.ImageClassifierFloatMobileNet.<init>(ImageClassifierFloatMobileNet.java:36)
        at com.example.android.tflitecamerademo.Camera2BasicFragment.lambda$updateActiveModel$0$Camera2BasicFragment(Camera2BasicFragment.java:355)
        at com.example.android.tflitecamerademo.Camera2BasicFragment$$Lambda$0.run(Unknown Source)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.os.HandlerThread.run(HandlerThread.java:61)
I/RequestQueue: Repeating capture request cancelled.
E/BufferQueueProducer: [SurfaceTexture-1-31045-1] cancelBuffer: BufferQueue has been abandoned
E/BufferQueueProducer: [SurfaceTexture-1-31045-1] cancelBuffer: BufferQueue has been abandoned
E/BufferQueueProducer: [SurfaceTexture-1-31045-1] cancelBuffer: BufferQueue has been abandoned
E/BufferQueueProducer: [SurfaceTexture-1-31045-1] cancelBuffer: BufferQueue has been abandoned
E/BufferQueueProducer: [SurfaceTexture-1-31045-1] cancelBuffer: BufferQueue has been abandoned
I/Process: Sending signal. PID: 31045 SIG: 9
Application terminated.
```

ググったらこういうのが見つかったし、

https://github.com/tensorflow/models/issues/5020

もしかしたらtoco実行する時に
output_arrays='TFLite_Detection_PostProcess','TFLite_Detection_PostProcess:1','TFLite_Detection_PostProcess:2','TFLite_Detection_PostProcess:3'
って指定すればいけるのかもしんない？

で、試した。

```
$ toco  --graph_def_file=output_tflite/tflite_graph.pb  --output_file=mobilenet_v1_0.5_224_my.tflite   --input_format=TENSORFLOW_GRAPHDEF   --input_shapes=1,224,224,3   --output_format=TFLITE   --input_arrays=normalized_input_image_tensor   --inference_type=FLOAT   --change_concat_input_ranges=true   --allow_custom_ops --output_arrays='TFLite_Detection_PostProcess','TFLite_Detection_PostProcess:1','TFLite_Detection_PostProcess:2','TFLite_Detection_PostProcess:3'
```

そしたらエラーは変化。

```
2019-02-22 00:26:11.946 1128-1146/android.example.com.tflitecamerademo E/AndroidRuntime: FATAL EXCEPTION: CameraBackground
    Process: android.example.com.tflitecamerademo, PID: 1128
    java.lang.IllegalArgumentException: Cannot copy between a TensorFlowLite tensor with shape [1, 10, 4] and a Java object with shape [1, 1].
        at org.tensorflow.lite.Tensor.throwIfShapeIsIncompatible(Tensor.java:282)
        at org.tensorflow.lite.Tensor.throwIfDataIsIncompatible(Tensor.java:249)
        at org.tensorflow.lite.Tensor.copyTo(Tensor.java:141)
        at org.tensorflow.lite.NativeInterpreterWrapper.run(NativeInterpreterWrapper.java:161)
        at org.tensorflow.lite.Interpreter.runForMultipleInputsOutputs(Interpreter.java:275)
        at org.tensorflow.lite.Interpreter.run(Interpreter.java:249)
        at com.example.android.tflitecamerademo.ImageClassifierFloatMobileNet.runInference(ImageClassifierFloatMobileNet.java:92)
        at com.example.android.tflitecamerademo.ImageClassifier.classifyFrame(ImageClassifier.java:126)
        at com.example.android.tflitecamerademo.Camera2BasicFragment.classifyFrame(Camera2BasicFragment.java:825)
        at com.example.android.tflitecamerademo.Camera2BasicFragment.access$1000(Camera2BasicFragment.java:75)
        at com.example.android.tflitecamerademo.Camera2BasicFragment$8.run(Camera2BasicFragment.java:718)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.os.HandlerThread.run(HandlerThread.java:61)
2019-02-22 00:26:11.992 1128-1146/android.example.com.tflitecamerademo I/Process: Sending signal. PID: 1128 SIG: 9
```

shape の指定がアカンのかなー。

また暇な時にためす。