ご注文はYOLOv3ですか！？～WindowsでYOLOv3を動かす～


# ご注文はYOLOv3ですか？
- 前回の[記事](https://qiita.com/chimamedia/items/85259c5587171ad407a0)で，YOLOを用いてみたわけですが、最近YOLOv3というさらに精度がよく、処理速度も速いとうわさがあったので、YOLOv3を用いて再度チャレンジ
- また、今回も心を癒されるためにごちうさを学習していきますよ～あぁ^～心がぴょんぴょんするんじゃぁ^～

## 使用環境
- Windows10
- CPU: Intel Core i7-8700K @ 3.70GHz
- RAM: 32GB
- NDIVIA Geforce GTX1080Ti(11GB)

# YOLOv3の入手と環境構築
- Windows版のYOLOは[ここ](https://github.com/AlexeyAB/darknet)からダウンロードできます。
- 環境は、先ほどのサイトにもありますが私は以下のようにしました。
    - OpenCV3.0
    - CUDA9.1
    - cuDNN v7
    - python3(Anaconda)
- 環境構築できれば、[darknet.sln]を起動して、[Release]モードでビルドするだけ
- プログラムに少し細工をします
    - 出力フォルダ
        - プログラムを走らせた後に、weightファイルを出力するフォルダがないというエラーが出てきますので、フォルダを自動で生成するように以下のコードを追加
        - 「detector.c」の「void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *gpus, int ngpus, int clear, int dont_show)」関数の中の「char *backup_directory = option_find_str(options, "backup", "/backup/");」の後に「_mkdir(backup_directory);」を追加する。
      
    - weightファイルについて
        - [detector.c]ファイルの180~182行目当たりのif文を変更するとweightファイルの書き出し方が変更になります。
        - 自分は「if(i >= (iter_save + 100))」となっており、100エポック毎に出力されていました。
        - ご自由に書き換えてください。



## 学習（Training）
- 学習の手順は前回の記事同様です。

1. アノテーション
2. 画像増幅
3. 学習

-今回使用したプログラム（後日UP）

# アノテーション
- 前回はBBox-Label-Tool.pyを用いて1つずつアノテーションしていましたが、今回は少し変更してみました。
- 今回もごちうさ（アニメ画像）を対象としていることと、YOLOで必要なアノテーションが「個数，x座標（始点），y座標（始点），x座標（終点），y座標（終点）」なので、cascadeの顔検出を用いることにしました。
- アニメのcascadeは[ここ](http://ultraist.hatenablog.com/entry/20110718/1310965532)にありますので、ここから「lbpcascade_animeface.xml」を拝借します。
- とりあえず、検出された画像ごとに、キーボードを打つようにします。

```c++:main.cppの一部
//顔検出
vector<cv::Rect> faces;
cascade.detectMultiScale(image, faces, 1.1, 3, 0, cv::Size(20, 20));

//顔検出
vector<int> face_cnt(label);
vector<vector<cv::Rect>> face_rect(label);

//初期化
for (int i = 0; i < face_cnt.size(); ++i)
	face_cnt[i] = 0;

//検出結果を表示
cv::Mat f = image.clone();
for (int i = 0; i < faces.size(); ++i)
{
	cv::rectangle(f, faces[i], cv::Scalar(0, 0, 255), 2);
}

//すべての画像を見やすくするためにサイズ変換（元の画像はいじらない）
cv::resize(f, f, cv::Size(320, 240));
cv::imshow("IMAGE", f);

//検出された顔画像だけ処理をする
for (int i = 0; i < faces.size(); ++i)
{
	cv::Mat res;
	res = image(faces[i]);

	//検出された画像を表示する
	cv::imshow("ROI", res);
	char key = cv::waitKey(0);

	//画像のIDをつける。
	//スペースキー：なし、1,2,...,9,a,b,...,z
	int no = ChartoInt(key);
	printf("no -- %2d\n", no);
	if (no >= 0)
	{
		//ID毎に保存する
		face_cnt[no]++;
		face_rect[no].push_back(faces[i]);
	}

	cv::destroyWindow("ROI");
}
```

- 例えば今回の場合は次のようにしました。
    1. チノ
    2. リゼ
    3. ココア
    4. シャロ
    5. チア
    6. マヤ
    7. めぐ
    8. モカさん
    9. 青山さん
    10. 他（お父さんなど）

その後、間違えた場合や少し四角形がおかしい場合「BBox-Label-Tool.py」にて修正を行います。


- この時点でのフォルダ構成

Trains（元のフォルダ）
｜－tools(BBox-Label-Tool.py、convert.pyなど）
｜－gochiusa（今回作成するフォルダ）
　　　｜－Images（プログラムで作成されているはず）
　　　｜　　｜－クラス数だけフォルダがある
　　　｜　　
　　　｜－Label（プログラムで作成されているはず）
　　　　　　｜－クラス数だけフォルダがある

# 増幅＋YOLOの形式に変更
- 画像の増幅は前回と同じ「inflate_images.py」を起動します。これは、前回と同じ

inflate_images.py [フォルダ名]

- 今回の場合は

inflate_images.py gochiusa

- YOLOv3の形式に変更
    - これは、前回のコードではいくつか修正する必要（主にfilterの計算部分）があったので、修正しました。

convert_v3.py [フォルダ名]

- 今回の場合は

convert_v3.py gochiusa


※前回と同様ですが、inflated_imageで出力フォルダをして指定ない場合で、学習数の比率を変更したい場合は、「obj」と入力

学習数の比率は指定しない場合は「学習数：テスト数＝１：１」となります（学習画像とテスト画像が同じ）。学習数の比率は0.1～0.9の間で指定してください。
今回は何も指定していません。

# 学習
- 学習するために「darknet53.conv.74」をダウンロードしてきます。
    - [ここ](https://github.com/AlexeyAB/darknet)からダウンロードできます。

- 学習は基本、YOLOv2と同じですが、もう一度書いておきます。
- これを、darknetの実行するフォルダに持っていきます。訓練のコマンドは以下の通りです。

　　.\darknet.exe detector train [dataファイル] [cfgファイル] .\darknet53.conv.74

今回の場合は

　　.\darknet.exe detector train .\gochiusa\config\learning.data .\gochiusa\config\learning.cfg .\darknet53.conv.74

となります。とりあえず10000回まわします。
あとは、実行が終わるのを待つだけ。。。
もし、もっと回したい、減らしたいという人は15行目のmax_batches = 10000を変更してください。

- なんと、今回から処理中のグラフが表示されるようになっています（すばらしい！！）
- 途中で学習が悪いと思えば視覚的にやめることができるのはいいですね。
![chart.jpg](https://qiita-image-store.s3.amazonaws.com/0/214668/cd22e8dd-2de0-31bd-d83c-91538b899495.jpeg)

- しかし、sキーを押したときだけ画像が出力ということらしいので、できればweightファイルと同じ時に出力がしたいと思ったので、detector.cを少し書き換えました。
- さきほど紹介したweightファイルを書き込む部分（180～182行目あたりのif文）の中の「save_weights(net, buff);」の下にコードを追加

```c++:detector.cppへの追加
char bufcv[256];
sprintf(bufcv, "%s/chart_%d.jpg", backup_directory, i);
cvSaveImage(bufcv, img, 0);
```

# 結果
- 10,000回学習した結果になります。グラフで見ると1,000回に至る段階で急激にエラー率が落ちていますね。
- とりあえず、100回、1,000回、5,000回、10,000回の結果を表示します。!


![chart_100.jpg](https://qiita-image-store.s3.amazonaws.com/0/214668/501f0dfd-3777-bb4b-fc66-679e41a3e55c.jpeg)![chart_1000.jpg](https://qiita-image-store.s3.amazonaws.com/0/214668/a3271115-6b26-bc9e-81a6-46479f176825.jpeg)
![chart_5000.jpg](https://qiita-image-store.s3.amazonaws.com/0/214668/085a13a0-7a61-7960-00e6-1020c22aaa1f.jpeg)![chart_10000.jpg](https://qiita-image-store.s3.amazonaws.com/0/214668/5d511702-190f-0487-2d70-7ca4c7a8a925.jpeg)

- まずは、前回と同じ画像を入力してみます。
![0.jpg](https://qiita-image-store.s3.amazonaws.com/0/214668/3684ea7a-aabc-061d-17e7-d584eca0ab72.jpeg)
![predictions.jpg](https://qiita-image-store.s3.amazonaws.com/0/214668/d64e0d23-2b2f-c263-e173-290e8c80b38a.jpeg)
- あれっ、YOLOv2よりも精度が悪い・・・
- 今回はイラスト系の画像は一切なく、アニメから抜粋した画像だったから・・・？？
- とりあえず、学習ができていないかったのか・・・・？？？？

- 一応、前回と同様でOPで試してみました。
[ご注文はYOLOv3ですか！？](http://www.nicovideo.jp/watch/sm33174795)


- なんと、そこそこ検出できているではないですか？静止画だと悪いのはなぜ？
- もしかして、YOLOv3のアノテーションの形式がv2とは違う？ってことはないと思うのですが。。。イラスト系も入れて再度学習してみてもいいかもですね。
- ついでに、10,000回のイテレーションに約10時間程度でした。
- 今回は、ココアちゃんとモカさんが識別できているので良しとしますか。


- 引用先

> 1. [OpenCVによるアニメ顔検出ならlbpcascade_animeface.xml](http://ultraist.hatenablog.com/entry/20110718/1310965532)