【飛槳開發者說】侯繼旭,海南師範大學本三自動化專業在讀,人工智慧開發愛好者,曾獲2019中國高校計算機大賽-人工智慧創意賽海南省一等獎、2019年度海南省高等學校科學研究“人工智慧”優秀成果獎
本專案以ssd_mobilenet_v1_voc演算法為例,詳細介紹了從準備資料集、到模型訓練,並將模型部署到樹莓派的全過程。缺訓練資料的痛苦,相信做過模型訓練的小夥伴們都深有感觸。為了便於大家實戰操作,訓練資料統統提供!無需任何準備工作就可以體驗全流程哈。
專案用到的開源工具包括百度的深度學習平臺飛槳以及模型開發套件PaddleDetection、端側部署工具Paddle Lite、百度一站式AI開發平臺AI Studio和樹莓派4B。可透過以下方式進行線上或者本地體驗:
線上體驗:
專案已在AI Studio上公開,包括資料集在內已經打包上傳,程式碼可線上跑通,歡迎Fork!連結:https://aistudio.baidu.com/aistudio/projectdetail/331209
本地體驗:
專案涉及的全部資料也都打包放在百度網盤(PaddleDetection、Paddle Lite Demo、Paddle Lite、opt),可下載到本地體驗。
連結:https://pan.baidu.com/s/1IKT-ByVN9BaVxfqQC1VaMw 提取碼:mdd1
資料集準備
本專案用的資料集格式是VOC格式,標註工具為labelimg,影像資料是手動拍攝獲取。
資料標註:
- 點選Open Dir,開啟資料夾,載入圖片
- 點選Create RectBox,即可在影像上畫框標註
- 輸入標籤,點選OK
- 點選Save儲存,儲存下來的是XML檔案
XML檔案內容如下
整理成VOC格式的資料集:
建立三個資料夾:Annotations、ImageSets、JPEGImages
將標註生成的XML檔案存入Annotations,圖片存入JPEGImages,訓練集、測試集、驗證集的劃分情況存入ImageSets。
在ImageSets下建立一個Main資料夾,並且在Mian資料夾下建立labellist.txt,裡面存入標註的標籤。
此labellist.txt檔案複製一份與Annotations、ImageSets、JPEGImages同級位置放置。
其內容如下:
執行該程式碼將會生成trainval.txt、train.txt、val.txt、test.txt,將我們標註的600張影像按照訓練集、驗證集、測試集的形式做一個劃分。
import os import random trainval_percent = 0.95 #訓練集驗證集總佔比 train_percent = 0.9 #訓練集在trainval_percent裡的train佔比 xmlfilepath = 'F:/Cola/Annotations' txtsavepath = 'F:/Cola/ImageSets/Main' total_xml = os.listdir(xmlfilepath) num=len(total_xml) list=range(num) tv=int(num*trainval_percent) tr=int(tv*train_percent) trainval= random.sample(list,tv) train=random.sample(trainval,tr) ftrainval = open('F:/Cola/ImageSets/Main/trainval.txt', 'w') ftest = open('F:/Cola/ImageSets/Main/test.txt', 'w') ftrain = open('F:/Cola/ImageSets/Main/train.txt', 'w') fval = open('F:/Cola/ImageSets/Main/val.txt', 'w') for i in list: name=total_xml[i][:-4]+'\n' if i in trainval: ftrainval.write(name) if i in train: ftrain.write(name) else: fval.write(name) else: ftest.write(name) ftrainval.close() ftrain.close() fval.close() ftest .close()
以下程式碼可根據在Main資料夾中劃分好的資料集進行位置索引,生成含有影像及對應的XML檔案的地址資訊的檔案。
import os import re import random devkit_dir = './' output_dir = './' def get_dir(devkit_dir, type): return os.path.join(devkit_dir, type) def walk_dir(devkit_dir): filelist_dir = get_dir(devkit_dir, 'ImageSets/Main') annotation_dir = get_dir(devkit_dir, 'Annotations') img_dir = get_dir(devkit_dir, 'JPEGImages') trainval_list = [] train_list = [] val_list = [] test_list = [] added = set() for _, _, files in os.walk(filelist_dir): for fname in files: print(fname) img_ann_list = [] if re.match('trainval.txt', fname): img_ann_list = trainval_list elif re.match('train.txt', fname): img_ann_list = train_list elif re.match('val.txt', fname): img_ann_list = val_list elif re.match('test.txt', fname): img_ann_list = test_list else: continue fpath = os.path.join(filelist_dir, fname) for line in open(fpath): name_prefix = line.strip().split()[0] print(name_prefix) added.add(name_prefix) #ann_path = os.path.join(annotation_dir, name_prefix + '.xml') ann_path = annotation_dir + '/' + name_prefix + '.xml' print(ann_path) #img_path = os.path.join(img_dir, name_prefix + '.jpg') img_path = img_dir + '/' + name_prefix + '.jpg' assert os.path.isfile(ann_path), 'file %s not found.' % ann_path assert os.path.isfile(img_path), 'file %s not found.' % img_path img_ann_list.append((img_path, ann_path)) print(img_ann_list) return trainval_list, train_list, val_list, test_list def prepare_filelist(devkit_dir, output_dir): trainval_list = [] train_list = [] val_list = [] test_list = [] trainval, train, val, test = walk_dir(devkit_dir) trainval_list.extend(trainval) train_list.extend(train) val_list.extend(val) test_list.extend(test) #print(trainval) with open(os.path.join(output_dir, 'trainval.txt'), 'w') as ftrainval: for item in trainval_list: ftrainval.write(item[0] + ' ' + item[1] + '\n') with open(os.path.join(output_dir, 'train.txt'), 'w') as ftrain: for item in train_list: ftrain.write(item[0] + ' ' + item[1] + '\n') with open(os.path.join(output_dir, 'val.txt'), 'w') as fval: for item in val_list: fval.write(item[0] + ' ' + item[1] + '\n') with open(os.path.join(output_dir, 'test.txt'), 'w') as ftest: for item in test_list: ftest.write(item[0] + ' ' + item[1] + '\n') if __name__ == '__main__': prepare_filelist(devkit_dir, output_dir)
最終建立完成的VOC資料集如下:
將整個檔案複製至 ./PaddleDetection/dataset/voc 下
以上全部完成後,還需要修改兩個地方,ssdmobilenetv1_voc原始碼中是以20類目標為準設計的,本專案的目標僅為兩類
1. 找到 ./PaddleDetection/configs/ssd/ssdmobilenetv1voc.yml檔案,修改第12行的numclasses,3代表2個標籤加一個背景
num_classes: 3
2. 找到 ./PaddleDetection/ppdet/data/source/voc.py檔案,修改167行的pascalvoclabel()函式,按照前面設定的labellist.txt檔案裡的標籤順序依次修改,並將多餘的內容刪掉
def pascalvoc_label(with_background=True): labels_map = { 'PepsiCola': 1, 'CocaCola': 2 } if not with_background: labels_map = {k: v - 1 for k, v in labels_map.items()} return labels_map
至此,整個資料集製作及配置完成。
建立專案
進入AI Studio建立專案
確認建立專案前,需要將資料集新增進去,點選建立資料集,將第一步做好的“PaddleDetection”整個資料夾壓縮打包上傳。
至此,建立專案完成。
環境配置
#安裝Python依賴庫 !pip install -r requirements.txt #測試專案環境 !export PYTHONPATH=`pwd`:$PYTHONPATH !python ppdet/modeling/tests/test_architectures.py
出現 No module named 'ppdet' 是環境配置的問題,有兩種解決辦法:
1. 設定環境變數
%env PYTHONPATH=/home/aistudio/PaddleDetection
2. 找到報錯的檔案新增以下程式碼
import sys DIR = '/home/aistudio/PaddleDetection' sys.path.append(DIR)
測試環境透過後,就可以開始訓練了
開始訓練
訓練命令如下:
%cd home/aistudio/PaddleDetection/ !python -u tools/train.py -c configs/ssd/ssd_mobilenet_v1_voc.yml --use_tb=True --eval
訓練完成後輸出的模型儲存在 ./PaddleDetection/output/ssdmobilenetv1voc 資料夾下,本次訓練總輪數預設為28000輪,每隔2000輪儲存一次模型,以輪次命名的均為階段性模型,modelfinal為訓練結束時儲存的模型,best_model是每次評估後的最佳mAP模型
#測試,檢視模型效果 %cd home/aistudio/PaddleDetection/ !python tools/infer.py -c configs/ssd/ssd_mobilenet_v1_voc.yml --infer_img=/home/aistudio/2001.jpg#infer_img輸入需要預測圖片的路徑,看一下效果
模型轉換
接下來,需要將原生模型轉化為預測模型
!python -u tools/export_model.py -c configs/ssd/ssd_mobilenet_v1_voc.yml --output_dir=./inference_model_final
生成的預測模型儲存在 ./PaddleDetection/inferencemodelfinal/ssdmobilenetv1_voc 資料夾下,會生成兩個檔案,模型檔名和引數檔名分別為model和params。
部署到樹莓派4B上需要使用Paddle Lite,而飛槳的原生模型需要經過opt工具轉化為Paddle Lite可以支援的naive_buffer格式。
%cd /home/aistudio/ #複製opt檔案到相應目錄下 !cp opt /home/aistudio/PaddleDetection/inference_model_final/ssd_mobilenet_v1_voc #進入預測模型資料夾 %cd /home/aistudio/PaddleDetection/inference_model_final/ssd_mobilenet_v1_voc #下載opt檔案 #!wget https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.3.0/opt #給opt加上可執行許可權 !chmod +x opt #使用opt進行模型轉化,將__model__和__params__轉化為model.nb !./opt --model_file=__model__ --param_file=__params__ --optimize_out_type=naive_buffer --optimize_out=./model !ls
這個opt自己下載實在是太慢了,因此我在網盤裡已經準備好了opt檔案,可以直接上傳至AI Studio操作,最終結果如下圖所示:
到目前為止,在AI Studio上的所有內容已經完成,生成了這個model.nb檔案,就可將其部署在樹莓派4B上使用。
預測庫編譯
Paddle Lite目前支援三種編譯的環境:
- Docker 容器環境
- Linux(推薦 Ubuntu 16.04)環境
- 樹莓派(推薦在樹莓派上直接編譯)
本次專案僅涉及到樹莓派的ARMLinux環境編譯,其他編譯環境請參考PaddleLite官方文件
編譯環境要求:gcc、g++、git、make、wget、python
cmake(建議使用3.10或以上版本)
官方安裝流程如下:
# 1. Install basic software apt update apt-get install -y --no-install-recomends \ gcc g++ make wget python unzip # 2. install cmake 3.10 or above wget https://www.cmake.org/files/v3.10/cmake-3.10.3.tar.gz tar -zxvf cmake-3.10.3.tar.gz cd cmake-3.10.3 ./configure make sudo make install
此環境樹莓派應該是會有的,可以自行檢查,沒有的包安裝上即可。至此完成所有的編譯環境配置。
將 Paddle Lite 和 Paddle Lite Demo 移動至樹莓派中,放在自己方便的目錄下即可,在這裡我的 Paddle Lite 放在了 /home/pi/ 下,將 Paddle Lite Demo 放在了 /home/pi/Desktop/ 下,並且將 /home/pi/Paddle/Paddle-Lite/lite/tools/build.sh 加上執行許可權
所有工作完成後,即可開始編譯Paddle Lite
cd /home/pi/Paddle/Paddle-Lite sudo ./lite/tools/build.sh \ --build_extra=OFF \ --arm_os=armlinux \ --arm_abi=armv7hf \ --arm_lang=gcc \ tiny_publish
雖然樹莓派4B已經是 ARMv8 的CPU架構,但官方系統為32位,還是需要使用ARMv7架構的編譯方式
編譯結束,結果如下:
檔案結構搭建
整體檔案結構如下:
1. 開啟 /home/pi/Desktop/Paddle-Lite-Demo/PaddleLite-armlinux-demo/objectdetectiondemo 資料夾,在此目錄下新建 Paddle Lite、code 資料夾。
2. Paddle Lite資料夾下新建 include、libs 資料夾。
3. libs資料夾下新建 armv7hf 資料夾。
4. 將 images、labels、CMakeLists.txt、run.sh、objectdetectiondemo.cc 檔案移入 code 資料夾下。
對於 Paddle Lite 的編譯結果,我們需要使用的東西在 /home/pi/Paddle/Paddle-Lite/build.lite.armlinux.armv7hf.gcc/inferencelitelib.armlinux.armv7hf/cxx 資料夾下
將 include 和 lib 中的標頭檔案和庫檔案提取出來,分別放入 include 和 armv7hf 資料夾中,至此已做好檔案結構的搭建.
模型部署
接下來就是最後一步了,將模型放進檔案中,稍作修改就大功告成了!
1. 進入 code 資料夾。
2. 修改 labels 資料夾下的 pascalvoclabellist ,內容必須與訓練時的 labellist.txt 檔案內容一致 (注意 pascalvoclabel_list 是純文字文件,不是 .txt 文字文件,弄錯了預測出來的框選標籤會打 unknow 的!)。
3. 將在PaddlePaddle學習之使用PaddleDetection在樹莓派4B進行模型部署(二)----- 深度學習模型訓練得到的 model.nb 放進 models 資料夾。
4. 開啟 run.sh 檔案,註釋掉第四行的 TARGETARCHABI=armv8 ,開啟第五行的,取消第5行 TARGETARCHABI=armv7hf 的註釋。
5. 修改第六行的 PADDLELITEDIR 索引到檔案中Paddle Lite目錄。
6. 修改第十九行的model檔案的模型索引目錄和預測圖片的索引目錄。
#!/bin/bash # configure #TARGET_ARCH_ABI=armv8 # for RK3399, set to default arch abi TARGET_ARCH_ABI=armv7hf # for Raspberry Pi 3B PADDLE_LITE_DIR=/home/pi/Desktop/Paddle-Lite-Demo/PaddleLite-armlinux-demo/object_detection_demo/Paddle-Lite if [ "x$1" != "x" ]; then TARGET_ARCH_ABI=$1 fi # build rm -rf build mkdir build cd build cmake -DPADDLE_LITE_DIR=${PADDLE_LITE_DIR} -DTARGET_ARCH_ABI=${TARGET_ARCH_ABI} .. make #run LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PADDLE_LITE_DIR}/libs/${TARGET_ARCH_ABI} ./object_detection_demo ../models/model.nb ../labels/pascalvoc_label_list ../images/2001.jpg ./result.jpg
修改完run.sh檔案後,就算是完成了所有的配置內容,可以開始放心的 RUN 了!!
/home/pi/Desktop/Paddle-Lite-Demo/PaddleLite-armlinux-demo/object_detection_demo/code sudo ./run.sh
最後的輸出結果如下:
圖片的預測結果就是這樣了,雖然一個類別只有300張圖,但是總的來說結果還算不錯!
關於影片流的實時監測,在原始碼的主函式中可以看到
if (argc > 3) { WARMUP_COUNT = 1; REPEAT_COUNT = 5; std::string input_image_path = argv[3]; std::string output_image_path = argv[4]; cv::Mat input_image = cv::imread(input_image_path); cv::Mat output_image = process(input_image, word_labels, predictor); cv::imwrite(output_image_path, output_image); cv::imshow("Object Detection Demo", output_image); cv::waitKey(0); } else { cv::VideoCapture cap(-1); cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); if (!cap.isOpened()) { return -1; } while (1) { cv::Mat input_image; cap >> input_image; cv::Mat output_image = process(input_image, word_labels, predictor); cv::imshow("Object Detection Demo", output_image); if (cv::waitKey(1) == char('q')) { break; } } cap.release(); cv::destroyAllWindows(); }
當我們在 `run.sh` 檔案中設定小於三個引數時,即可使用影片流實時監測。在這裡註釋掉圖片路徑和輸出路徑即可。
#run LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PADDLE_LITE_DIR}/libs/${TARGET_ARCH_ABI} ./object_detection_demo ../models/ssd_mobilenet_v1_pascalvoc_for_cpu/best.nb ../labels/pascalvoc_label_list #../images/2.jpg ./result.jpg
注:如果有用 Opencv-4.1.0 版本的,可能在編譯 objectdetectiondemo.cc 時在 267、268 行會報錯。
原始碼如下:
cap.set(CV_CAP_PROP_FRAME_WIDTH, 640); cap.set(CV_CAP_PROP_FRAME_HEIGHT, 480);
由於新版本的API發生了變化。需要修改為如下程式碼:
cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
至此,整個專案就完成了。
參考資料
- Paddle Lite官方文件https://paddle-lite.readthedocs.io/zh/latest/index.html
- PaddleDetection官方文件https://github.com/PaddlePaddle/PaddleDetection
- 系列文章:如何利用PaddleDetection做一個完整的專案https://blog.csdn.net/yzl819819/article/details/104336990?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task