labelImgで自前の学習データを作ってYOLOv3で学習させる

#目的
YOLOv3を使ってobject detectionをするため

#環境
ubuntu16.04
Geforce 1080ti
YOLOv3(YOLOのインストール方法は[こちら](https://qiita.com/okubo999/items/79e1d97a7a3a3de70de2)）


#だいじポイント
YOLOはjpg対応です。
拡張子.jpgを探しているみたいなので.jpegもだめです。
集めた画像は.jpgに変換して準備します。
画像が.jpegになっていて少々時間を取りました…

また、画質が良すぎてもメモリが足りなくなるし、悪すぎても計算がうまくできないらしいので、
画像は程よいサイズが良いです。100kBくらい？


#画像を集めてくる
今回はちょっとした実験なので250枚くらいを使って学習してみます。

#labelImgのインストール
[github] https://github.com/tzutalin/labelImg


まず、色々必要なものをインストール

```
sudo apt install pyqt5-dev-tools
sudo apt install python3-pip
sudo pip3 install lxml
```

そして、自分のworkspaceにlabelImgをダウンロード

```
cd workspace  
git clone https://github.com/tzutalin/labelImg.git
```

labelImgに移動してmake

```
cd labelImg
make qt5py3
```

1行分の英語しか返ってこないですが、エラーが出てなければおｋです。


実行

```
python3 labelImg.py
```

labelimgフォルダの中でプログラムを実行してlabelImgのUIが表示されたら完了



#labelImgの使いかた
参考にさせていただきました:bow:
https://demura.net/misc/14350.html
http://takesan.hatenablog.com/entry/2018/08/16/013452



- labelImgの中に好きな名称のディレクトリ(**testv1**とする)を作成し画像を入れます。
- labelImg/data の中にあるpredefined_class.txtの中身を全部消し、自分用にクラス名を改行して書き込みます。
- 日本語でクラスを記入すると後に学習結果を表示させるときにsegmentation errorが出るので英語表記に

```
apple
banana
lemon
..
```

labelImgフォルダの中でプログラムを起動します。

```
python3 labelImg.py
```

#labelImgが起動したら初めに設定すること

1. OpenDir で**testv1**を選択しopenする
2. Change Save Dir で上と同じディレクトリを選択しているか確認
3. Saveの下にある　PscalVOC を選択して、必ずYOLOに変更
- ~~上部のメニューバーからView->Single Class Modeにチェックを入れる。~~

今回は4はしてませんが、このモードにするとずっと同じobjectのアノテーションをするときに、最初にクラス選択をしたら連続でデータを入力できるので便利です。



#アノテーション手順

作業環境が整ったらアノテーションに移ります。
基本的な作業の流れは以下

1. Create nRectBox or Wキーで十字キーを表示させる
2. 対象物を四角で囲めたらラベルを選んでok 
3. save or ctrl+Sキーで保存
4. Next Image or Dキーで次の画像へ進む

これを繰り返します。

W -> ラベル選択+ok -> ctrl+S -> D -> W　…

で基本は流れていきます。



# YOLOの下準備　testデータとtrainデータに分ける

**testv1**の中にJpgファイルとtxtファイルが全てきちんと対になるようにデータが揃いましたら、
YOLOv3をインストールしているディレクトリのdarknet/data の中に、そのjpgとtxtの入った**testv1**ごとコピーします。
ついでに、さらにその中に空のbackupフォルダを作っておきます。

```
cd /darknet/data/testv1
mkdir backup
```

さらにその**testv1**の中にprocess.pyの名称で、以下の内容のファイルを作成
percentage_test=10のところでテストデータの割合は変えられます。

```
atom process.py
```

```
# -*- coding: utf-8 -*-
import glob, os

# Current directory
current_dir = os.path.dirname(os.path.abspath(__file__))

# Directory where the data will reside, relative to 'darknet.exe'
path_data = 'data/testv1/'     # ------>適宜変えます。

# Percentage of images to be used for the test set
percentage_test = 10;

# Create and/or truncate train.txt and test.txt
file_train = open('train.txt', 'w')  
file_test = open('test.txt', 'w')

# Populate train.txt and test.txt
counter = 1  
index_test = round(100 / percentage_test)  
for pathAndFilename in glob.iglob(os.path.join(current_dir, "*.jpg")):  
    title, ext = os.path.splitext(os.path.basename(pathAndFilename))

    if counter == index_test:
        counter = 1
        file_test.write(path_data + title + '.jpg' + "\n")
    else:
        file_train.write(path_data + title + '.jpg' + "\n")
        counter = counter + 1
```

そして実行

```
python process.py
```

これでランダムにtrainデータとtestデータに振り分けられ、各々のtrain.txtとtest.txtファイルが作成されます。
percentage_test = 10; の数字はテストデータの割合を表していて、お好みで変えても良いです。

# cfgファイルを編集

もともと用意されてる設定ファイルをコピーして編集します。

```
cd darknet/cfg
cp yolov3-voc.cfg testv1.cfg
atom testv1.cfg 
```

3,4行目をコメント化．6,7行目の#を外します。
classes=8とするなら、filters=39(=classes+5)*3)

```
2行目# Testing
3行目#batch=1
4行目#subdivisions=1
5行目# Training
6行目batch=64　#32
7行目subdivisions=16    


605行目 filters=39
611行目 classes=8
689行目 filters=39
695行目 classes=8
773行目 filters=39
779行目 classes=8
```


batchというのは1度に処理する画像の枚数で、多いほうが学習レベルは高くなるらしいです。
ただ似たような画像がかたまって入ったりすると精度が落ちることもあるとか。
subdicisionsはそれを何回に分けるかを表します。

<img width="250" alt="ios__________.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506640/a874e410-e3f2-61b6-5c0c-8fecef929b23.jpeg">
手書きのメモ恥ずかしいからちっさく載せとこう。

もし、batch=64,subdivision=16 でout of memoryになるときは、batch=32,subdivision=8に落とします。

あとは、`nvidia-smi`でGPUの使用状況を確認できるのですが、
chromeとかその他諸々のアプリケーションを止めて、学習だけにするとGPUの使用率が下がるので、それもout of memoryの対策として有効です。

さらに私は、デュアルディスプレイでやってたのを、シングルかつ解像度も落としてみたのですが、xorgのGPU使用率が半分近く減りました。



#testv1.namesとtestv1.dataファイルを作る

そのcfgディレクトリの中にさらにファイルを作成します。
このファイルとclasses.txtは揃えます。

```
nano testv1.names 
```
```
apple
banana
lemon
..
```
そして色々参照先を示すファイルも作成

```
nano testv1.data
```
```
classes=8
train = data/testv1/train.txt
valid = data/testv1/test.txt
names = cfg/testv1.names
backup = data/testv1/backup
```

# いよいよ学習を実行する
darknet dir の中で実行

```
./darknet detector train cfg/testv1.data cfg/testv1.cfg darknet53.conv.74
```
darknet53.conv74は./darknetの中にあらかじめ↓からダウンロードしておきました。
> Download pre-trained weights for the convolutional layers (154 MB):  http://pjreddie.com/media/files/darknet53.conv.74 and put to the directory build\darknet\x64


#学習にかかった時間

学習回数の設定を何も変えずに学習させたら1.5日くらいかかりました。

# テストする
学習で自動的に保存されたウェイトを使ってテストします。
テスト用設定ファイルを作成します。

```
cd ~/src/darknet/cfg
cp wrs_train.cfg testv1_test.cfg
```
testv1_test.cfgの１〜7行目を以下のように変更します。

```
# Testing
batch=1
subdivisions=1
# Training
#batch=64
#subdivisions=16 # 16
```


#画像の場合
テストデータをフォルダに入れて実行

```
./darknet detector test cfg/testv1.data cfg/testv1_test.cfg ~/workspace/YOLOv3/darknet/data/testv1/backup/testv1.backup ~/workspace/YOLOv3/darknet/data/test.jpg 
```
.backupファイルとjpgファイルの場所は適宜変更してください。
.backupはウェイトの種類なので好きなウェイトのファイルを選びます。

できました:dancer:

#カメラの場合

```
./darknet detector demo cfg/testv1.data cfg/testv1_test.cfg ~/workspace/YOLOv3/darknet/data/ceiling/backup/testv1_final.weights 
```

こちらも無事成功。


# おわりに

何とか自前の学習データを使って学習させることができました
学習回数の設定は何も変えなかったので、最後500000.wightsまで保存されていましたが、100000.weightsとの差は体感的には感じられませんでした。
100000回でもいいかな？

もし学習回数を変更する場合は、

https://demura.net/misc/14458.html
> なお、学習の終了条件は、wrs.cfgファイルの20行目で次のように50万200回と大きな値になっているので適宜変更する。
max_batches = 500200


です！