極市導(dǎo)讀 本文中介紹的整套程序只依賴OpenCV庫(kù)就能正常運(yùn)行,,徹底擺脫了對(duì)深度學(xué)習(xí)框架的依賴。文章講述了作者在自己編寫用OpenCV的dnn模塊做YOLOv5目標(biāo)檢測(cè)的程序的過程中遇到的bug以及解決的辦法,。 >>加入極市CV技術(shù)交流群,,走在計(jì)算機(jī)視覺的最前沿 最近看到多篇講解YOLOv5在OpenVINO部署做目標(biāo)檢測(cè)文章,但是沒看到過用OpenCV的DNN模塊做YOLOv5目標(biāo)檢測(cè)的,。于是,,我就想編寫一套用OpenCV的DNN模塊做YOLOv5目標(biāo)檢測(cè)的程序。 在編寫這套程序時(shí),,遇到的bug和解決辦法,,在這篇文章里講述一下。 在YOLOv5之前的YOLOv3和YOLOv4的官方代碼都是基于darknet框架的實(shí)現(xiàn)的,,因此OpenCV的DNN模塊做目標(biāo)檢測(cè)時(shí),,讀取的是.cfg和.weight文件,那時(shí)候編寫程序很順暢,,沒有遇到bug,。 但是YOLOv5的官方代碼(https://github.com/ultralytics/yolov5)是基于Pytorch框架實(shí)現(xiàn)的,而OpenCV的DNN模塊不支持讀取Pytorch的訓(xùn)練模型文件,。如果想要把Pytorch的訓(xùn)練模型.pth文件加載到OpenCV的DNN模塊里,,需要先把Pytorch的訓(xùn)練模型.pth文件轉(zhuǎn)換到.onnx文件,然后才能載入到Opencv的DNN模塊里,。 因此,,用OpenCV的DNN模塊做YOLOv5目標(biāo)檢測(cè)的程序,包含兩個(gè)步驟: 1. 把Pytorch的訓(xùn)練模型.pth文件轉(zhuǎn)換到.onnx文件,。 2. OpenCV的DNN模塊讀取.onnx文件做前向計(jì)算,。 1. 把Pytorch的訓(xùn)練模型.pth文件轉(zhuǎn)換到.onnx文件在做這一步時(shí),我得吐槽一下官方代碼: https://github.com/ultralytics/yolov5 這套程序里的代碼混亂,,在Pytorch里,,通常是在.py文件里定義網(wǎng)絡(luò)結(jié)構(gòu)的,但是官方代碼是在.yaml文件定義網(wǎng)絡(luò)結(jié)構(gòu),,利用Pytorch動(dòng)態(tài)圖特性,,解析.yaml文件自動(dòng)生成網(wǎng)絡(luò)結(jié)構(gòu)。在.yaml文件里有depth_multiple和width_multiple,,它是控制網(wǎng)絡(luò)的深度和寬度的參數(shù),。 這么做的好處是能夠靈活的配置網(wǎng)絡(luò)結(jié)構(gòu),但是不利于理解網(wǎng)絡(luò)結(jié)構(gòu),。假如你想設(shè)斷點(diǎn)查看某一層的參數(shù)和輸出數(shù)值,,那就沒辦法了,。因此,在我編寫的轉(zhuǎn)換到.onnx文件的程序里,,網(wǎng)絡(luò)結(jié)構(gòu)是在.py文件里定義的,。 其次,在官方代碼里,,還有一個(gè)奇葩的地方,,那就是.pth文件。 起初,,我下載官方代碼到本地運(yùn)行時(shí),torch.load讀取.pth文件總是出錯(cuò),,后來把pytorch升級(jí)到1.7,,就讀取成功了。可以看到版本兼容性不好,,這是它的一個(gè)不足之處,。 設(shè)斷點(diǎn)查看讀取的.pth文件里的內(nèi)容,可以看到.pth里既存儲(chǔ)有模型參數(shù),,也存儲(chǔ)有網(wǎng)絡(luò)結(jié)構(gòu),,還儲(chǔ)存了一些超參數(shù),包括anchors,,stride等等的,。第一次見到有這種操作的,通常情況下,,.pth文件里只存儲(chǔ)了訓(xùn)練模型參數(shù)的,。 查看models\yolo.py里的Detect類,在構(gòu)造函數(shù)里,,有這么兩行代碼: 我嘗試過把這兩行代碼改成self.anchors = a 和 self.anchor_grid = a.clone().view(self.nl, 1, -1, 1, 1, 2),,程序依然能正常運(yùn)行,但是torch.save保存模型文件后,,可以看到.pth文件里沒有存儲(chǔ)anchors和anchor_grid了,,在網(wǎng)頁(yè)搜索register_buffer,解釋是:pytorch中register_buffer模型保存和加載的時(shí)候可以寫入和讀出,。 在這兩行代碼的下一行: 它的作用是做特征圖的輸出通道對(duì)齊,,通過1x1卷積把三種尺度特征圖的輸出通道都調(diào)整到 num_anchors*(num_classes+5)。 閱讀Detect類的forward函數(shù)代碼,,可以看出它的作用是根據(jù)偏移公式計(jì)算出預(yù)測(cè)框的中心坐標(biāo)和高寬,,這里需要注意的是,計(jì)算高和寬的代碼: pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i] 沒有采用exp操作,,而是直接乘上anchors[i],,這是YOLOv5與YOLOv3v4的一個(gè)最大區(qū)別(還有一個(gè)區(qū)別就是在訓(xùn)練階段的loss函數(shù)里,YOLOv5采用鄰域的正樣本anchor匹配策略,增加了正樣本,。其它的是一些小區(qū)別,,比如YOLOv5的第一個(gè)模塊采用FOCUS把輸入數(shù)據(jù)2倍下采樣切分成4份,在channel維度進(jìn)行拼接,,然后進(jìn)行卷積操作,,YOLOv5的激活函數(shù)沒有使用Mish)。 現(xiàn)在可以明白Detect類的作用是計(jì)算預(yù)測(cè)框的中心坐標(biāo)和高寬,,簡(jiǎn)單來說就是生成proposal,,作為后續(xù)NMS的輸入,進(jìn)而輸出最終的檢測(cè)框,。我覺得在Detect類里定義的1x1卷積是不恰當(dāng)?shù)?,?yīng)該把它定義在Detect類的外面,緊鄰著Detect類之前定義1x1卷積,。 在官方代碼里,,有轉(zhuǎn)換到onnx文件的程序:python models/export.py --weights yolov5s.pt --img 640 --batch 1 在pytorch1.7版本里,程序是能正常運(yùn)行生成onnx文件的,。觀察export.py里的代碼,,在執(zhí)行torch.onnx.export之前,有這么一段代碼: 注意其中的for循環(huán),,我試驗(yàn)過注釋掉它,,重新運(yùn)行就會(huì)出錯(cuò),打印出的錯(cuò)誤如下: 由此可見,,這段for循環(huán)代碼是必需的,。 2. OpenCV的DNN模塊讀取.onnx文件做前向計(jì)算在生成.onnx文件后,就可以用OpenCV的DNN模塊里的cv2.dnn.readNet讀取它,。然而,,在讀取時(shí),出現(xiàn)了如下錯(cuò)誤: 我在網(wǎng)頁(yè)搜索這個(gè)問題的解決辦法,,看到一篇技術(shù)文章(https://zhuanlan.zhihu.com/p/286298001),,文章里講述的第一條: 于是查看YOLOv5的代碼,在common.py文件的Focus類,,torch.cat的輸入里有4次切片操作,,代碼如下: 那么現(xiàn)在需要更換索引式的切片操作,觀察到注釋的Contract類,,它就是用view和permute函數(shù)完成切片操作的,,于是修改代碼如下: 其次,在models\yolo.py里的Detect類里,,也有切片操作,,代碼如下: 前面說過,,Detect類的作用是計(jì)算預(yù)測(cè)框的中心坐標(biāo)和高寬,生成proposal,,這個(gè)是屬于后處理的,,因此不需要把它寫入到onnx文件里。 總結(jié)一下,,按照上面的截圖代碼,,修改Focus類,把Detect類里面的1x1卷積定義在緊鄰著Detect類之前的外面,,然后去掉Detect類,,組成新的model,作為torch.onnx.export的輸入,, torch.onnx.export(model, inputs, output_onnx, verbose=False, opset_version=12, input_names=['images'], output_names=['out0', 'out1', 'out2']) 最后生成的onnx文件,,opencv的dnn模塊就能成功讀取了,接下來對(duì)照Detect類里的forward函數(shù),,用python或者C++編寫計(jì)算預(yù)測(cè)框的中心坐標(biāo)和高寬的功能。 周末這兩天,,我在win10+cpu機(jī)器里編寫了用OpenCV的DNN模塊做Yolov5目標(biāo)檢測(cè)的程序,,包含Python和C++兩個(gè)版本的。程序都調(diào)試通過了,,運(yùn)行結(jié)果也是正確的,。 我把這套代碼發(fā)布在了Github上,地址是: https://github.com/hpc203/yolov5-dnn-cpp-python 后處理模塊,,python版本用numpy array實(shí)現(xiàn)的,,C++版本的用vector和數(shù)組實(shí)現(xiàn)的,整套程序只依賴OpenCV庫(kù)(opencv4版本以上的)就能正常運(yùn)行,,徹底擺脫對(duì)深度學(xué)習(xí)框架pytorch,tensorflow,caffe,mxnet等等的依賴,。 用OpenVINO作目標(biāo)檢測(cè),需要把onnx文件轉(zhuǎn)換到.bin和.xml文件,,相比于用DNN模塊加載onnx文件做目標(biāo)檢測(cè)是多了一個(gè)步驟的,。因此,我就想編寫一套用OpenCV的DNN模塊做YOLOv5目標(biāo)檢測(cè)的程序,,用Opencv的DNN模塊做深度學(xué)習(xí)目標(biāo)檢測(cè),,在win10和ubuntu,在cpu和gpu上都能運(yùn)行,,可見DNN模塊的通用性更好,,很接地氣。 |
|