1. 程式人生 > 實用技巧 >YOLOv5在最新OpenVINO 2021R02版本的部署與程式碼演示詳解

YOLOv5在最新OpenVINO 2021R02版本的部署與程式碼演示詳解

YOLOv5在OpenVINO上的部署,網上有很多python版本的程式碼,但是基本都有個很內傷的問題,就是還在用pytorch的一些庫做解析,C++的程式碼有個更大的內傷就是自定義解析解釋的不是很清楚,所以本人閱讀YOLOv5的pytorch程式碼推理部分,從原始的三個輸出層解析實現了boxes, classes, nms等關鍵C++程式碼輸出,實現了純OpenVINO+OpenCV版本的YOLOv5s模型推理的程式碼演示。下面就是詳細的系統環境與各個部分解釋,以及程式碼實現與演示影象。

系統版本資訊與依賴

  • Window 10 64bit

  • Pytorch1.7+CUDA10.0

  • Python3.8.5

  • VS2015

  • OpenVINO_2021.2.185

01

YOLOv5下載與測試執行

YOLOv5是第二個非官方的YOLO物件檢測版本,也是第一個Pytorch實現的YOLO物件檢測版本。Github地址如下:

https://github.com/ultralytics/yolov5

需要克隆到本地

git clone https://github.com/ultralytics/yolov5.git

然後執行

pip install -r requirements.txt

安裝所有依賴。

最後執行一段視訊或者影象完成測試

python detect.py --source D:\images\video\SungEun.avi --weights yolov5s.pt --conf 0.25

視訊測試結果如下:

影象測試結果如下:

02

模型轉換

模型轉換主要是把原始的YOLOv5的pytorch模型檔案轉換為通用的開放模型格式ONNX與OpenVIN特有的檔案格式IR(*.xml與*.bin)。

OpenVINO從2020R02以後版本開始支援直接讀取ONNX格式檔案,所以我們既可以通過指令碼直接匯出onnx格式檔案,直接給OpenVINO呼叫,也可以對得到ONNX檔案通過OpenVINO的模型轉換指令碼做進一步轉換生成IR中間格式(*.bin檔案與*.xml檔案)。

匯出ONNX格式檔案的指令碼

Pytorch的YOLOv5專案本身已經提供了轉換指令碼,命令列執行方式如下:

# export at 640x640 with batch size 1
python models/export.py --weights yolov5s.pt --img 640 --batch 1

然後生成的yolov5s.onnx檔案就在同一目錄下面。

ONNX轉為為IR中間格式

把ONNX轉換為IR中間格式,執行結果如下

03

OpenVINO SDK+YOLOv5s程式碼演示

上面我們已經成功轉換為YOLOv5s模型IR,現在就可以基於最新的SDK來說完成呼叫解析與呼叫。

第一步:

初始Core物件,讀取模型(載入ONNX格式或者IR格式均可以,親測有效)

//建立IE外掛,查詢支援硬體裝置
Coreie;
vector<string>availableDevices=ie.GetAvailableDevices();
for(inti=0;i<availableDevices.size();i++){
printf("supporteddevicename:%s\n",availableDevices[i].c_str());
}

//載入檢測模型
autonetwork=ie.ReadNetwork("D:/python/yolov5/yolov5s.xml","D:/python/yolov5/yolov5s.bin");
//autonetwork=ie.ReadNetwork("D:/python/yolov5/yolov5s.onnx");

第二步:

設定輸入與輸出格式,YOLOv5s輸入的影象被歸一化到0~1之間,而且是RGB通道順序,輸入與輸出格式設定資料為浮點數,這部分的程式碼如下:

//設定輸入格式
for(auto&item:input_info){
autoinput_data=item.second;
input_data->setPrecision(Precision::FP32);
input_data->setLayout(Layout::NCHW);
input_data->getPreProcess().setResizeAlgorithm(RESIZE_BILINEAR);
input_data->getPreProcess().setColorFormat(ColorFormat::RGB);
}

//設定輸出格式
for(auto&item:output_info){
autooutput_data=item.second;
output_data->setPrecision(Precision::FP32);
}
autoexecutable_network=ie.LoadNetwork(network,"CPU");

第三步:

設定輸入影象資料並實現推理預測

int64start=getTickCount();
/**Iteratingoverallinputblobs**/
for(auto&item:input_info){
autoinput_name=item.first;

/**Gettinginputblob**/
autoinput=infer_request.GetBlob(input_name);
size_tnum_channels=input->getTensorDesc().getDims()[1];
size_th=input->getTensorDesc().getDims()[2];
size_tw=input->getTensorDesc().getDims()[3];
size_timage_size=h*w;
Matblob_image;
resize(src,blob_image,Size(w,h));
cvtColor(blob_image,blob_image,COLOR_BGR2RGB);

//NCHW
float*data=static_cast<float*>(input->buffer());
for(size_trow=0;row<h;row++){
for(size_tcol=0;col<w;col++){
for(size_tch=0;ch<num_channels;ch++){
data[image_size*ch+row*w+col]=float(blob_image.at<Vec3b>(row,col)[ch])/255.0;
}
}
}
}

//執行預測
infer_request.Infer();

上面的程式碼跟SSD物件檢測的OpenVINO呼叫基本上沒有太大區別。主要的區別是在對推理完成結果的解析部分。

第四步:

解析輸出結果,實現顯示輸出。要完成這個部分,首先需要看一下YOLOv5專案中的yolo.py中對推理部分的組裝。首先輸出層,從YOLOv3開始到YOLOv5,輸出層都是3層,分別對應的降取樣的倍數是32、16、8。

以輸入640x640大小的影象為例,得到三個輸出層大小應該分別是20、40、80。每個層上對應三個尺度的anchor,表示如下:

模型的預測是在20x20、40x40、80x80每個輸出層的每個特徵點上預測三個框,每個框預測分類!每個框的維度大小為 cx,cy,w,h,conf + number of class, 圖示如下:

以YOLOv5專案的預訓練模型是基於COCO的80個物件類別為例,在檢測階段最終三個輸出層是:

1x3x20x20x851x3x40x40x851x3x80x80x85

我電腦上實際載入模型之後的三個輸出層實現執行結果:

然後迴圈每個輸出層,解析每個特徵點對應的3個框與相關資料。由於在匯出的時候ONNX格式檔案時模型的推理得到的三個輸出層原始結果,所以還需要對每個資料先完成sigmoid歸一化,然後再計算相關值,這部分的程式碼實現我參考了專案中的yolo.py中的Detection部分,得到初始每個物件的檢測框之後,採用OpenVINO中自帶非最大抑制函式,完成非最大抑制,就得到了最終的預測框,然後繪製顯示。所以最終的解析輸出層部分的程式碼如下:

for(inti=0;i<side_square;++i){
for(intc=0;c<out_c;c++){
introw=i/side_h;
intcol=i%side_h;
intobject_index=c*side_data_square+row*side_data_w+col*side_data;

//閾值過濾
floatconf=sigmoid_function(output_blob[object_index+4]);
if(conf<0.25){
continue;
}

//解析cx,cy,width,height
floatx=(sigmoid_function(output_blob[object_index])*2-0.5+col)*stride;
floaty=(sigmoid_function(output_blob[object_index+1])*2-0.5+row)*stride;
floatw=pow(sigmoid_function(output_blob[object_index+2])*2,2)*anchors[anchor_index+c*2];
floath=pow(sigmoid_function(output_blob[object_index+3])*2,2)*anchors[anchor_index+c*2+1];
floatmax_prob=-1;
intclass_index=-1;

//解析類別
for(intd=5;d<85;d++){
floatprob=sigmoid_function(output_blob[object_index+d]);
if(prob>max_prob){
max_prob=prob;
class_index=d-5;
}
}

//轉換為top-left,bottom-right座標
intx1=saturate_cast<int>((x-w/2)*scale_x);//topleftx
inty1=saturate_cast<int>((y-h/2)*scale_y);//toplefty
intx2=saturate_cast<int>((x+w/2)*scale_x);//bottomrightx
inty2=saturate_cast<int>((y+h/2)*scale_y);//bottomrighty

//解析輸出
classIds.push_back(class_index);
confidences.push_back((float)conf);
boxes.push_back(Rect(x1,y1,x2-x1,y2-y1));
//rectangle(src,Rect(x1,y1,x2-x1,y2-y1),Scalar(255,0,255),2,8,0);
}
}

非最大抑制的程式碼如下:

vector<int>indices;
NMSBoxes(boxes,confidences,0.25,0.5,indices);
for(size_ti=0;i<indices.size();++i)
{
intidx=indices[i];
Rectbox=boxes[idx];
rectangle(src,box,Scalar(140,199,0),4,8,0);
}
floatfps=getTickFrequency()/(getTickCount()-start);
floattime=(getTickCount()-start)/getTickFrequency();

官方的兩張測試影象,測試結果如下:

信OpenVINO得加速,OpenVINO+YOLOv5 真的可以在一起!(CORE i78th)。

掃碼加入我的知識星球,2021一起努力!

金舟不能凌陽侯之波

玉馬不任騁千里之跡

推薦閱讀

OpenCV4系統化學習路線圖-視訊版本!

Tensorflow + OpenCV4 安全帽檢測模型訓練與推理

OpenCV Python + Tesseract-OCR輕鬆實現中文識別

乾貨 | 那些鬼斧神工的池化操作,看完我炸裂!

經驗 | OpenCV影象旋轉的原理與技巧

彙總 | OpenCV DNN模組中支援的分類網路

彙總 | OpenCV DNN支援的物件檢測模型

彙總 | OpenCV4中的非典型深度學習模型