![](https://hackmd.io/_uploads/ryZ06v9K2.png) ## 目錄 [toc] ## 前言 最近公司專案的物件辨識需要使用樹梅派推論,筆者查找了一下資料後決定使用Yolo Fastest V1 這個開源框架來做,因為是第一次使用darknet ,著實吃了許多苦頭,所以詳細記錄一下步驟以及一些網路上查到的坑點,避免自己以後還會犯錯。 ## Base 1. 訓練主機 : CPU : Intel I7–10700 GPU: GTX 1660 Super OS : Ubuntu 20.04 LTS CUDA : 11.0 2. 推論主機 : Raspberry Pi 4 Model B 4GB OS : raspbian 64 bit ## 訓練主機配置 ### 下載相依性套件 1. 下載Yolo Fastest V1 專案,安裝openCV(C++),並打開Makefile配置 ``` git clone https://github.com/jerry800416/Yolo-Fastest sudo apt-get install libopencv-dev cd Yolo-Fastest nano MakeFile ``` 2. 編譯設定 設定編譯環境 如下,請依照自己的訓練機器配置調整,主要要調整的是GPU和CUDNN,其他的如果有裝OPENCV C++ 版的記得設定為1,其他的設定基本上不用動 ``` # set GPU=1 and CUDNN=1 to speedup on GPU GPU=1 CUDNN=1 # set CUDNN_HALF=1 to further speedup 3 x times (Mixed-precision on Tensor Cores) GPU: Volta, Xavier, Turing and higher CUDNN_HALF=0 OPENCV=1 # set AVX=1 and OPENMP=1 to speedup on CPU (if error occurs then set AVX=0) AVX=1 OPENMP=0 LIBSO=0 ZED_CAMERA=0 ZED_CAMERA_v2_8=0 ``` ### 編譯Darknet 1. 新建build 資料夾,並且編譯Darknet ``` mkdir build2 cd build2/ cmake ../ # j後面帶的是核心數 make -j16 ``` 2. 這時候如果有跳出cmake版本過低,按照以下步驟繼續 a.請刪除舊版並安裝新版本cmake,網址如下[點我](https://cmake.org/download/) b.找到`cmake-3.22.0-rc2-linux-x86_64.sh` 並點選下載,然後繼續安裝cmake ``` sudo cp cmake-3.22.0-rc2-linux-x86_64.sh /opt/ cd /opt/ sudo chmod +x cmake-3.22.0-rc2-linux-x86_64.sh sudo bash cmake-3.22.0-rc2-linux-x86_64.sh sudo ln -s /opt/cmake-3.22.0-rc2-linux-x86_64/bin/* /usr/local/bin ``` 3. 確認 版本正確後就可以繼續編譯 ``` cmake — version ``` 4. 編譯完成會在build2 資料夾下面發現darknet 可執行檔,代表編譯成功 ### 生成預訓練權重 1. 在build2資料夾下打上以下指令,會在build2資料夾下產生yolofastest 預訓練權重 ``` ./darknet partial ../ModelZoo/yolo-fastest-1.1_coco/yolo-fastest-1.1.cfg ../ModelZoo/yolo-fastest-1.1_coco/yolo-fastest-1.1.weights yolo-fastest.conv.109 109 ``` 2. 當然fastest - xl 的預訓練權重也可以生成,只需要將指令更改即可 ``` ./darknet partial ../ModelZoo/yolo-fastest-1.1_coco/yolo-fastest-1.1-xl.cfg ../ModelZoo/yolo-fastest-1.1_coco/yolo-fastest-1.1-xl.weights yolo-fastest-1.1-xl.conv.109 109 ``` ### 資料前處理(標框) 這裡假設已經蒐集完所有的圖檔,要將圖片中要辨識的物件標出來 1. 安裝Label Image 套件 ``` sudo apt-get install labelImg ``` 2. labelImg 打開視窗 ![](https://hackmd.io/_uploads/BJMxZu5th.png) 3. 點選開啟目錄,選擇圖片資料夾 開始標記(記得把資料型態改成yolo) ![](https://hackmd.io/_uploads/SkiWZ_cKh.png) 4. 標記完成後數據會如下圖,資料夾下會有圖檔、txt檔、class檔 ![](https://hackmd.io/_uploads/S1Kz-_5Yh.png) ### 資料前處理(產生darknet格式的數據) 1. build2 資料夾下新建data 資料夾 2. 在data 資料夾下新建images 和 labels 資料夾、train.txt空白檔案、test.txt空白檔案 3. 將步驟2產出的txt檔案複製到labels資料夾下(不包含 classes.txt) 4. 將步驟2的圖檔複製到images資料夾下 5. 將步驟2的classes.txt複製到data資料夾下,並更名為screw.names 6. 在data 資料夾下新建screw.data 並新增內容如下 ``` # classes 代表幾個class , 可以打開screw.names 確認數量對不對 classes= 2 train = data/train.txt valid = data/test.txt names = data/screw.names # backup 代表模型儲存的路徑 backup = backup ``` 7. 在data資料夾下新建`makedata.py` 內容如下 ```Python import os import random # 自行決定切分比例 trainval_percent = 0.2 train_percent = 1 imgfilepath = './images' total_img = os.listdir(imgfilepath) num = len(total_img) list1 = range(num) tv = int(num * trainval_percent) tr = int(tv * train_percent) trainval = random.sample(list1, tv) train = random.sample(trainval, tr) ftest = open('./test.txt', 'w') ftrain = open('./train.txt', 'w') for i in list1: name = total_img[i] if i in trainval: if i in train: ftest.write('data/images/'+name+'\n') else: ftrain.write('data/images/'+name+'\n') ftrain.close() ftest.close() ``` 8. 執行`makedata.py` * 這時後會發現train.txt 和 test.txt 裡面已經分好train data的路徑和test data的路徑了 9. 將labels 資料夾裡面的txt檔案複製到images資料夾內就完成製作darknet 訓練資料的動作了 ### 配置訓練參數 1. 將`Yolo-Fastest/ModelZoo/yolo-fastest-1.1_coco/yolo-fastest-1.1.cfg` 和 `yolo-fastest-1.1-xl.cfg` 複製到`Yolo-Fastest/build2` 資料夾下 2. 打開`yolo-fastest-1.1.cfg` 第2,3行,這裡依你的GPU ram 大小調整,如果後續訓練時出現out of memory 再回來調整 ``` [net] batch=16 subdivisions=8 ``` 3. 第16~20行 `burn_in` 和 `max_batches` 裡面設定的是訓練趟數(epoch),`steps` 裡面的兩個數字也要改,公式是 `epoch`* 0.8 , `epoch` * 0.9,代表的是learning rate 的開始衰變步數,`scales` 代表衰變幅度 ``` burn_in=8000 max_batches=8000 policy=steps steps=6400,7200 scales=.1,.1 ``` 4. 第858,926行的`filter`參數,這裡修改的公式為`filter = (class數量+5)*3`,注意只需要修改這兩行的參數,其他的`filter`不需要修改 ``` [convolutional] size=1 stride=1 pad=1 filters=21 ``` 5. 在866 和934 行的`classes` 也要修改成你自己的class數量 6. 以上是修改`yolo-fastest-1.1.cfg`,若是要用`yolo-fastest-1.1-xl.cfg`,行數會不一樣,只需找到相對應的參數名稱修改即可 ### 開始訓練 1. 在build2 資料夾下新建backup 資料夾 ``` mkdir backup ``` 2. 在build2 資料夾下輸入以下開始訓練(darknet 支援許多後綴參數,詳細情形可以自己google,這裡僅列出最基本的) ``` ./darknet detector train data/screw.data yolo-fastest-1.1.cfg yolo-fastest-1.1.conv.109 backup/ -clear ``` 3. 訓練時會出現loss下降圖,可以觀察LOSS 下降的情況,注意一開始訓練的時候LOSS 很大不會出現在圖片上,LOSS 低於20才會出現 ![](https://hackmd.io/_uploads/By_Y4dcYn.png) 4. 訓練完成會在backup 資料夾下生成.weights檔案 ### 測試 ``` ./darknet detector test data/screw.data yolo-fastest-1.1.cfg backup/yolo-fastest-1_final.weights <這裡輸入你要測試圖片的路徑 > -thresh 0.55 ``` ![](https://hackmd.io/_uploads/rJfaNu9Kn.png) ## 推論主機配置 ### 安裝系統 1. 拿出你的樹梅派,然後確認它還是可用的 * 還有樹梅派的攝影機或是webcam,我自己是使用羅技的C310 2. 安裝64位元的raspbian系統 * 前往樹梅派官方地址 並下載最新64位元映像檔 [點我](https://downloads.raspberrypi.org/raspios_arm64/images/) ### 編譯NCNN 1. 前置環境配置 ``` sudo apt install build-essential git cmake sudo apt-get install -y gfortran sudo apt-get install -y libprotobuf-dev libleveldb-dev libsnappy-dev libopencv-dev libhdf5-serial-dev protobuf-compiler libvulkan-dev vulkan-utils sudo apt-get install — no-install-recommends libboost-all-dev sudo apt-get install -y libgflags-dev libgoogle-glog-dev liblmdb-dev libatlas-base-dev ``` 2. 下載NCNN庫 並編譯 ``` git clone https://github.com/Tencent/ncnn cd ncnn mkdir build cd build cmake ../ make -j4 make install ``` 3. 在build資料夾下會出現三個資料夾examples、install、tools,至此ncnn編譯成功。 ### 轉換Model #### 準備檔案 1. clone repo ``` cd ~ git clone https://github.com/jerry800416/Yolo-Fastest cd ~/ncnn/build/install ``` 2. 將install 資料夾下的bin、include、src 資料夾複製到 Yolo-Fastest/sample/ncnn 資料夾下 3. 在ncnny 資料夾下新建model 資料夾 4. 將訓練主機訓練出來的`yolo-fastest-1_final.weights`檔案和`yolo-fastest-1.1.cfg`配置檔複製到剛剛建立的Model 資料夾內 #### 開始轉換 1. 執行轉換程式 ``` cd ~/ncnn/build/tools/darknet ./darknet2ncnn ../../../Model/yolo-fastest-1.1.cfg ../../../Model/yolo-fastest-1_last.weights ../../../Model/yolo-fastest-screw.param ../../../Model/yolo-fastest-screw.bin ``` 2. 跑完會在Model 資料夾下產生`yolo-fastest-screw.param`、`yolo-fastest-screw.bin` 兩隻檔案 3. 將這兩個檔案複製到Yolo-Fastest/sample/ncnn/model 資料夾內 #### 轉換bf16 浮點數格式 1. 執行`YoloDet.cpp` ``` nano ~/Yolo-Fastest/sample/ncnn/src/YoloDet.cpp ``` 2. 在 int YoloDet::init 方法內的開頭新增以下兩行,並保存退出 ``` DNet.opt.use_packing_layout = true; DNet.opt.use_bf16_storage = true; ``` #### 指定運行的CPU核心數 1. 打開`YoloDet.h` ``` nano ~/Yolo-Fastest/sample/ncnn/src/include/YoloDet.h ``` 2. 將`# define NUMTHREADS 8` 的 8 改成 4 ,保存退出 ### 運行DEMO #### 新增demo_img.cpp 1. 在~/Yolo-Fastest/sample/ncnn/下新增`demo_img.cpp` 2. code 如下,有需要修改的地方都有註解記得要改 ```C++ #include "YoloDet.h" int main() { // 這裡填入class 的名稱,注意 ,這裡第一個必須填unknow,剩下的才是你的class名稱 static const char* class_names[] = { "unknow","screw","nut" }; YoloDet api; // 這裡填入model 的位置 api.init("model/yolo-fastest-screw.param", "model/yolo-fastest-screw.bin"); // 這裡填入測試照片的位置 cv::Mat cvImg = cv::imread("test.jpg"); std::vector<TargetBox> boxes; api.detect(cvImg, boxes); for (int i = 0; i < boxes.size(); i++) { std::cout<<boxes[i].x1<<" "<<boxes[i].y1<<" "<<boxes[i].x2<<" "<<boxes[i].y2 <<" "<<boxes[i].score<<" "<<boxes[i].cate<<std::endl; char text[256]; sprintf(text, "%s %.1f%%", class_names[boxes[i].cate], boxes[i].score * 100); int baseLine = 0; cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine); int x = boxes[i].x1; int y = boxes[i].y1 - label_size.height - baseLine; if (y < 0) y = 0; if (x + label_size.width > cvImg.cols) x = cvImg.cols - label_size.width; cv::rectangle(cvImg, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)), cv::Scalar(255, 255, 255), -1); cv::putText(cvImg, text, cv::Point(x, y + label_size.height), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0)); cv::rectangle (cvImg, cv::Point(boxes[i].x1, boxes[i].y1), cv::Point(boxes[i].x2, boxes[i].y2), cv::Scalar(255, 255, 0), 2, 2, 0); } // 這裡填入結果照片的位置 cv::imwrite("output.png", cvImg); return 0; } ``` #### 新增demo_video.cpp 1. 在~/Yolo-Fastest/sample/ncnn/下新增 `demo_video.cpp` 2. code 如下,有需要修改的地方都有註解記得要改 ```C++ #include "YoloDet.h" #include <time.h> // 這裡填入class 的名稱,注意 ,這裡第一個必須填unknow,剩下的才是你的class名稱 static const char* class_names[] = { "unknow","screw","nut" }; int drawBoxes(cv::Mat srcImg, std::vector<TargetBox> boxes) { // printf("Detect box num: %d\n", boxes.size()); for (int i = 0; i < boxes.size(); i++) { cv::rectangle (srcImg, cv::Point(boxes[i].x1, boxes[i].y1), cv::Point(boxes[i].x2, boxes[i].y2), cv::Scalar(255, 255, 0), 2, 2, 0); std::string cate = class_names[boxes[i].cate]; std::string Ttext = cate; cv::Point Tp = cv::Point(boxes[i].x1, boxes[i].y1-20); cv::putText(srcImg, Ttext, Tp, cv::FONT_HERSHEY_TRIPLEX, 0.5, cv::Scalar(0, 255, 0), 1, CV_AA); std::string score =std::to_string(boxes[i].score); std::string Stext = "Score:" + score; cv::Point Sp = cv::Point(boxes[i].x1, boxes[i].y1-5); cv::putText(srcImg, Stext, Sp, cv::FONT_HERSHEY_TRIPLEX, 0.5, cv::Scalar(0, 0, 255), 1, CV_AA); } return 0; } int testCam() { YoloDet api; // 這裡填入model 的位置 api.init("model/yolo-fastest-screw.param", "model/yolo-fastest-screw.bin"); cv::Mat frame; std::vector<TargetBox> output; cv::VideoCapture cap(0); // 這裡填入你的攝影機畫素 cap.set(CV_CAP_PROP_FRAME_WIDTH, 1080); cap.set(CV_CAP_PROP_FRAME_HEIGHT, 720); while (true) { double start = ncnn::get_current_time(); cap >> frame; if (frame.empty()) break; api.detect(frame, output); drawBoxes(frame, output); output.clear(); cv::imshow("camera", frame); cv::waitKey(10); double end = ncnn::get_current_time(); double time = end - start; double fps = 1000 / time; printf("Speed per frame :%7.2f ms\n",time); printf("FPS :%7.2f \n",fps); } cap.release(); return 0; } int main() { testCam(); return 0; } ``` #### 編譯demo檔案 ``` g++ -o demo_img demo_img.cpp src/YoloDet.cpp -I src/include -I include/ncnn lib/libncnn.a `pkg-config --libs --cflags opencv` -fopenmp g++ -o demo_video demo_video.cpp src/YoloDet.cpp -I src/include -I include/ncnn lib/libncnn.a `pkg-config --libs --cflags opencv` -fopenmp ``` #### 運行demo 檔案 1. 執行`demo_img` ``` ./demo_img ``` ![](https://hackmd.io/_uploads/BkxwYOqtn.png) 2. 執行`demo_video` ``` ./demo_video ``` ![](https://hackmd.io/_uploads/SJNuFu9Y2.png) 3. 分析 可以看到上圖的FPS 不高,主要是因為在C++檔案中把frame 秀出來了,而且我同時開著螢幕錄影,若是只有輸出bbox 和 label 結果,使用yolo fastest 可達15-16 fps ,若是使用yolo fastest-xl 可達10-11 fps ,符合原作者提供的數據 ![](https://hackmd.io/_uploads/H1AoKdcth.png) ## 結語 至此所有的教學結束,後續或許可以試試樹梅派超頻看看會不會提升FPS,另外作者有發布yolo fastest V2 版本,backbone 改成最近很紅的shuffle-net,聽說又大幅提高了速度,但筆者一直 run不起來,就先放著,下次有時間來試試 yoloX 的表現吧!