【初心者向け】Tensorflow Object Detection API を使ってみた

# はじめに
RHEMS技研のIchiLabです。

今回はTensorFlowのObject Detection APIを使って、
自分が認識してほしい物体を検出させ、
最終的にAndroid端末でそれを試すというところまでやってみました。

多くの参考になるサイトは自分のためになりましたが、
それでも私は1つ2つだけのページだけでは解決できない点がとても多かったです。

本記事では、私が大なり小なりつまづいた部分をピックアップしながら記載しますので、
同じような苦労をする方が少しでも減ることがあれば幸いです。

追記：Cloud TPUを使用する場合の記事も書きました（2020/6/19~)
[【TPUを使いたい人向け】Tensorflow Object Detection API を使ってみた 2](https://qiita.com/IchiLab/items/6eb09270c46accfa05df)

追記２：TensorFlow2.0で学習させる工程も追記しました。（2020/9/30~)
TF1については本記事では執筆時点のソースコードで説明したものになりますので、参考にする場合は説明に記載しているブランチでお試し下さい。

TensorFlow 1.15を`TF1`、TensorFlow 2.3.1を`TF2`として以下に記載していきます。

# 環境
## TF1
TensorFlow 1.15

MacBook Pro
OS : Catalina 10.15
CPU : Intel Core i7 3.5GHz
Memory : 16GB 2133MHz

## TF2
TensorFlow 2.3.1

Google Colabratory (GPU)

# 環境構築
基本的には下記の公式に書いてあるとおりです。  
## TF1

[Object Detection API with TensorFlow 1](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf1.md)  

TF1については下記ブランチのソースコードをもとに記載しております。
（2019/11/9　本記事を執筆した時期に合わせております）

```console
$ git clone -b tf_2_1_reference https://github.com/tensorflow/models.git
```
PYTHONPATHを設定するところでは、`pwd`で説明されていますが、省略せず記述すると以下のとおりです。  
（`tensorflow`ではなく`tf`がルートディレクトリであるコンテナも存在しますので要確認）  

```console
$ export PYTHONPATH=$PYTHONPATH:/tensorflow/models/research:/tensorflow/models/research/slim
```
## TF2
ローカル環境でももちろん構築は可能ですが、ここでは誰でも無料で高性能のGPUが使えてかつ環境構築が容易なColab環境で実施していくことにします。

[Object Detection API with TensorFlow 2](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2.md) 

### Google Colabratoryでの環境構築
Colabでは初めにランタイムの設定をします。
`「ランタイム」`→`「ランタイムのタイプを変更」`→ハードウェア アクセラレータを`「GPU」`にします。

TF2では最新のソースをcolabの環境に持ってきます。

```python

import os
import pathlib

if "models" in pathlib.Path.cwd().parts:
  while "models" in pathlib.Path.cwd().parts:
    os.chdir('..')
elif not pathlib.Path('models').exists():
  !git clone --depth 1 https://github.com/tensorflow/models
```

インストールします。

```console
%%bash
cd models/research/
protoc object_detection/protos/*.proto --python_out=.
cp object_detection/packages/tf2/setup.py .
python -m pip install .
```

今回は教師データやモデルの保存場所をGoogle Driveにしたいので、Colabからgdriveを読めるように設定します。

```console
from google.colab import drive
drive.mount('/content/drive')
```


## 環境構築する上での注意点（TF2対応前にあった注意点）
（※本項目は当時TF2対応前のソースで起こった問題です。現在はTF1とTF2が共存され解決されています。）

もしDockerを使用してコンテナ内で動かす場合は、下記の公式ページの通り色々種類がありますが、
もし`latest`（最新版）を使用する場合は注意が必要です。  

[Tensorflow - Docker](https://www.tensorflow.org/install/docker?hl=ja)  
  
なぜなら、執筆時点の2019年11月現在では**Object Detection APIは
TF2には対応していない**からです。
（2020/7/10より対応されました）

参考：[TensorFlow 2 meets the Object Detection API](https://blog.tensorflow.org/2020/07/tensorflow-2-meets-object-detection-api.html)


`pip`コマンドでのインストール時も同様、TensorFlowのバージョン確認は必須です。  
確認したい場合は、以下のようにコマンドを打つと良いでしょう。

```console
$ pip list | grep tensor
tensorboard                        1.15.0     
tensorflow                         1.15.0rc3  
tensorflow-estimator               1.15.1 
```

もしバージョンをTF2からTF1に変更したい場合は、以下のコマンドを実行しましょう。
（今回の例では1.15に変更）

```console
$ pip install tensorflow==1.15.0rc3
```
ちなみに、このバージョンの違いは結構重要なので補足しますが、
TensorFlowの公式では一応バージョンが2.0でも自動変換してくれるスクリプトを用意してくれています。
ただし、`except for contrib`（contribを除く）と記載されています。  

[Migrate your TensorFlow 1 code to TensorFlow 2](https://www.tensorflow.org/guide/migrate)  

もしTensorFlow2.0でObject Detection APIの`model_builder_test.py`を実行すると、
次のようになり失敗します。  

```console
AttributeError: module 'tensorflow' has no attribute 'contrib'
```
contribでエラーに引っかかっていますね。
つまり自動変換のスクリプトは今のところ使えないということです。  

さて、無事`model_builder_test.py`は実行してうまくOKと表示されましたでしょうか？
`model_builder_test.py`は、`models/research`ディレクトリで以下を実行します。

### TF1

```console
$ python object_detection/builders/model_builder_test.py

（最新ソースは以下の名前に変更されている）
$ python object_detection/builders/model_builder_tf1_test.py
```
成功すると、以下のようにOKと表示されます。（長いので一部省略してます）

```console
Running tests under Python 3.6.8: /usr/local/bin/python
[ RUN      ] ModelBuilderTest.test_create_faster_rcnn_model_from_config_with_example_miner  
...    

[ RUN      ] ModelBuilderTest.test_unknown_ssd_feature_extractor
[       OK ] ModelBuilderTest.test_unknown_ssd_feature_extractor
----------------------------------------------------------------------
Ran 16 tests in 0.313s
```

### TF2

```
!python /content/models/research/object_detection/builders/model_builder_tf2_test.py
```

成功すると、以下のようにOKと表示されます。

```console
Running tests under Python 3.6.9: /usr/bin/python3
[ RUN      ] ModelBuilderTF2Test.test_create_center_net_model
...   
[       OK ] ModelBuilderTF2Test.test_unknown_ssd_feature_extractor
----------------------------------------------------------------------
Ran 20 tests in 46.313s

OK (skipped=1)
```

環境構築は以上です。
# 教師データの準備
自分が認識してほしい物体を検出させるには、教師データを作成します。
教師データを作るためには、たくさんの検出させたい画像を用意しなければなりません。  
  
もし自力で集めるのが大変な場合は、`google-images-download`という便利なツールも存在するので、
興味のある方はぜひ調べて使ってみて下さい。  
  
さて、教師データの説明です。
教師データは、TensorFlow推奨の<b>TFRecord形式</b>として用意します。
TFRecordは、バイナリレコードのシリーズを保存するための単純な形で、
データがシリアライズ化（配列データとして保存）されて連続的に読み込めるようになっています。  
TFRecord形式のデータを作るためには、大まかに2通りの方法があります。
1. アノテーションツールを使用する
2. ソースから自分で作る

アノテーションとは、いわゆるタグ付けです。
写真や動画を表示しながら、「この部分がこれだよ」と教えていく作業です。
私はこのために<b>VoTT</b>というMicrosoftから出ているツールを使用しました。
[VoTT](https://github.com/Microsoft/VoTT/releases)  

  
## VoTTの使用上の注意

* Export Settingsでは**Tensorflow Records**を選択する
* Asset StateではOnly visited Assets<b>以外</b>に設定する
* Active Learning（帽子のようなマーク）は押すとせっかくつけたタグ付けが消えることがあるので、あまり使用をおすすめしない
* 1枚の画像に大量のタグ付けをすると処理が重くなるので、その場合は何枚かに切り取って分けたほうが良い  

このように、色々と使いにくい点が目立つツールではありますが、
TFRecord形式に変換する面倒がなくなりますので、その点ではおすすめです。
## ソースから自分で作る場合
TensorFlowで用意されているライブラリで、画像からTFRecord形式のファイルを生成することは可能です。
しかし、以下の公式チュートリアルで生成されるTFRecordと、
VoTTで生成されるTFRecordは若干要素名が異なり、一緒に混ぜて学習させることはできませんでした。
[TFRecords と tf.Example の使用法](https://www.tensorflow.org/tutorials/load_data/tfrecord)

こちらのドキュメントの方が役立つでしょう。
[using_your_own_dataset.md](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/using_your_own_dataset.md)

この辺の話は、長くなるので別記事で私がまとめみましたので、宜しければご覧ください。
[物体検出のためのTFRecordファイル作成備忘録](https://qiita.com/IchiLab/items/8ad0cdb9c2006f416c3b)


# 学習

ここからの説明について、ディレクトリは全て`models/research`を基準にすることを前提として記載しています。

TFRecord形式のファイルが準備出来たら、いよいよ学習の開始です。
ここでは「転移学習」と呼ばれる、既存の学習モデルをもとに自分のオリジナルを学習させて、
よりカスタマイズされた学習モデルを作るという手法で進めていきます。  
転移学習は、大規模なデータで事前に学習した重みを用いることで、
少量の訓練データでも十分な性能を得られることが期待されます。
## 学習モデルのダウンロード  
まず、転移学習のための学習済みモデルをダウンロードします。

学習済みモデルは、以下のページからダウンロードできます。 
なお、これらはTF1とTF2でモデルの構造が違いますので、バージョンの違うmodelで学習に使用することは出来ません。ご注意を。

[TensorFlow 1 Detection Model Zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf1_detection_zoo.md) 

[TensorFlow 2 Detection Model Zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2_detection_zoo.md)

### TF1
今回は`MobileNet v3`を使って進めていきましょう。
この中で、`ssd_mobilenet_v3_large_coco`というものをダウンロードします。
  
ダウンロードしたモデルは、`object_detection`ディレクトリに配置しましょう。
コマンドで実行するなら、以下のようにします。

```console
$ wget http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v3_large_coco_2020_01_14.tar.gz
$ tar zxvf ssd_mobilenet_v3_large_coco_2020_01_14.tar.gz
```

### TF2
今回は`EfficientDet D1`を試してみました。
本モデルは[まだtfliteへの変換には対応していない](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/running_on_mobile_tf2.md)そうなので、注意が必要です。

## 学習モデルのファイル構成  
学習モデルのファイル構成は、基本的に以下のようになっています。

`MobileNet v3`
-checkpoint
- frozen_inference_graph.pb
- model.ckpt.data-00000-of-00001
- model.ckpt.index
- model.ckpt.meta
- pipeline.config
- saved_model /
    - saved_model.pb
	- variables / 

`EfficientDet D1`
- checkpoint /
    - checkpoint
    - ckpt-0.data-00000-of-00001
    - ckpt-0.index
- pipeline.config
- saved_model /
    - assets /
    - saved_model.pb
    - variables /
        - variables.data-00000-of-00001
        - variables.index

転移学習を始める際は、この中の`pipeline.config`というファイルの中身を編集して使用します。
  
## トレーニングと評価のためのディレクトリ構造とファイル名
ここでは用意したTFRecordファイルのディレクトリについて説明します。
TFRecordファイルは、訓練（教師）用と検証用にディレクトリを分けます。  
一般的に、比率は訓練：検証が8:2、つまり訓練用をデータの80％、残りの20％を検証用に分けるとされています。
（書籍[「scikit-learnとTensorFlowによる実践機械学習」](https://www.oreilly.co.jp/books/9784873118345/) P30より）
もちろん、きちんとその割合で分けていなくても一応学習は可能です。  
以下、ディレクトリ構造です。（trainはtraining, valはvalidationの略）

- test0001 /
    - label_map (.pbtxt)　ラベルファイル
    - train /
        - TFRecord file（.tfrecord）訓練用ファイル
    - val /
        - TFRecord file（.tfrecord）検証用ファイル
    - save /
        - 学習したデータを保存するために用意したディレクトリ（最初は空でOK）

TFRecordファイル名は、バラバラではなく連番で統一させておきます。
例えば、ここでは次のように名付けておきます。

* hoge0000.tfrecord
* hoge0001.tfrecord
* hoge0002.tfrecord
* ...(以下同様に)
  
ここでのファイル名の付け方は、次のコンフィグファイルの設定に関連してきます。
## コンフィグファイルの編集
先程ダウンロードしてきた学習済みモデルの中にある、`pipeline.config`を開きます。
ここではバッチサイズや重み付け、画像の拡張など様々なパラメータを調整することが出来ますが、よく編集するであろう項目の説明をしていきます。
(説明のために実際のconfigから抜粋しています)

```json:pipeline.config
model {
    ssd {
        num_classes: 1
```
`num_classes` :
分類する数の設定です。
コンフィグファイルの比較的上部に書かれています。

```json:pipeline.config
train_config: {
    batch_size: 32
    num_steps: 10000
    optimizer {
        momentum_optimizer: {
            learning_rate: {
                cosine_decay_learning_rate {
                    total_steps: 10000
                    warmup_steps: 10000
    fine_tune_checkpoint: "./object_detection/ssd_mobilenet_v3_large_coco/model.ckpt"
}
```
`batch_size` : 
確率的勾配降下法を使う場合、異常値の影響を小さくするために、
データセットを幾つかのサブセットに分けて学習させます。
そのぞれぞれのサブセットに含まれるデータの数をバッチサイズと呼びます。
この値は機械学習の分野の慣習として2のn乗の値が使われることが多いです。
そして、この値は大きくするほど学習時に負荷がかかり、環境によってはプロセスが死んで学習してくれなくなることがあります。

`num_steps` :
学習を行うstep数です。
学習を実施する際のコマンドでもstep数は指定することができ、
私が試した限りではコマンドの指定が優先されます。

`total_steps`と`warmup_steps` :
他のモデルのconfigになかった項目なので調査中ですが、
total_stepsは、warmup_steps以上である必要があります。
（この条件に満たないとエラーが出て学習が始まりません）

`fine_tune_checkpoint` :
転移学習のモデルを指定します。
ダウンロードした学習モデルのあるディレクトリを「〜.ckpt」まで書けばOKです。
ssd_mobilenet_v3をダウンロードした時点ではこの項目が入っていませんでした。
そのため、私は自分で追加しました。
大抵のモデルには元からこの項目が存在します。  
転移学習をしない場合はこの行は不要になります。

```json:pipeline.config
train_input_reader: {
    tf_record_input_reader {
        input_path: "./object_detection/test0001/train/hoge????.tfrecord"
    }
    label_map_path: "./object_detection/test0001/tf_label_map.pbtxt"
}
eval_input_reader: {
    tf_record_input_reader {
        input_path: "./object_detection/test0001/val/hoge????.tfrecord"
    }
    label_map_path: "./object_detection/test0001/tf_label_map.pbtxt"
}
```
`input_path` :
用意した訓練用と検証用のTFRecordファイルのディレクトリを指定します。
例えば、今回の場合は`hoge0000.tfrecord`という連番で名付けたので、`hoge????.tfrecord`と書きます。  
  
`label_map_path` :
用意したラベルを指定します。trainもevalも同じ指定で問題ありません。  

## 学習開始
configファイルを書いたら、いよいよ学習の開始です。

### TF1で学習
（※説明はtf_2_1_referenceのブランチのソースコードを元にしています。
現在でも私はこのソースでうまく動作を確認出来ていますので、執筆当時のままに残しておきます。）

学習には、`object_detection`ディレクトリにある`model_main.py`というファイルを使用します。
実行時の引数の説明です。  

* `--model_dir`：
学習データの保存先を指定します。
指定したディレクトリには、「.ckpt~」というファイルが学習中に作られていきます。

* `--pipeline_config_path`：
使用するconfigファイルを指定します。
先程編集したコンフィグファイルを指定しましょう。
* ` --num_train_steps`：
学習回数を指定します。
configで指定した数を実施するならこのオプションは不要ですが、
もしconfigとは別の回数で学習させたい場合は、こちらで指定した回数が優先されます。（実際に試した結果）  
  
以下、実行例です。

```console
$ python object_detection/model_main.py \
--pipeline_config_path="object_detection/ssd_mobilenet_v3_large_coco/pipeline.config" \
--model_dir="./object_detection/test0001/save" \
--alsologtostderr
```

オプションが多くていちいち入力するのは面倒ですので、シェルスクリプトにしておくと、実行が楽になります。  

```sh
#! /bin/bash

PIPELINE_CONFIG_PATH="./object_detection/ssd_mobilenet_v3_large_coco.config"
MODEL_DIR="./object_detection/test0001/save"
NUM_TRAIN_STEPS=10000

cd '/tensorflow/models/research'

python object_detection/model_main.py \
    --pipeline_config_path=$PIPELINE_CONFIG_PATH \
    --model_dir=$MODEL_DIR \
#    --num_train_steps=$NUM_TRAIN_STEPS \
    --alsologtostderr
```
「あれ、作ったシェルが実行できない…？」
という方はパーミッションの変更を忘れていませんか？

```console
$ chmod 775 hoge.sh
```

#### 学習開始するもプロセスが途中で死んでしまう場合
学習が始まったと思ったら、下記のように起こる場合があります。

```console
~ session_manager.py:500] Running local_init_op.
~ session_manager.py:502] Done running local_init_op.
~  basic_session_run_hooks.py:606] Saving checkpoints for 0 into {チェックポイントを保存指定したディレクトリ/ファイル名}.ckpt
Killed
```
プロセスが死んでしまう理由はいくつかありますが、
いずれも動作環境のリソースが足りなくなって起こっている可能性が高いです。

ここでは、私がこの事象を経験した時に解決した手段を紹介します。

##### 〜解決策その1．Docker Engineの設定を変更してみる〜
これはDockerコンテナ内で実施している場合に有効かもしれない手段です。
まず設定を開きます。
<b>「Preferences」→「Advanced」タブ</b>を選択し、リソースを増やしてみましょう。


##### 〜解決策その2．バッチサイズを小さくしてみる〜
これはpipeline.configの設定によって解決します。

どのようなパラメータの組み合わせでプロセスが死んでしまうかは、
動作環境や使用した教師データの量やサイズによって違うと思います。
まずはバッチサイズの値を小さくすることで解決できることがあるので、
ここで悩んだ人は試してみましょう。
#### 学習データの確認  
学習データは、`model_main.py`を実行したときに指定した保存先のディレクトリに随時保存されていきます。
学習中に下記のような`Saving`と表示されるタイミングでデータが保存されていきます。
この段階で、次で説明する`Tensorboard`で学習中の状況が可視化できます。

```console
INFO:tensorflow:Saving 'checkpoint_path' summary for global step 500: object_detection/test0001/save/model.ckpt-500
I1012 08:29:56.544728 139877141301056 estimator.py:2109] Saving 'checkpoint_path' summary for global step 500: object_detection/test0001/save/model.ckpt-500
```

### TF2で学習
TF2ではColabを使用して学習する方法を記載します。

Colabでは、変数をフォーム入力で出来る機能がありますので、それを使用しました。

```console
#@title params
pipeline = "/content/drive/My\\ Drive/tf2/pipeline.config" #@param {type:"string"}
save = "/content/drive/My\\ Drive/tf2/save/" #@param {type:"string"}
num_steps = 10000 #@param {type:"integer"}
last_model_path = "/content/drive/My\\ Drive/tf2/efficientdet_d1_coco17_tpu-32/checkpoint/ckpt-0" #@param {type:"string"}

!python /content/models/research/object_detection/model_main_tf2.py \
    --pipeline_config_path={pipeline} \
    --model_dir={save} \
    --alsologtostderr \
    --num_train_steps={num_steps} \
    --trained_checkpoint_dir={last_model_path}
```

学習には、`object_detection`ディレクトリにある`model_main_tf2.py`というファイルを使用します。
引数は基本的にTF1と同様です。


# Tensorboard〜学習の可視化〜
学習データの可視化は必須であり、Tensorboardはそれにとても役立ちます。
## Tensorboardの起動
tensorboardは以下のようなコマンドで起動できます。

```console
$ tensorboard --logdir=object_detection/test0001/save
```

* `--logdir` :
学習開始時に指定した、**学習データの保存先のディレクトリをここで指定**します。  
過去に実施した学習結果の保存先を指定すれば、学習終了後でも再度見ることが可能です。
  
TensorboardをWebブラウザで確認するには、ローカルホストへアクセスします。
デフォルトではポート番号は6006です。

```console
http://localhost:6006/
```  
もしもDockerコンテナの環境で実行している場合は、`docker run`する時に`-p 6006:6006`とアクセスできるようにしておきましょう。
私は`docker-compose.yml`にポートの設定を記載しています。  

## ColabでのTensorboardの起動
ColabでもTensorBoardを使用することが出来ます。
ポートを意識しなくても良いので楽ですね。

```console
%load_ext tensorboard
%tensorboard --logdir /content/drive/My\\ Drive/tf2/save
```

## Tensorboardの見方  
せっかく学習を可視化できても、見方がさっぱり分からないと辛いですよね。
私なりに調べた内容をまとめてみましたので、参考にして頂ければと思います。
### GRAPHS
ソースコードの処理からグラフが自動生成されます。

|項目 |説明 |
|---|---|
|Run|ログが置かれているサブディレクトリを切り替えられる|
|Upload|Tensorflowのモデルファイルをアップロード|
|Trace inputs|ノードの依存関係を追いかけられる|
|Color|色分け方法を選ぶ<br>- Structure : モデル（ネットワーク）構成<br>- Device : 処理を行ったデバイス（CPU vs GPU）<br>- Compute time : 処理時間<br>- Memory : メモリ使用量<br>- TPU Compatibility : tensor processing unit上で実行

### SCALARS
|項目 |説明 |
|---|---|
|Show data download links|グラフを保存できるリンクを表示させる。CSVかJSON形式を選択できる。|
|ignore outliners in chart scaling|外れ値を避けてグラフをスケーリングするかどうか（チェックを入れると避ける）|
|Tooltip sorting method|ツールチップの並び順<br>- default : アルファベット順<br>- descending :値の大きい順<br>- ascending : 値の小さい順<br>- nearest : マウスカーソルに近い順<br>|
|Smoothing|グラフのスムージング|
|Horizontal Axis|折れ線グラフの横軸（X軸）の指定<br>- STEP : ステップ（実行回数）<br>- RELATIVE : 実行時間（初回からの差分）<br>- WALL : 時刻<br>- Runs : グラフの表示・非表示の切り替え<br>|

  
参考：[TensorBoardによる学習の可視化](
https://www.slideshare.net/aitc_jp/20180127-tensorboard)
続いて、主要な各グラフについての説明です。
その前に、説明上知っておく必要がある単語の説明を記載しておきます。

* **IOU** :  
IOUとは、Intersection(領域の共通部分) over Union(領域の和集合)の略で、2つの領域が「どれくらい重なっているか」を表す指標のことです。  
この値は1に近いほど教えた正解と推論が一致していることになります
* **Recall** :  
再現率。見つけるべきもののうち、正しく見つけることができたものの割合。Sensitivity(敏感度)ともいう。


参考：[IoU（評価指標）の意味と値の厳しさ](https://mathwords.net/iou)
参考：[【入門者向け】機械学習の分類問題評価指標解説(正解率・適合率・再現率など)](https://qiita.com/FukuharaYohei/items/be89a99c53586fa4e2e4)  

以下に、`object_detection/metrics/coco_tools.py`に書いてある項目の説明を参考に抜粋します。  


|項目 |説明 |
|---|---|
|Precision/mAP|5刻みで5〜95の範囲のIOUしきい値を平均したクラスの平均精度。|
|Precision/mAP@\.50IOU|50％IOUの平均精度|
|Precision/mAP@\.75IOU|75％IOUの平均精度|
|Precision/mAP (small)|小さなオブジェクトの平均精度（32×32 px未満）|
|Precision/mAP (medium)|中サイズのオブジェクトの平均精度 (32×32 px〜96×96 px)|
|Precision/mAP (large)|大きなオブジェクトの平均精度 (96×96 px〜10000×10000 px)|
|Recall/AR@1|1回の検出で正しく見つけられた平均の割合|
|Recall/AR@10|10回の検出で正しく見つけられた平均の割合|
|Recall/AR@100|100回の検出で正しく見つけられた平均の割合|
|Recall/AR@100 (small)|100回検出されたsmallオブジェクトの平均再現率|
|Recall/AR@100 (medium)|100回検出されたmediumオブジェクトの平均再現率|

### IMAGES

画像データを表示します。
検証用データの正解と推論結果の差が、.ckptファイル（チェックポイント）単位で確認できます。  

# 推論グラフへの変換  
学習が終了したら、いよいよ推論グラフへデータを変換します。
具体的には、学習して作られた.ckptファイルを、.pbファイルへ変換します。
  
なお、変換するためのソースは`object_detection`ディレクトリ内に複数用意されています。
* `export_inference_graph.py`
* `export_tflite_ssd_graph.py`  

**Androidで試すには、**.tfliteという形式に変換しますので、**後者の方を使用**します。

#### `export_inference_graph.py`の場合
実行時の引数の説明です。  

* `--input_type` :
推論グラフには、ユーザーに応じて次の3つのいずれかを指定します
    * `image_tensor` :  
    4次元テンソル [None, None, None, 3]  
    通常これを指定すれば良いと思われます。
    * `encoded_image_string_tensor` : 1次元テンソル [None]  
    エンコードされたPNGまたはJPEG画像を含む。  
    複数の画像が提供される場合、画像の解像度は同一であることを想定します。
    * `tf_example` : 1次元文字列テンソル [None]  
    シリアル化されたTFExample protosを含む複数の画像が提供される場合、画像の解像度は同一であることを想定します。  
    
* `--pipeline_config_path` :
学習時に使用したconfigファイルを指定します。  
* `--trained_checkpoint_prefix` :
学習データの保存先にできた「model.ckpt-XXXX」ファイルを指定します。
XXXXには実施した学習Step数の1番最新（大きいもの）を指定します。  
例えば、10000回実施したなら「model.ckpt-10000」となります。
* `--output_directory` :
書き出したいディレクトリを指定します。
指定したディレクトリには、最終的に初めのダウンロードしたモデルのファイル構成と全く同じ構成のファイルが出来上がります。  
  
以下、実行例です。

```console
$ python object_detection/export_inference_graph.py \
--input_type image_tensor \
--pipeline_config_path object_detection/ssd_mobilenet_v3_large_coco/pipeline.config \
--trained_checkpoint_prefix object_detection/test0001/save/model.ckpt-10000 \
--output_directory object_detection/test0001/output
```
#### `export_tflite_ssd_graph.py`の場合
学習済みモデルを.tflite互換のモデルに変換するプログラムです。
引数は基本的に`export_inference_graph.py`と同様です。 
（`--input_type`はありません）
  
以下、実行例です。

```console
$ python object_detection/export_tflite_ssd_graph.py \
--pipeline_config_path=object_detection/ssd_mobilenet_v3_large_coco/pipeline.config \
--trained_checkpoint_prefix=object_detection/test0001/save/model.ckpt-10000 \
--output_directory=object_detection/test0001/tflite \
--add_postprocessing_op=true
```
`--output_directory`で指定したディレクトリには、`tflite_graph.pb`と`tflite_graph.pbtxt`の2つのファイルが出来ます。

#### `exporter_main_v2.py.py`の場合(TF2)
TF2の場合はこちらのプログラムを使用します。
以下はColabでフォーム入力対応したものです。

```console
#@title params
pipeline = "/content/drive/My\\ Drive/tf2/pipeline.config" #@param {type:"string"}
save = "/content/drive/My\\ Drive/tf2/save/" #@param {type:"string"}
export_dir = "/content/drive/My\\ Drive/tf2/export" #@param {type:"string"}

!python /content/models/research/object_detection/exporter_main_v2.py \
 --input_type encoded_image_string_tensor \
 --trained_checkpoint_dir {save} \
 --output_directory {export_dir} \
 --pipeline_config_path {pipeline}
```

# TensorFlow Lite形式へ変換する
（※本項目はまだTF1版のみ記載しています。
TF2版はまだ試せていませんが、以下のリンクが公式の説明です）

[Running TF2 Detection API Models on mobile](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/running_on_mobile_tf2.md)

tflite形式へ変換するには、`tflite_convert`を使用します。
このコンバーターは元から入っているはずです。

`tflite_convert --help`を打つと、usageが出てきてくれます。
追加するオプションが多いので、私は以下のようにシェルスクリプトでまとめました。
ディレクトリは自分に合わせて変更して下さい。  
なお、`--input_shapes`はおそらく学習時のconfigの`image_resizer`に合わせた値で良いと認識していますが、確証はありません…
数字の意味は、(バッチサイズ、入力画像の高さ、入力画像の幅、入力画像の深度（RGBチャンネル）)となります。

[Converter command line reference](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/r1/convert/cmdline_reference.md)

```sh
#! /bin/bash

OUTPUT_FILE="object_detection/test0001/tflite/test.tflite"
GRAPH_DEF_FILE="object_detection/test0001/tflite/tflite_graph.pb"
INTERFACE_TYPE="FLOAT"
INPUT_ARRAY="normalized_input_image_tensor"
OUTPUT_ARRAYS="TFLite_Detection_PostProcess,TFLite_Detection_PostProcess:1,TFLite_Detection_PostProcess:2,TFLite_Detection_PostProcess:3"
INPUT_SHAPES="1,300,300,3"

cd '/tensorflow/models/research'

tflite_convert \
    --output_file=$OUTPUT_FILE \
    --graph_def_file=$GRAPH_DEF_FILE \
    --inference_type=$INTERFACE_TYPE \
    --input_arrays=$INPUT_ARRAY \
    --input_shapes=$INPUT_SHAPES \
    --output_arrays=$OUTPUT_ARRAYS \
    --default_ranges_min=0 \
    --default_ranges_max=6 \
    --mean_values=128 \
    --std_dev_values=127 \
    --allow_custom_ops
```
なお、他のサイトで調べると`bazel`や`toco`をインストールする説明を複数見つけましたが、それらをしなくてもこのコマンドラインだけで可能でした。
(正確にはそれらの方法も試しましたが、同じ結果でした)

なお、この時TensorFlowのバージョンが**1.12.0rc0だと失敗**しましたが、
**1.15.0rc3**にすると全く同じコマンドで**うまくいきました**。

  
なお、公式ではPython APIを推奨する記述がされていました。
[Converter command line reference](https://www.tensorflow.org/lite/convert/cmdline)  
  
# Androidで実際に動かしてみる
大変長くなりましたが、いよいよ本番です。
### Android Studioのインストール  

まずは「[Android Studio](https://developer.android.com/studio/)」をインストールしておきましょう。
他のツールの方が使いやすい方はそちらでも構いません。
### 公式からサンプルをダウンロード
次に、公式からサンプル集をダウンロードしておきます。

```console
$ git clone --depth 1 https://github.com/tensorflow/examples.git
```
### サンプルの修正  

Android Studioを起動したら、`examples/lite/examples/object_detection/android`を開きます。  
  
#### 自分のtfliteファイルを入れる
`examples/lite/examples/object_detection/android/app/src/main/assets`ディレクトリに、自分の`test.tflite`と`labelmap.txt`を配置します。
`labelmap.txt`は、自分でタグ付けしたタグ名が羅列されたテキストファイルです。

例えば、「apple」と「orange」という2種類のタグ名を用意したなら、次のようなテキストファイルになります。

```txt:labelmap.txt
???
apple
orange
```
重要なのは、1行目は`???`にすることです。

#### DetectorActivity.javaの編集
長いですが、`examples/lite/examples/object_detection/android/app/src/main/java/org/tensorflow/lite/examples/detection/`ディレクトリにある`DetectorActivity.java`を編集します。

```java:DetectorActivity.java
private static final boolean TF_OD_API_IS_QUANTIZED = false; //true->false
private static final String TF_OD_API_MODEL_FILE = "test.tflite"; //detect.tflite -> my tflite
private static final String TF_OD_API_LABELS_FILE = "file:///android_asset/labelmap.txt"; //my txt
```
  
次に、`examples/lite/examples/object_detection/android/app/`ディレクトリにある`build.gradle`を編集します。  
  
40行目あたりにある以下をコメントアウトします。  
（ここをコメントアウトしないと、ビルドした時にデフォルトのサンプルデータに置き換わってしまいます）

```groovy:build.gradle
apply from:'download_model.gradle' //コメントアウトする
```
#### ビルドする
ここまで出来たらあとはビルドして端末で動かして見るだけです！
うまく試すことが出来たならおめでとうございます。


以下、Androidに関する参考サイト

[Training and serving a realtime mobile object detector in 30 minutes with Cloud TPUs](https://medium.com/tensorflow/training-and-serving-a-realtime-mobile-object-detector-in-30-minutes-with-cloud-tpus-b78971cf1193)
[How to Train Your Own Custom Model with Tensorflow Object Detection API and Deploy It into Android with TF Lite](https://medium.com/datadriveninvestor/how-to-train-your-own-custom-model-with-tensorflow-object-detection-api-and-deploy-it-into-android-aeacab7fa76f)
[Detecting Pikachu on Android using Tensorflow Object Detection](https://towardsdatascience.com/detecting-pikachu-on-android-using-tensorflow-object-detection-15464c7a60cd)  
# おわりに
いかがでしたでしょうか？
他のサイトのまとめページみたいに仕上がってしまいましたが、
少なくともTensorFlowのObject Detection APIを使用する上で必要そうな情報はかなり網羅出来たのではないかと思います。  
  
特にTensorFlowはバージョンの違いで細かい挙動が異なり、
様々な参考サイトは当時使用しているバージョンがそれぞれ異なるので、同じ苦労をされた方も多いのではないかと思います。  
  
まだまだ私自身も理解できていない部分が多いですが、少しでもお役に立てれば幸いです。
長文記事を読んで頂きありがとうございました。