カニカニクラブライフ

Python系技術メモ、書評とか

最短でYOLOv3を学習させて物体検出させたい人のために(Python, Keras)

仕事で、物体検出を用いた業務発注を行う関係で勉強していたのと、これに応募してみようとして色々やっていて、表題のプログラムが動かせるようになったので一応手順を共有しておきたく。

第2回衛星データ分析コンテスト「Tellus Satellite Challenge」を開催します (METI/経済産業省)

すでに以下の日本語の記事があって、ほぼほぼそれのパクリなのですが、元のgithubなりコードを調べないと解決しない箇所があったのでまとめておきます。

最新の物体検出YOLO v3 (Keras2.2.0+TensorFlow 1.8.0)を独自データで学習できるようにしてみた: EeePCの軌跡

疑問点、指摘などコメントでいただけるとありがたいです。

1. YOLOとは?&今回のコード

機械学習まわりの画像処理には、画像の認識(その画像に何が移っているのか判別する)と検出(画像内のどこに写っているかを推論する)のタスクがあり、それぞれのタスクを別々に行う方法もありますが、この画像内の位置推定を回帰問題ととらえてまとめて一つのネットワークで表現し、「どこに」「何が」写っているのかワンショットで推論させちゃおうぜ、というのが基本的なYOLOのアイデアということです。

まあ、私の説明よりかは、以下の書籍で整理されているのでざっと読むのがわかりやすいかと思います。

画像認識 (機械学習プロフェッショナルシリーズ)

画像認識 (機械学習プロフェッショナルシリーズ)

原理的なことや他の物体検出との比較はこちらの記事がわかりよいかと思います。
【論文紹介】YOLOの論文を読んだので要点をまとめてみた | DevelopersIO


KerasとPythonによる実装は、Githubにすでにあるため、それを使います。

github.com

クイックスタートとして、すでにVOC2007等で学習済みのモデルを使って物体検出させてみましょう(上記の記事を参照してください)。

$ python yolo_video.py --image


ワシも試しにネットで適当に拾ってきた自転車の画像を読ませてみました。

f:id:russENG:20190224170322j:plain

できていますが、後ろの柵をベンチと言っています。まあ納得感のある間違い方。

2. 学習させたい画像と対象の位置情報を記述したファイルを用意する

さて、学習させたい独自データを用意しましょう。

画像と、その中で対象が写っている領域を矩形で表現したデータ(アノテーションといいます)を用意します。

アノテーションは、Githubのreadmeにもありますが、テキストファイルで、以下のような記述方法で用意します。

One row for one image;
Row format: image_file_path box1 box2 … boxN;
Box format: x_min,y_min,x_max,y_max,class_id (no space).
例:
path/to/img1.jpg 50,100,150,200,0 30,50,200,120,3
path/to/img2.jpg 120,300,250,600,2…

VOC datasetであれば、voc_annotation.pyが使えるそうです。


上記記事では、独自のアノテーションデータ作成ソフトを使っていますが、とりあえず学習プログラムをテストするのが目的だったので、適当な画像を作るスクリプトを書きました。

sandbox/gazou_tsukuruzo.py at master · russENG/sandbox · GitHub

すると、こういうファンシーな画像がたくさん得られます。この黒三角▲の位置を特定するというタスクを解くための学習を行っていきます。

f:id:russENG:20190224180404j:plain
学習用データ

3. 事前学習済み重みファイルを用意する

おそらく、まったくまっさらな状態(要は、ネットワーク重みが初期値の状態)の学習方法というのは用意されていないので、行おうとすると自分で実装を組むしかないのだと思われます。

上記の記事が掲載されているブログの他のアルゴリズム紹介記事のコメントにも、最近のコードはそうだ、みたいなコメントがありました。

TensorFlow+KerasでSSDを独自データで使えるようにしてみた: EeePCの軌跡

最初の学習では、weight.SSD300.hdf5を使うしかないので、そのままご使用ください。ただ、一度独自データで学習すると、checkpointsというフォルダに〜.hdf5という学習器が生成されるため、その後独自データでの転移学習(画像の追加や新たなラベルでの学習)されるときは、その中から適当な学習器を選んで置き換えてください。

このコード自体が、0からの学習ができない仕様のため、最初はなんらかの学習器を読み込んで使うしかありません。最近公開されているディープラーニングのプログラムコードはこういうものが多いです。ですが、これでまあまあ上手くいくので、一度お試しください。

READMEのTraingのIf書きに書いてある部分に従い、https://pjreddie.com/media/files/darknet53.conv.74からdarknet53.weightsを落としてきて、convert.pyで変換し、.h5ファイルにします。

4. クラス定義ファイル、アンカー定義ファイルを用意し、train.py内のパスを変更する

クラス定義ファイル、アンカー定義ファイルもともにテキストファイルで用意します。
クラス定義ファイルについては、Githubのmodel_dataディレクトリのcoco_classes.txt 等を参考としてください。
先ほど定めたclass_id順に並べる用のだと思われます。私の例では、ただ’kurosankaku’と記載したテキストファイルを用意しました。

アンカー定義ファイルは、アンカーボックス、物体検出においての画像上の領域の大きさのパターンを定義しているファイルだと思われます。
しかし、今回学習用に独自に設定してもうまく動かなかったため、おそらく予め設定ルールが決まっているのでしょう。
これは後々確認していこうと思いますが、デフォルトのyolo_anchors.txt を用いても、物体サイズはわりと対象物に対して過不足なくフィットしているような気がします。

3.も含めすべてのファイルが用意できたら、train.py 内の_main関数内にあるパスを書き換えます。
アノテーションファイル、クラス定義ファイル、アンカー定義ファイルについては、それぞれ
annotation_path = ....
classes_path = ....
anchors_path = .... の部分を書き換えます。

先の事前学習済み重みファイルについては、

if is_tiny_version: 
         model = create_tiny_model(input_shape, anchors, num_classes, 
             freeze_body=2, weights_path='model_data/tiny_yolo_weights.h5') 
     else: 
         model = create_model(input_shape, anchors, num_classes, 
             freeze_body=2, weights_path='model_data/yolo_weights.h5') # make sure you know what you freeze 

の部分のパスを書き換えます。

以上で、学習の準備は終了です。

5. 学習実行&学習結果(学習済みファイル)の取り出し

さっそく実行してみましょう。

$ python train.py

ワシは手持ちのMacbookで実行したのですが、CPUが明らかに音を立ててフル稼働してました。

lossなどは実行中に元のプログラムのprint()で表示されます。

最初は、(おそらく学習の安定化のため)いくつかのネットワークがフリーズされた状態で50epochやるみたいなんですが、
だいたい1step:50秒、1epoch20分程度、といった感じです。

だいたい1日後くらいの77epochくらいで学習を打ち切りました。結果を見てみます。

logフォルダの中にある学習済みモデル重みをmodel_dataに移動させ、わかりやすいように名前を書き換え、yolo_video.py を実行します。

f:id:russENG:20190224175927j:plain

こんな感じで、一応できてました。

あとは、yolo_video.py を実行する際には、--の引数指定はなんかうまく動かなかったので、パスについてはまた書き換えました。


どっとはれ。