TensorFlowで独自の物体検出 (Object detection) モデルを作ります。
出来上がるまで1ヶ月ほど掛かりました。ホントエラーの連続で大変だった。
今回はポケGOのポケストップを検出するモデルを作って行きます。
最終目標はAndroidで動かすこと!
環境
- Windows 11 Home 21H2
- Docker 20.10.12
- TensorFlow 1.15.2
- Python 3.6.9
- LabelImg 1.8.1
準備
作業用フォルダー作成
機械学習させるうえで、たくさんのファイルが必要になります。
手に負えなくなる前にファイルを振り分けていきます。
今回は「📂pokego」という作業用フォルダーを作成し、必要なフォルダーを数個作成しました。
ここまでのフォルダー構成
1 2 3 4 5 6 7 8 9 10
| 📂pokego ┣📂Data ┃ ┗📂JPEGImages ┣📂OutputModel ┣📂SaveModel ┗📂VOC2012 ┣📂Annotations ┗📂ImageSets ┗📂Main
|
Dockerコンテナの作成
公式のDockerfileで試したところ、ライブラリが足りなかったりビルドエラーで思うように使えなかったので、修正済みDockerfile でDockerコンテナを作成します。

「📂pokego」と同じ階層に保存したら、次のコマンドでDockerイメージ作成と、Dockerコンテナ作成をします。
Dockerイメージ作成
1 2
| # Dockerイメージ作成 docker build -f Dockerfile -t od-1_15_2_gpu .
|

Dockerコンテナ作成
バインドマウントするパスは各自で変更してください。
1 2
| # Dockerコンテナ作成 docker run -v D:\tensorflow\pokego:/home/tensorflow/models/research/pokego -p 10000:6006 -it od-1_15_2_gpu
|

引数説明
- -v:ローカルの「📂pokego」フォルダーをバインドマウントして、Dockerコンテナ内から使えるようにしています。
- -p:後のTensorBoardで学習状況を確認するため、Dockerコンテナにポート番号を割り当てています。
ここまでのフォルダー構成
1 2 3 4 5 6 7 8 9 10
| 📂pokego ┣📂Data ┃ ┗📂JPEGImages ┣📂OutputModel ┣📂SaveModel ┣📂VOC2012 ┃ ┣📂Annotations ┃ ┗📂ImageSets ┃ ┗📂Main ┗📄Dockerfile ←🆕
|
学習データの作成
今回は「LabelImg」を使いました。PASCAL VOC型式で出力して TFRecord型式に変換する必要があります。

使い方は 昔記事 にしたので割愛。注意する点は PASCAL VOC型式で保存すること。
他にもTFRecord型式で出力できる「VoTT」も試したのですが、モデル学習の過程でエラーが出て進めなかったです。
ラベリング中は適当なフォルダーに保存していって、終わったらまとめて指定フォルダーに移動させると楽です。
指定フォルダーに移動
- 画像データ:📂pokego >📂Data >📂JPEGImages
- VOCデータ:📂pokego >📂VOC2012 >📂Annotations
VOCデータの修正
VOCデータを一括置換で修正します。
- folderの値を「Data」に置換
- pathの値からパス情報を削除

学習データの振り分け
訓練データ (train_data)と検証データ (validation_data)に振り分けていきます。
訓練80%、検証20%がよくある比率らしいので、そんな感じにします。
「📂pokego >📂VOC2012 >📂ImageSets >📂Main」に「📄aeroplane_train.txt」というテキストファイルを作り、学習データの80%分の画像ファイル名を振り分けます。

同様に「📄aeroplane_val.txt」も作り、残りの20%分のファイル名を振り分けます。

TensorFlow用ラベルリストの作成
ラベリングした時に名付けたラベル名をリストにしたファイルを作成します。
「📂pokego」フォルダー内に「📄tf_label_map.pbtxt」というテキストファイルを作り、次のようにラベル名を入力していきます。
1 2 3 4 5 6 7 8 9 10 11 12
| item { id: 1 name: 'Ace' } item { id: 2 name: 'Seven' } item { id: 3 name: 'King' }
|
今回は1つしかラベルしていないので次のようになりました。

ここまでのフォルダー構成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 📂pokego ┣📂Data ┃ ┗📂JPEGImages ┃ ┣📄image_0001.jpg ←🆕 ┃ ┣📄image_0002.jpg ←🆕 ┃ ┃・・・ ┃ ┗📄image_0038.jpg ←🆕 ┣📂OutputModel ┣📂SaveModel ┣📂VOC2012 ┃ ┣📂Annotations ┃ ┃ ┣📄image_0001.xml ←🆕 ┃ ┃ ┣📄image_0002.xml ←🆕 ┃ ┃ ┃・・・ ┃ ┃ ┗📄image_0038.xml ←🆕 ┃ ┗📂ImageSets ┃ ┗📂Main ┃ ┣📄aeroplane_train.txt ←🆕 ┃ ┗📄aeroplane_val.txt ←🆕 ┣📄Dockerfile ┗📄tf_label_map.pbtxt ←🆕
|
TFRecord型式に変換
TensorFlow で機械学習するには、ラベリングした学習データをTFRecord型式に変換する必要があります。
Dockerのターミナルに次のコマンドを入力すると、「📂pokego」フォルダー内に変換されたファイルが作られます。
1 2 3 4 5 6 7 8 9 10 11
| # 訓練データの変換 python object_detection/dataset_tools/create_pascal_tf_record.py \ --label_map_path=./pokego/tf_label_map.pbtxt \ --data_dir=./pokego --year=VOC2012 --set=train \ --output_path=./pokego/pascal_train.record
# 検証データの変換 python object_detection/dataset_tools/create_pascal_tf_record.py \ --label_map_path=./pokego/tf_label_map.pbtxt \ --data_dir=./pokego --year=VOC2012 --set=val \ --output_path=./pokego/pascal_val.record
|

変換されたファイル
- pascal_train.record
- pascal_val.record

ここまでのフォルダー構成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 📂pokego ┣📂Data ┃ ┗📂JPEGImages ┃ ┣📄image_0001.jpg ┃ ┣📄image_0002.jpg ┃ ┃・・・ ┃ ┗📄image_0038.jpg ┣📂OutputModel ┣📂SaveModel ┣📂VOC2012 ┃ ┣📂Annotations ┃ ┃ ┣📄image_0001.xml ┃ ┃ ┣📄image_0002.xml ┃ ┃ ┃・・・ ┃ ┃ ┗📄image_0038.xml ┃ ┗📂ImageSets ┃ ┗📂Main ┃ ┣📄aeroplane_train.txt ┃ ┗📄aeroplane_val.txt ┣📄Dockerfile ┣📄pascal_train.record ←🆕 ┣📄pascal_val.record ←🆕 ┗📄tf_label_map.pbtxt
|
パイプラインデータの作成
機械学習を始めるときに必要なデータをまとめて記載したパイプラインデータを作成します。
今回はひな形を少し修正して、機械学習させていきます。
公式GitHub から「📄ssd_mobilenet_v1_coco.config」をダウンロードして、次の箇所を修正します。
- 9行目:num_classes: 1(学習するラベル数と同じにする)
- 141行目:batch_size: 4(GPUに自信があれば大きくてもよい 2,4,8,16,32…)
- 156行目:fine_tune_checkpoint: “”(空に)
- 162行目:num_steps: 10000(学習してみて足りなければ増やせばよい)
- 175行目:input_path: “./pokego/pascal_train.record”
- 177行目:label_map_path: “./pokego/tf_label_map.pbtxt”
- 189行目:input_path: “./pokego/pascal_val.record”
- 191行目:label_map_path: “./pokego/tf_label_map.pbtxt”
今回はホントにまっさらな状態から学習させました。途中からだとうまく学習できなかったためです。
ここまでのフォルダー構成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 📂pokego ┣📂Data ┃ ┗📂JPEGImages ┃ ┣📄image_0001.jpg ┃ ┣📄image_0002.jpg ┃ ┃・・・ ┃ ┗📄image_0038.jpg ┣📂OutputModel ┣📂SaveModel ┣📂VOC2012 ┃ ┣📂Annotations ┃ ┃ ┣📄image_0001.xml ┃ ┃ ┣📄image_0002.xml ┃ ┃ ┃・・・ ┃ ┃ ┗📄image_0038.xml ┃ ┗📂ImageSets ┃ ┗📂Main ┃ ┣📄aeroplane_train.txt ┃ ┗📄aeroplane_val.txt ┣📄Dockerfile ┣📄pascal_train.record ┣📄pascal_val.record ┣📄ssd_mobilenet_v1_coco.config ←🆕 ┗📄tf_label_map.pbtxt
|
独自モデルの学習
これで準備完了です。
次のコマンドをDockerコンソールにコピペすると学習が始まります。
1 2 3 4
| python object_detection/model_main.py \ --pipeline_config_path="./pokego/ssd_mobilenet_v1_coco.config" \ --model_dir="./pokego/SaveModel" \ --alsologtostderr
|

学習が始まると次のログが流れます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| INFO:tensorflow:Saving checkpoints for 0 into ./pokego/SaveModel/model.ckpt. I0601 12:18:31.965519 140684566607680 basic_session_run_hooks.py:606] Saving checkpoints for 0 into ./pokego/SaveModel/model.ckpt. INFO:tensorflow:loss = 15.334896, step = 1 I0601 12:18:39.144777 140684566607680 basic_session_run_hooks.py:262] loss = 15.334896, step = 1 INFO:tensorflow:global_step/sec: 2.3923 I0601 12:19:20.944656 140684566607680 basic_session_run_hooks.py:692] global_step/sec: 2.3923 INFO:tensorflow:loss = 8.772529, step = 101 (41.801 sec) I0601 12:19:20.945703 140684566607680 basic_session_run_hooks.py:260] loss = 8.772529, step = 101 (41.801 sec) INFO:tensorflow:global_step/sec: 2.542 I0601 12:20:00.283499 140684566607680 basic_session_run_hooks.py:692] global_step/sec: 2.542 INFO:tensorflow:loss = 6.0293255, step = 201 (39.339 sec) I0601 12:20:00.284253 140684566607680 basic_session_run_hooks.py:260] loss = 6.0293255, step = 201 (39.339 sec) INFO:tensorflow:global_step/sec: 2.16601 I0601 12:20:46.451487 140684566607680 basic_session_run_hooks.py:692] global_step/sec: 2.16601 INFO:tensorflow:loss = 5.0683546, step = 301 (46.169 sec) I0601 12:20:46.453217 140684566607680 basic_session_run_hooks.py:260] loss = 5.0683546, step = 301 (46.169 sec)
|

このように100ステップ毎にログが出力され、ロスが徐々に下がっていれば正常に学習が進んでいる証拠です。
確認
ある程度学習が進むと、定期的に検証処理が入り、「📂SaveModel」フォルダーに「📄model.ckpt」ファイルが作成されていきます。

学習を中断させたいときは「Ctrl+C」で中断させることができます。最初と同じコマンドで再実行することもできます。
ここまでのフォルダー構成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 📂pokego ┣📂Data ┃ ┗📂JPEGImages ┃ ┣📄image_0001.jpg ┃ ┣📄image_0002.jpg ┃ ┃・・・ ┃ ┗📄image_0038.jpg ┣📂OutputModel ┣📂SaveModel ┃ ┣📂eval_0 ←🆕 ┃ ┣📄checkpoint ←🆕 ┃ ┣📄graph.pbtxt ←🆕 ┃ ┣📄model.ckpt-0.data-00000-of-00001 ←🆕 ┃ ┣📄model.ckpt-0.index ←🆕 ┃ ┣📄model.ckpt-0.meta ←🆕 ┃ ┣📄model.ckpt-1436.data-00000-of-00001 ←🆕 ┃ ┣📄model.ckpt-1436.index ←🆕 ┃ ┗📄model.ckpt-1436.meta ←🆕 ┣📂VOC2012 ┃ ┣📂Annotations ┃ ┃ ┣📄image_0001.xml ┃ ┃ ┣📄image_0002.xml ┃ ┃ ┃・・・ ┃ ┃ ┗📄image_0038.xml ┃ ┗📂ImageSets ┃ ┗📂Main ┃ ┣📄aeroplane_train.txt ┃ ┗📄aeroplane_val.txt ┣📄Dockerfile ┣📄pascal_train.record ┣📄pascal_val.record ┣📄ssd_mobilenet_v1_coco.config ┗📄tf_label_map.pbtxt
|
後は、気が済むまで学習させて下さい。
学習結果の確認
どれぐらい学習したのか確認したくなると思います。
次のコマンドをDockerコンソールに入力すると、tensorboard で確認することができるようになります。
1
| tensorboard --port 6006 --logdir="./pokego/SaveModel"
|

TensorBoard を起動させると http://localhost:10000 このURLから見ることができます。

TFLite型式に変換
Androidで物体検出(object etection)を実行させるには、学習モデルをTFLite型式に変換する必要があります。
中間データに変換
次のコマンドで中間データに変換します。
trained_checkpoint_prefix の「model.ckpt-1436」は保存されてる学習済みデータの中で一番大きい数値のモデルを使ってください。
1 2 3 4 5
| python object_detection/export_tflite_ssd_graph.py \ --pipeline_config_path="./pokego/ssd_mobilenet_v1_coco.config" \ --trained_checkpoint_prefix="./pokego/SaveModel/model.ckpt-1436" \ --output_directory="./pokego/OutputModel" \ --add_postprocessing_op=true
|
このコマンドを実行すると「📂OutputModel」フォルダーに2つのファイルができあがります。
- 📄tflite_graph.pb
- 📄tflite_graph.pbtxt

TFLite型式に変換
そのまま続けてTFLite型式に変換します。
1 2 3 4 5 6 7 8 9 10 11 12
| tflite_convert \ --output_file="./pokego/OutputModel/pokego.tflite" \ --graph_def_file="./pokego/OutputModel/tflite_graph.pb" \ --inference_type=QUANTIZED_UINT8 \ --input_arrays=normalized_input_image_tensor \ --input_shapes=1,300,300,3 \ --output_arrays='TFLite_Detection_PostProcess','TFLite_Detection_PostProcess:1','TFLite_Detection_PostProcess:2','TFLite_Detection_PostProcess:3' \ --default_ranges_min=0 \ --default_ranges_max=6 \ --mean_values=128 \ --std_dev_values=128 \ --allow_custom_ops
|
TFLite型式のファイルが出来上がります。

メタデータの挿入
最後にAndroidアプリで動かせるようにするため、メタデータを挿入します。
メタデータ用ラベルリスト作成
まず「📂pokego」フォルダーに「labels.txt」というテキストファイルを作成し、ラベルリストを作成します。
こっちのラベルリストは1行に1ラベルずつ記載していきます。
私の場合は1ラベルしかないので、次のようになりました。

メタデータ挿入準備
次に「📂pokego」フォルダーに「📄object_detector_Metadata_Writer.py」というPythonファイルを作成して、次のコードをコピペします。
1 2 3 4 5 6 7 8 9 10 11 12
| from tflite_support.metadata_writers import object_detector from tflite_support.metadata_writers import writer_utils
ObjectDetectorWriter = object_detector.MetadataWriter _MODEL_PATH = "./pokego/OutputModel/pokego.tflite" _LABEL_FILE = "./pokego/labels.txt" _SAVE_TO_PATH = "./pokego/OutputModel/pokego_metadata.tflite" _INPUT_NORM_MEAN = 127.5 _INPUT_NORM_STD = 127.5
writer = ObjectDetectorWriter.create_for_inference(writer_utils.load_file(_MODEL_PATH), [_INPUT_NORM_MEAN], [_INPUT_NORM_STD], [_LABEL_FILE]) writer_utils.save_file(writer.populate(), _SAVE_TO_PATH)
|
次のコマンドをDockerコンソールで実行すると「📄pokego.tflite」にメタデータが挿入されます。
1
| python ./pokego/object_detector_Metadata_Writer.py
|
「📄pokego_metadata.tflite」が作成されれば完成です。

最終フォルダー構成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 📂pokego ┣📂Data ┃ ┗📂JPEGImages ┃ ┣📄image_0001.jpg ┃ ┣📄image_0002.jpg ┃ ┃・・・ ┃ ┗📄image_0038.jpg ┣📂OutputModel ┃ ┣📄pokego.tflite ←🆕 ┃ ┣📄pokego_metadata.tflite ←🆕👑 ┃ ┣📄tflite_graph.pb ←🆕 ┃ ┗📄tflite_graph.pbtxt ←🆕 ┣📂SaveModel ┃ ┣📂eval_0 ┃ ┣📄checkpoint ┃ ┣📄graph.pbtxt ┃ ┣📄model.ckpt-0.data-00000-of-00001 ┃ ┣📄model.ckpt-0.index ┃ ┣📄model.ckpt-0.meta ┃ ┣📄model.ckpt-1436.data-00000-of-00001 ┃ ┣📄model.ckpt-1436.index ┃ ┗📄model.ckpt-1436.meta ┣📂VOC2012 ┃ ┣📂Annotations ┃ ┃ ┣📄image_0001.xml ┃ ┃ ┣📄image_0002.xml ┃ ┃ ┃・・・ ┃ ┃ ┗📄image_0038.xml ┃ ┗📂ImageSets ┃ ┗📂Main ┃ ┣📄aeroplane_train.txt ┃ ┗📄aeroplane_val.txt ┣📄Dockerfile ┣📄labels.txt ←🆕 ┣📄object_detector_Metadata_Writer.py ←🆕 ┣📄pascal_train.record ┣📄pascal_val.record ┣📄ssd_mobilenet_v1_coco.config ┗📄tf_label_map.pbtxt
|
Androidで物体検出
ホントこれがやりたかった。
前回記事 の物体検出アプリに今回学習したモデルを入れて、動作確認します。
assetsフォルダーにメタデータ挿入済みモデルとメタデータ用ラベルリストをコピペし、
- 📄labels.txt
- 📄pokego_metadata.tflite

「📄DetectorActivity.java」の設定を修正します。
- TF_OD_API_MODEL_FILE = “pokego_metadata.tflite”;
- TF_OD_API_LABELS_FILE = “labels.txt”;

確認
実機で確認します。

左上バグってますが、正しくポケストップが物体検出されています。
テストなのでカメラで動作確認しましたが、今後の予定として、実機の画面に映ったポケストップを検出させて自動化できるアプリを考えています。
おわりに
Dockerいいですね。簡単に環境が作れました。
いろんなサイトを見て回ってやっとたどり着いた私なりの手順になります。Dockerイメージさえあれば、どんなPCでも機械学習ができるので、よければ参考にしてみて下さい。
GitHub
今回作ったファイルを GitHub で公開します。学習用画像とラベリングデータを用意すればすぐに機械学習が始められると思います。
https://github.com/noitaro/tensorflow-object-detection
参考リンク