TensorFlowで独自物体検出モデルを作成

Uncategorized
2.7k words

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コンテナを作成します。

Dockerfile

「📂pokego」と同じ階層に保存したら、次のコマンドでDockerイメージ作成と、Dockerコンテナ作成をします。

Dockerイメージ作成

1
2
# Dockerイメージ作成
docker build -f Dockerfile -t od-1_15_2_gpu .

Dockerイメージ作成コマンド

Dockerコンテナ作成

バインドマウントするパスは各自で変更してください。

1
2
# Dockerコンテナ作成
docker run -v D:\tensorflow\pokego:/home/tensorflow/models/research/pokego -p 10000:6006 -it od-1_15_2_gpu

Dockerコンテナ作成コマンド

引数説明
  • -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の値からパス情報を削除

VOCデータ修正

学習データの振り分け

訓練データ (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つしかラベルしていないので次のようになりました。

TensorFlow用ラベルリスト

ここまでのフォルダー構成

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

TFRecord型式に変換するコマンド

変換されたファイル

  • pascal_train.record
  • pascal_val.record

TFRecord型式に変換

ここまでのフォルダー構成

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起動コマンド

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

TensorBoard

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型式のファイルが出来上がります。

TFLite型式のファイル作成

メタデータの挿入

最後にAndroidアプリで動かせるようにするため、メタデータを挿入します。

メタデータ用ラベルリスト作成

まず「📂pokego」フォルダーに「labels.txt」というテキストファイルを作成し、ラベルリストを作成します。

こっちのラベルリストは1行に1ラベルずつ記載していきます。

1
2
3
Ace
Seven
King

私の場合は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

assetsフォルダー

「📄DetectorActivity.java」の設定を修正します。

  • TF_OD_API_MODEL_FILE = “pokego_metadata.tflite”;
  • TF_OD_API_LABELS_FILE = “labels.txt”;

DetectorActivity.java

確認

実機で確認します。

ポケストップ物体検出

左上バグってますが、正しくポケストップが物体検出されています。

テストなのでカメラで動作確認しましたが、今後の予定として、実機の画面に映ったポケストップを検出させて自動化できるアプリを考えています。

おわりに

Dockerいいですね。簡単に環境が作れました。

いろんなサイトを見て回ってやっとたどり着いた私なりの手順になります。Dockerイメージさえあれば、どんなPCでも機械学習ができるので、よければ参考にしてみて下さい。

GitHub

今回作ったファイルを GitHub で公開します。学習用画像とラベリングデータを用意すればすぐに機械学習が始められると思います。

https://github.com/noitaro/tensorflow-object-detection

参考リンク