自前データをYOLOv4で学習させるためのToolkit

# darknetを使用してみる
windowsで`darknet`を使用して機械学習(物体検出)に触れてみたいと思います。
いちいち設定ファイルを作成するのはめんどくさいので、なるべく簡単に設定ファイルを作成できるように[YoloToolkit](https://github.com/kyokio6002/YoloToolkit)を作りました。

https://github.com/kyokio6002/YoloToolkit

使い方を説明しながら実際に自前データで学習を行っていきます。

# 1.darknetのセットアップ
フレームワークに`darknet`を使用します。
YOLOの[原論文](https://arxiv.org/abs/1506.02640)を書いた人が作ったフレームワークです。(YOLOv4からは違う人が受け継いでます)

https://github.com/AlexeyAB/darknet


`darknet`のセットアップは今回はできている前提で進めます。
できていない人は下記を参考にセットアップしてみてください。
・[Windows で Darknet を使って 機械学習する](https://noitalog.tokyo/windows-darknet/)
・[YOLOv4環境構築①](https://qiita.com/kzsDev/items/89731ffe7df4e1cf4628)
・[YOLOv4環境構築②](https://qiita.com/kzsDev/items/85d117195f44a86dd8fe)

# 2.YoloToolkitをcloneする

https://github.com/kyokio6002/YoloToolkit

このリポジトリをcloneします。(cloneして名前が変更されている場合は`YoloToolkit`に変更してください)
簡単に学習できるようなプログラムやフォルダが集まっています。

```
C:.
├─backup       # weightのバックアップ
├─cfg          # Yolov4の設定ファイル
├─images       # アノテーションに使用する画像
├─net          # ネットワーク 
├─toolkit      # toolkit(*.py)　
├─VoTT         # VoTTの出力先
└─YoloDataset  # 学習用のデータセット
```

# 3.データセットの作成
データセットを作成していきます。
今回はドライブレコーダーで撮影した映像からナンバープレートと車を検出するものを作成します。

## 3.1.画像収集
今回はドライブレコーダーで撮影された映像から学習に必要なデータを収集します。
データの収集方法は何でもいいです。

収集した画像は`imagesディレクトリ`内に**コピーしてください。**
各種コードは`.jpg`の画像を想定しているため拡張子は.jpgにしてください。(ほかの拡張子でも動作するようにそのうち変更します)

## 3.2.画像の整形
~~アノテーションを行う前に画像サイズを固定する必要があります。
アノテーション後にサイズを変更すると出力されるxmlファイルに書かれた座標がずれてしまい、アノテーションを再度する必要が発生する可能性があります。~~

~~YOLOv4は学習データの画像を32の倍数にする必要があります。
今回は416×416を使用します。
※自分のPCスペックを考えてサイズを決めてください。大きすぎるとメモリが足りなくなります。
今回はリサイズではなく、トリミングを使用して1920×1080の画像をトリミングします。~~

追記 2022/5/21
動作未確認ですが、コメントよりDarknetはcfgファイルのサイズに自動でリサイズされるようです。
今回は事前にトリミングを行なってからアノテーションを行います。

### トリミング(trimming.py)
1. `trimming.py`を実行して、拡張子、x座標、y座標、幅、高さを入力してください。
 x座標、y座標はトリミングする領域の左上の座標を指定します。
1. バリデーションが実行されて問題がない場合、サンプルとして領域が書き込まれたサンプル画像が表示されます。
1. サンプル画像に表示された領域でトリミングを行う場合、`y`を入力すると全ての画像に対してトリミングが実行されます。

### リサイズ(resize.py)
1. `resize.py`を実行して、拡張子、幅、高さを入力してください。
1. アスペクト比を保持したままリサイズされ、足りない場所は白で埋められます。


## 3.3.アノテーション(VoTT)
学習画像をリサイズやトリミングで固定サイズに整形できたらアノテーションを行います。
今回はアノテーションに[VoTT](https://github.com/Microsoft/VoTT/releases)を使用します。

https://github.com/Microsoft/VoTT/releases

YoloToolkitを使用する際のVoTTの使用方法は下記スライドを参考にしてください。

<iframe src="//www.slideshare.net/slideshow/embed_code/key/B8CDHjRaq6kEKZ" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/kyokio/vott-with-yolotoolkit" title="Vott with YoloToolkit" target="_blank">Vott with YoloToolkit</a> </strong> from <strong><a href="https://www.slideshare.net/kyokio" target="_blank">kyokio</a></strong> </div>

VoTTをインストールできていない人は下記を参考にインストールしてください。
・[物体検出のアノテーションツール VOTT の使い方](https://pystyle.info/object-detection-annotation-tool-vott-tutorial/)
・[VoTTの使い方](https://aidynamics.jp/services/vott_manual)

## 3.4.makedataset.pyを実行する
`toolkitディレクトリ`内にある`makedataset.py`を実行します。
`makedataset.py`を実行すると下記がまとめて実行されます。

**①アノテーションの形式変更(xml->txt)**
convert.pyを実行すると以下が実行される。
・`xmlファイル`をパースしてYOLOv4形式の`txtファイル`を作成し`YoloDatasetディレクトリ`に保存する
・`xmlファイル`に対応する`画像ファイル`も`txtファイル`と同時に`YoloDatasetディレクトリ`にコピーする

**②データの分割(train,val,test)**
アノテーションされたファイルを`train`,`valid`,`test`に分割します。
デフォルトでは、train=70%,valid=20%,test=10%に分割します。
※変更したい場合は、`makedataset.py`の`data_split関数`の[train_rate](https://github.com/kyokio6002/YoloToolkit/blob/master/toolkit/makedataset.py#L150),[valid_rate](https://github.com/kyokio6002/YoloToolkit/blob/master/toolkit/makedataset.py#L151)を変更してください。

各種振り分けられた結果は`cfgディレクトリ`内に`train.txt`,`valid.txt`,`test.txt`が作成され書き込まれます。

**③設定ファイルの作成**
YOLOv4を実行するのに必要な設定ファイルを作成します。

|ファイル名　 |用途　　　　　　　　　　　　　　　|
|:-----------|------------------------------:|
| .name      |アノテーションされたラベル対応する|
| .data      |class数、各種pathを記入する　　　|

それぞれ`classes.name`,`test.data`で作成され設定が書き込まれます。


# 4.cfgファイルを作成
ネットワークの設定を行います。
書き方は[darknetのREADME](https://github.com/AlexeyAB/darknet#how-to-train-to-detect-your-custom-objects)に書いてあります。
最初は最低限の変更だけでいいと思います。
`netディレクトリ`内に[yolov4-custom.cfg](https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4-custom.cfg)を入れてあるので各自変更してください。(他ネットワークも入ってる)

### 変更部分
**[net]**
それぞれ一か所ずつ
・`batch=64`
・`subdivisions=64`
　それぞれスペックによって設定([[7]](https://ask.csdn.net/questions/7485777),[[9]](https://github.com/pjreddie/darknet/issues/224)))
・`width=(アノテーションを行った画像のサイズ)`  # 今回は416
・`height=(アノテーションを行った画像のサイズ)`  # 今回は416
・`max_batches=(クラス数*2000)もしくは6000の大きい方`  # 今回は6000
・`steps=(max_batchesの80%,90%)`  # 今回は4800,5400

**[convolutional]**
[yolo]の前の層の[convolutional]３ヶ所→[(1)](https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4-custom.cfg#L963)、[(2)](https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4-custom.cfg#L1051)、[(3)](https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4-custom.cfg#L1139)
・`filters=(クラス数+4+1)×3`  # 今回は21

**[yolo]**
[yolo]３ヶ所→[(1)](https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4-custom.cfg#L970)、[(2)](https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4-custom.cfg#L1058)、[(3)](https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4-custom.cfg#L1146)
・`classes=(クラス数)`  # 今回は2

# 5.weightを用意する
学習済みのweights([yolov4.conv.137](https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.conv.137))をダウンロードして`cfgディレクトリ`内に配置してください。

# 6.学習を実行
`cd [path to darknet]/darknet/build/darknet/x64`でdarknet.exeがある場所まで移動して下記コマンドを実行してください。

```powershell
darknet detector train [path to YoloToolkit]/cfg/test.data [path to YoloToolkit]/net/yolov4-custom.cfg [path to YoloToolkit]/cfg/yolov4.conv.137 -map
```
※`[path to YoloToolkit]`は`YoloToolkit`をcloneした場所までのpathです。
※ pathが通ってない場合はdarknet.exeのpathを通してください

ネットワークが表示された後に学習が開始されたら学習が開始されます。

![スクリーンショット 2022-01-03 035013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356842/ca4dc739-e7e1-c79a-73b7-bb42a2d92d76.png)


# 7.結果

学習がある程度進んだのでepochを1000で止めました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356842/081cf494-3aea-f401-14ba-f2916d3d331f.png" alt="代替テキスト" width="500" height="500">

cfgディレクトリ内のtest.txtに書いてあるtest画像でtestを行ってみます。

##### comand prompt
```
darknet detector test [path to YoloToolkit]/cfg/test.data  [path to YoloToolkit]/cfg/yolov4.cfg  [path to YoloToolkit]/backup/yolov4-custom_best.weights < [path to YoloToolkit]/cfg/test.txt
```
##### powershell
```powershell
 cat [path to YoloToolkit]/cfg/test.txt | darknet detector test [path to YoloToolkit]/cfg/test.data  [path to YoloToolkit]/cfg/yolov4-custom.cfg  [path to YoloToolkit]/backup/yolov4-custom_best.weights
```

今回はiterationが1000で止めたのでweightsにyolov4_1000.weightsを使用しました。
１枚目は結構うまくいってます。
２枚目はナンバープレートを二重で検出してしまっています。
３枚目はうまくいっていますが通行人も車として検出してしまっています。
もう少し学習を進めるとうまくいくかもしれないです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356842/334c8ea2-6291-455f-ffd0-9a11a7ca0ca6.jpeg" width=302> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356842/d6c8cb58-1958-e5bd-472d-d159acf9539d.jpeg" width=302> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356842/a819c617-9de6-cb0c-da04-ceabbe0caf02.jpeg" width=302>



# 動作確認環境
PC: Leginon 7 15IMH05
OS: Windows 10 Home
CPU: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz   2.59 GHz
GPU: NVIDIA GeForce RTX2080 Super with Max-Q Design
RAM: 32GB


# 参考
[[1]Yolov4を使ったオリジナルデータの学習(with Ubuntu20.04)](https://qiita.com/kitata9/items/936c97f0ac33dae13ab5)
[[2]AlexeyAB/darknet](https://github.com/AlexeyAB/darknet)
[[3]YOLOv4（Darknet）で異常検知モデル作成挑戦（GoogleColab）](https://qiita.com/taichinakabeppu/items/e4d38f19c4041b9f4fc3)
[[4]Darknet YOLOv4 を Google Open Images Datasetのデータを使って学習させる with Elixir](https://qiita.com/the_haigo/items/76d1ee732f63f6b6d979)
[[5]アノテーションファイル変換（xmlからYOLOへ）](https://qiita.com/cv_carnavi/items/bd909df26d159401d166)
[[6]Windows で Darknet を使って 機械学習する](https://noitalog.tokyo/windows-darknet/)
[[7]Error: cuDNN isn't found FWD algo for convolution](https://ask.csdn.net/questions/7485777)
[[8]Error: l.outputs == params.inputs filters= in the [convolutional]-layer doesn't correspond to classes= or mask= in [yolo]-layer #2263](https://github.com/pjreddie/darknet/issues/2263)
[[9]what's the meaning of 'subdivisions' and how to set it's value? #224](https://github.com/pjreddie/darknet/issues/224)
[[10]【Darknet】実行コマンドまとめ](https://ichiya.netlify.app/posts/2019/12/22/_20191222.html)
[[11]YOLOv4環境構築①](https://qiita.com/kzsDev/items/89731ffe7df4e1cf4628)
[[12]YOLOv4環境構築②](https://qiita.com/kzsDev/items/85d117195f44a86dd8fe)
[[13]物体検出のアノテーションツール VOTT の使い方](https://pystyle.info/object-detection-annotation-tool-vott-tutorial/)
[[14]VoTTの使い方](https://aidynamics.jp/services/vott_manual)