にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

Windows 10上のDarknetでYolo v3をトレーニングしOpenCVから使ってみる

DarknetはCで書かれたディープラーニングフレームワークである。物体検出のYOLOというネットワークの著者実装がDarknet上で行われている。
もともとはLinux等で動かすもののようだが、ありがたいことにWindowsコンパイルできるようにしたフォークが存在している:
github.com
これを利用してWindowsで動かしてみる。

環境

事前準備

Visual Studio 2017 Community

まずVisual Studio 2017 Communityをインストール。
プロジェクトがVisual Studio 2015のものなので、v140ツールセットも入れる。インストーラを起動し、「変更」を押し、右側のペインの「C++によるデスクトップ開発」の下のオプションから「デスクトップ用VC++ 2015.3 v14.00 (v140) ツールセット」にチェックを入れ、「変更」を押してインストール。

CUDA/cuDNNのインストール

次にCUDAとcuDNNをインストール。今回入れたバージョンは次の通り。リポジトリにはそれぞれ9.1, 7.0を使うように書かれていたが、最新版でも問題なさそうである。

  • CUDA 9.2
  • cuDNN 7.1

CUDAのインストールはインストーラに従えばよい。cuDNNはCUDAと同じディレクトリに突っ込んでおくと楽。

PathにC:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.2\binを追加しておく。

OpenCVのインストール

OpenCVをダウンロードしてきて展開する。最新はv3.4.2だが、リポジトリの注意書きにあるようにv3.4.0(またはそれ以前)を使う。
OpenCVのReleasesから3.4.0のWin packをダウンロードしてきて適当な場所(以下、C:\opencv340とする)に展開する。

OpenCVのdllが存在するディレクトリ(C:\opencv340\opencv\build\x64\vc14\bin)にPathを通しておく。

コンパイル

ここからソースコード一式をダウンロードしてくる。ReleasesからYolo_v3のタグがついたものをダウンロードしてきたが、git cloneしても問題ないはず。

ダウンロードしてきたものを展開し、build/darknet/darknet.slnをVisual Studio 2017 (Community)で開く。
開いた際にツールセットを変換するか聞かれたのだが変換しないようにした。(これはどうするのが最適なのか不明)

ソリューション構成・プラットフォームの変更

構成: Release, プラットフォーム: x64に変更。

カスタム ビルド ツールの指定

プロジェクト名(darknet)を右クリックして「ビルドの依存関係」→「ビルドのカスタマイズ」を開き、「CUDA 9.1」のチェックを外し、代りに「CUDA 9.2」をオンにする。

プロジェクトのプロパティの編集

プロジェクト名を右クリックして「プロパティ」を開き、

これらを編集して、実際にインストールしたOpenCVディレクトリに合わせる(C:\opencv_3.0\…C:\opencv340\…)。
また、
「リンカー」→「全般」→「追加のライブラリディレクトリ」の中の

  • $(CUDA_PATH)lib\$(PlatformName)$(CUDA_PATH)\lib\$(PlatformName)

が変な気がしたので\を足しておいた。

コンパイル

「ビルド」→「ソリューションのビルド」などでコンパイルする。

すると、build\darknet\x64以下にdarknet.exeが出来上がる。

試す

ちゃんと動くか確認する。
YOLOのページ(https://pjreddie.com/darknet/yolo/)からYOLOv3のweightsファイルをダウンロードしてきてbuild\darknet\x64以下に入れる。
コマンドプロンプトbuild\darknet\x64を開き、

darknet.exe detector test data/coco.data yolov3.cfg yolov3.weights -i 0 -thresh 0.25 dog.jpg

を実行する。これはdarknet_yolo_v3.cmdの中身でもある。認識がうまくいくと次のような認識結果が表示される。
f:id:nixeneko:20180803152630p:plain

トレーニング準備

トレーニングについては

に詳しく書いてある。
また、データセットの見本として、

のページの「The data set I composed for this article can be found here (19.4Mb).」と書かれているところからダウンロードできるデータを参考にできる。(これをそのまま使う場合、改行コードがWindows用\r\nになっているため変換が必要になるかもしれないらしい)

今回はイラストと顔の領域を指定したデータを用意し、イラストの顔検出をやってみる。

学習データを用意

アノテーションLabelImg等を使うと楽。形式が異なっている場合は適宜変換する。

学習データは、画像ファイル(.jpg)群*1と(拡張子以外)同名のテキストファイル(.txt)を用意する。画像ファイルとテキストファイルは同一のディレクトリに入れる。テキストファイルはアノテーションを定義する。
アノテーションの形式は、

<クラスNo.> <領域の中心のx座標> <領域の中心のy座標> <領域の幅> <領域の高さ>

である。クラスNo.は.namesファイルで定義したものに対応する番号である(0オリジン)。今回はクラスが1つだけの場合を考えるのでクラスNoは必ず0になる。
座標や幅・高さは左上を(0.0, 0.0), 右下を(1.0, 1.0)とする割合で示す。2,3番目の座標は領域の中心の座標であることに注意。一行に一つの領域をかき、領域が複数になる場合は同様の内容を複数行書くことになる。

一例をあげると

0 0.2 0.2 0.1 0.1

は0番目のクラス、領域の中心は画像の左から2/10、上から2/10の位置で、幅が画像の1/10、高さが画像の1/10となる。

画像ファイルのリスト

学習用(train.txt)とテスト用(test.txt)に、画像ファイルの一覧を書いたファイルを用意する。darknet.exeからの相対パスを一行一ファイルとして羅列したものとなる。画像ファイル群を適当な割合で分割して学習用とテスト用にする。今回は1000個弱の画像ファイルを学習:テスト=9:1に分けた。

画像ファイルは最終的にbuild\darknet\x64\data\obj以下に入れるとすると、train.txttest.txtは次のような内容になる。

data/obj/00001.jpg
data/obj/00002.jpg
data/obj/00003.jpg

これらをbuild\darknet\x64\data\obj\train.txt, build\darknet\x64\data\obj\test.txtに用意した。

クラス名の設定

.namesファイルがクラス名の設定である。一行に一つのクラスを書く。今回は一つのクラスだけにするので、obj.namesファイルを

face

とだけ書いて保存する。

.dataファイルの設定

classes= 1
train  = data/obj/train.txt
valid  = data/obj/test.txt
#difficult = data/difficult_2007_test.txt
names = data/obj.names
backup = backup/

クラス数を1に変更、trainvalidに先ほど用意したtrain.txt, test.txtを指定。
クラスの名前を書いたファイルをnamesに指定。backupはトレーニング結果の重みの出力先となる。

cfgファイルの設定

yolov3.cfgをコピーしてyolov3-obj.cfgとし、編集する。

max_batchesは何回繰り返すかを指定する。今回は10000とした(トレーニングに丸1日程度かかった)。

GPUのメモリが6GBと少ないのでsubdivisionを増やす。メモリが十分ある場合は小さい値(8や16)で問題ないが、メモリ関係のエラーで止まる場合は32, 64と値を増やすといいらしい。

  • subdivision=16subdivision=32

クラスの数に合わせて、3ヵ所について次の変更を施す。filtersはfilters=(クラス数+5)×3で計算し、クラスが1つの場合は18となる。

  • classes=80classes=1
  • filters=255filters=18

トレーニング

ネットワークの重みの初期値としてdarknet53.conv.74をダウンロードしてきて使う。

これをbuild\darknet\x64に入れる。

build\darknet\x64に移動して、次のコマンドを実行する。

darknet.exe detector train data/obj.data yolov3-obj.cfg darknet53.conv.74

lossが下がっていくのを眺めながら、トレーニングが終了するまで待つ(あるいは適当なところで終了する)。

トレーニング途中でdarknetが終了してしまう場合

darknet.exeと同じディレクトリにbad.listというファイルが生成され、問題となった画像ファイルが追記されるようだ。このファイルを用意したtrain.txttest.txtから取り除いてトレーニングし直すといいっぽい。これを学習が止まらなくなるまで繰り返す。

結果の確認

トレーニング結果は

darknet.exe detector test data/obj.data yolov3-obj.cfg backup/yolov3-obj_final.weghts -i 0

で確認できる。動画だと

darknet.exe detector test data/obj.data yolov3-obj.cfg backup/yolov3-obj_final.weghts moviefile.mp4 -i 0 -out_filename result.avi

とか。

OpenCV/Pythonで動かす

最新のOpenCVにはDNNモジュールがあり、darknetのネットワークも利用できる。
ただし、YOLOv3(内部で利用しているshortcutレイヤ)を使うためにはOpenCV 3.4.2より前のバージョンでは対応していないので、最新版をインストールする必要がある。Python版はpip install opencv-pythonなどで入れられる。


OpenCV公式のsamples/dnn/object_detection.pyにDNNモジュールによる物体検出のサンプルがあるので、これ改変して動くものをつくった。

コード

動作環境

gist.github.com

ダウンロード

weightsファイル等も含めて動かせるセットは次からダウンロードできる。Python 3向け。OpenCV 3.4.2より前のバージョンでは動かないので注意。

備考

入力に与えたい画像と、ネットワークの受け付ける入力の形が異なるので、cv2.dnn.blobFromImage関数を利用してデータの橋渡しをしてやる必要がある、ということのようだ。.cfgファイルがネットワークを定義しているので、入力画像の大きさについてもここに定義されている(今回は(416, 416))。
また、スケーリングを行い入力が0.0~1.0の範囲になるようにしているようだ。
OpenCVがBGRなので、(BGRでなく)RGB順を使うよう指定している。

動作画面

f:id:nixeneko:20180807170439p:plain
まあ動く。

*1:.jpg以外の形式も使えるかは確認してない。