YOLOv4をRaspberryPi上で動かせなかった件まとめ

#1.この記事の内容

darknetのYOLOv4，YOLOv4-tinyをRaspberryPi上で動作させ，COCO Evaluation Serverを用いてmAP算出しようと試みましたが，YOLOv4はRaspberryPiのメモリ不足による強制終了，YOLOv4-tinyは処理時間が長すぎることで断念しました．
代わりに，PC上（Windows Subsystem for Linux）での推論及びCOCO Evaluation ServerでのmAP算出までを実施しました．

環境構築からmAP算出までの内容をまとめます．

##1-1.使用環境

* darknet
  * YOLOv4
  * YOLOv4-tiny
* Raspberry Pi 3 Model B+
* Windows10
  * WSL2
  * Ubuntu 18.04

#2.darknetの取得・ビルドからCOCO Evaluation Serverまで

MS COCO test-dev2017 datasetの推論結果をCOCO Evaluation Server上でmAP算出する手順を紹介します．
本説明で記載する各ディレクトリは下記の表記とします．
<pre>
$ export WORK_DIR=\<path to your work dir>
$ export DATASET_DIR=\<path to your dataset dir>
$ export PRETRAINED_MODEL_DIR=\<path to your pretrained model dir>
</pre>

##2-1.darknetコードの準備とPretrainedモデルの取得(RaspberryPi, WSL共通)

### darknetコードの取得

<pre>
$ mkdir -p ${WORK_DIR}
$ cd ${WORK_DIR}
$ git clone https://github.com/AlexeyAB/darknet.git
</pre>

### ビルド

<pre>
$ cd darknet
※GPU, CUDNN, OPENCV等を有効にする場合は，Makefileを編集する
　RaspberryPi上ではすべて0のまま．PC上で動作する場合に1にセットする
GPU=0
CUDNN=0
OPENCV=0
　↓
GPU=1
CUDNN=1
OPENCV=1
$ make
</pre>

### Pretrainedモデルの取得

* YOLO v4  
${PRETRAINED_MODEL_DIR}へ[YOLOv4 model zoo](https://github.com/AlexeyAB/darknet/wiki/YOLOv4-model-zoo)からダウンロードしたモデルを格納する．
[YOLOv4 model zoo](https://github.com/AlexeyAB/darknet/wiki/YOLOv4-model-zoo)ではGoogle Drive上でモデルが公開されており，wgetで取得ができない．
* YOLOv4-tiny  
YOLOv4-tinyのPretrainedモデルはgithub上で公開されており，wgetで取得することができます．
<pre>
wget https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov4-tiny.cfg
wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v4_pre/yolov4-tiny.weights
</pre>

## 2-2.MS COCO dataset の準備(RaspberryPi, WSL共通)

Windows PC上で，[公式サイトのダウンロードページ](https://cocodataset.org/#download)から，「2017 Test images [41K/6GB]」をダウンロードして解凍します．解凍したフォルダは共有フォルダに設定します（2-3でRaspberryPiからマウントできるようにします）．

「2017 Test images [41K/6GB]」ファイルサイズ及びファイル数が大きいため，FAT32フォーマットのストレージで扱うことができず，RaspberryPiに接続するmicroSDカードやUSBメモリには格納できません．

共有フォルダの設定方法及びRaspberryPi上でのマウント方法については，2-3を参照してください．

## 2-3.RaspberryPiでWindowsの共有フォルダをマウントする(RaspberryPiのみの手順)

[RaspberryPiでWindowsの共有フォルダをマウントする](https://qiita.com/ryoma-jp/items/ddee8fcf6ab49c5333ec)に記載の手順で，2-2で解凍した「2017 Test images [41K/6GB]」をマウントします．

## 2-4.推論実行(RaspberryPi, WSL共通)

1. 推論対象画像リストの取得とパスの変更  
githubからtestdev2017.txtを取得して，ファイル内のパスを自身の環境に合わせて変更します
<pre>
$ cd ${DATASET_DIR}
$ wget https://raw.githubusercontent.com/AlexeyAB/darknet/master/scripts/testdev2017.txt
</pre>
1. cfg/coco.dataの編集  
cfg/coco.dataのvalid（testdev2017.txtのパス）を自身の環境に合わせて変更します
1. 推論実行  
下記コマンドで推論処理を実行します．  
処理が完了するとresults以下に推論結果（jsonファイル）が格納されます．  
RaspberryPiでは推論結果が得られず，2-5のmAP算出はWindows PCでの実行結果を用いたものとなります．
  * YOLOv4
<pre>
$ mkdir results
$ ./darknet detector valid cfg/coco.data ${PRETRAINED_MODEL_DIR}/yolov4.cfg ${PRETRAINED_MODEL_DIR}/yolov4.weights
</pre>
  * YOLOv4-tiny
<pre>
$ mkdir results
$ ./darknet detector valid cfg/coco.data ${PRETRAINED_MODEL_DIR}/yolov4-tiny.cfg ${PRETRAINED_MODEL_DIR}/yolov4-tiny.weights
</pre>

## 2-5.mAP算出(COCO Evaluation Serverへのアップロード)

resultsディレクトリ以下に出力されるjsonファイルをzip圧縮して，[COCO Evaluation Server](https://cocodataset.org/#detection-eval)へSubmitします．

1. jsonファイルのzip圧縮  
<pre>
$ cd results
$ mv coco_results.json detections_test-dev2017_yolov4_results.json
$ zip detections_test-dev2017_yolov4_results.zip detections_test-dev2017_yolov4_results.json
</pre>
1. [COCO Detection Challenge (Bounding Box)](https://competitions.codalab.org/competitions/20794#participate)へのアップロード
  2. ユーザ登録します（未登録の人）
  2. Participateタブから必要事項を記入してSubmitをクリックします
  2. ファイル選択画面が開くので，detections_test-dev2017_yolov4_results.zipを指定します
  2. 一覧表に追加されて，STATUSがRunningと表記され，評価中の状態になります
  2. サーバ上で評価が完了するとSTATUSがFinishedに変わり，SCOREが得られます  
Finishedには自動では変わらないので，10分程度経過後にブラウザをリロードして確認します
  2. "View scoring output log"をクリックすると，評価結果の詳細を確認できます
<pre>
overall performance
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.436
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.644
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.479
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.247
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.468
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.557
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.340
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.557
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.597
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.389
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.641
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.757
Done (t=404.87s)
</pre>

#3.補足：推論処理実行結果について

RaspberryPi上で推論結果を出力できなかった件について，補足として記載します．

## RaspberryPi上でのYOLOv4動作について

RaspberryPi上でYOLOv4の推論を実行すると「強制終了」のメッセージとともに，darknetが落ちました．

<pre>
$ ./darknet detector valid cfg/coco.data pretrained_model/yolov4/yolov4.cfg pretrained_model/yolov4/yolov4.weights
 GPU isn't used
 OpenCV isn't used - data augmentation will be slow
results: Using default 'results'
mini_batch = 1, batch = 8, time_steps = 1, train = 0
   layer   filters  size/strd(dil)      input                output
   0 conv     32       3 x 3/ 1    512 x 512 x   3 ->  512 x 512 x  32 0.453 BF
   1 conv     64       3 x 3/ 2    512 x 512 x  32 ->  256 x 256 x  64 2.416 BF
   2 conv     64       1 x 1/ 1    256 x 256 x  64 ->  256 x 256 x  64 0.537 BF
   3 route  1                                      ->  256 x 256 x  64
   4 conv     64       1 x 1/ 1    256 x 256 x  64 ->  256 x 256 x  64 0.537 BF
   5 conv     32       1 x 1/ 1    256 x 256 x  64 ->  256 x 256 x  32 0.268 BF
   6 conv     64       3 x 3/ 1    256 x 256 x  32 ->  256 x 256 x  64 2.416 BF

・・・（中略）

Loading weights from pretrained_model/yolov4/yolov4.weights...
 seen 64, trained: 0 K-images (0 Kilo-batches_64)
Done! Loaded 162 layers from weights-file
Learning Rate: 0.00261, Momentum: 0.949, Decay: 0.0005
 Detection layer: 139 - type = 28
 Detection layer: 150 - type = 28
 Detection layer: 161 - type = 28
4
強制終了
</pre>

Netdataでモニタリングするとメモリ消費により落ちている傾向があり，メモリ不足により実行できなかったと考えられます．

![メモリモニタリング_YOLOv4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983907/0c588426-8920-86a6-e6fe-3b1da931e0b1.png)

## RaspberryPiでのYOLOv4-tiny動作について

YOLOv4-tinyはメモリ不足による強制終了は発生しませんが，画像1枚あたり約36秒かかる（約0.028[fps]）為，全20288枚に要する推論時間は約8日となります．
私の都合で8日間連続動作させることができませんでしたので，推論結果は出さずに保留しました．連続動作させれば推論結果を得ることができると思いますので，可能な方は実施してみてもらえればと思います．

* モニタリングの結果  
![メモリモニタリング_YOLOv4-tiny.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983907/c045a895-f266-bea5-e1e9-4bca10493bac.png)
* 推論時間計測結果  
![YOLOv4-tiny_実行時間計測.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983907/50ab6ffe-3243-bb00-a034-483706313b26.png)  
時間計測用のコード変更内容は下記のとおりです．  
<pre>
diff --git a/src/detector.c b/src/detector.c
index a2fdf0b..b6f1368 100644
--- a/src/detector.c
+++ b/src/detector.c
@@ -662,7 +662,7 @@ void validate_detector(char *datacfg, char *cfgfile, char *weightfile, char *out
         layer lk = net.layers[k];
         if (lk.type == YOLO || lk.type == GAUSSIAN_YOLO || lk.type == REGION) {
             l = lk;
\-            printf(" Detection layer: %d - type = %d \n", k, l.type);
\+            printf(" Detection layer: %d - type = %d (in validate_detector())\n", k, l.type);
         }
     }
     int classes = l.classes;
@@ -747,8 +747,9 @@ void validate_detector(char *datacfg, char *cfgfile, char *weightfile, char *out
         thr[t] = load_data_in_thread(args);
     }
     time_t start = time(0);
\+    printf("m + nthreads = %d\n", m + nthreads);
     for (i = nthreads; i < m + nthreads; i += nthreads) {
\-        fprintf(stderr, "%d\n", i);
\+        fprintf(stderr, "i %d, elapsed_time %f [sec]\n", i, (double)time(0) - start);
         for (t = 0; t < nthreads && i + t - nthreads < m; ++t) {
             pthread_join(thr[t], 0);
             val[t] = buf[t];
</pre>


#4.さいごに

推論結果はWindows PC（WSL, RTX 2070 SUPER）で出力したもので，[YOLOv4 model zoo](https://github.com/AlexeyAB/darknet/wiki/YOLOv4-model-zoo)と推論実行環境が異なる為，結果の一致はしませんでしたが，YOLOv4 model zoog記載の数値（AP=43.0，AP_50=64.9，AP_75=46.5，…）とほぼ同程度の結果が得られましたので，環境構築手順及び実行手順，結果は妥当なものと思います．

Raspberry Pi 3 Model B+上での推論結果算出は断念しましたが，Raspberry Pi 4B メモリ8GB版などを購入した際に試行できれば，追記したいと思います．

#5.関連リンク

* [darknet](https://github.com/AlexeyAB/darknet)
* [YOLOv4 model zoo](https://github.com/AlexeyAB/darknet/wiki/YOLOv4-model-zoo)
* [Pre-trained models](https://github.com/AlexeyAB/darknet#pre-trained-models)
* [Training and Evaluation of speed and accuracy on MS COCO](https://github.com/AlexeyAB/darknet/wiki#training-and-evaluation-of-speed-and-accuracy-on-ms-coco)
* [How to evaluate accuracy and speed of YOLOv4](https://github.com/AlexeyAB/darknet/wiki/How-to-evaluate-accuracy-and-speed-of-YOLOv4)
* [COCO dataset; Detection Evaluation](https://cocodataset.org/#detection-eval)
* [Upload Results to Evaluation Server](https://cocodataset.org/#upload)
* [RaspberryPiでWindowsの共有フォルダをマウントする](https://qiita.com/ryoma-jp/items/ddee8fcf6ab49c5333ec)
* [COCO Detection Challenge (Bounding Box)](https://competitions.codalab.org/competitions/20794#participate)
* [ファイルシステム別の制限事項まとめ](https://qiita.com/ryoma-jp/items/f9124a057ac8c646ae61)
* [Raspberry Piのシステムモニタリング](https://qiita.com/ryoma-jp/items/f20616ad75d949d0e360)