<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C++實現(xiàn)yolov5的OpenVINO部署

          共 5258字,需瀏覽 11分鐘

           ·

          2020-12-19 13:30

          [GiantPandaCV導語] 本文介紹了一種使用c++實現(xiàn)的,使用OpenVINO部署yolov5的方法。此方法在2020年9月結(jié)束的極市開發(fā)者榜單中取得后廚老鼠識別賽題第四名。2020年12月,注意到y(tǒng)olov5有了許多變化,對部署流程重新進行了測試,并進行了整理。希望能給需要的朋友一些參考,節(jié)省一些踩坑的時間

          模型訓練

          1. 首先獲取yolov5工程

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

          本文編輯的時間是2020年12月3日,官方最新的releases是v3.1,在v3.0的版本中,官網(wǎng)有如下的聲明

          • August 13, 2020**: v3.0 release(https://github.com/ultralytics/yolov5/releases/tag/v3.0): nn.Hardswish() activations, data autodownload, native AMP.

          yolov5訓練獲得的原始的模型以.pt文件方式存儲,要轉(zhuǎn)換為OpenVINO的.xml和.bin的模型存儲方式,需要經(jīng)歷兩次轉(zhuǎn)換.

          兩次轉(zhuǎn)換所用到的工具無法同時支持nn.Hardswish()函數(shù)的轉(zhuǎn)換,v3.0版本時需要切換到v2.0版本替換掉nn.Hardswish()函數(shù)才能夠完成兩次模型轉(zhuǎn)換,當時要完成模型轉(zhuǎn)換非常的麻煩.

          在v3.1版本的yolov5中用于進行pt模型轉(zhuǎn)onnx模型的程序?qū)n.Hardswish()進行了兼容,模型轉(zhuǎn)換過程大為化簡.

          2. 訓練準備

          yolov5官方的指南: https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data

          描述信息準備

          在yolov5的文件夾下/yolov5/models/目錄下可以找到以下文件

          yolov5s.yaml

          yolov5m.yaml

          yolov5l.yaml

          這三個文件分別對應s(小尺寸模型),m(中尺寸模型)和l(大尺寸模型)的結(jié)構(gòu)描述信息

          其中為了實現(xiàn)自己的訓練常常需要更改以下兩個參數(shù)

          • nc

            需要識別的類別數(shù)量,yolov5原始的默認類別數(shù)量為80

          • anchors

            通過kmeans等算法根據(jù)自己的數(shù)據(jù)集得出合適的錨框. 這里需要注意:yolov5內(nèi)部實現(xiàn)了錨框的自動計算訓練過程默認使用自適應錨框計算.

            經(jīng)過實際測試,自己通過kmeans算法得到的錨框在特定數(shù)據(jù)集上能取得更好的性能

            在3.執(zhí)行訓練中將提到禁止自動錨框計算的方法.

          數(shù)據(jù)準備

          參考官方指南的

          • Create Labels
          • Organize Directories

          部分的數(shù)據(jù)要求

          注意標注格式是class x_center y_center width height,其中x_center y_center width height均是根據(jù)圖像尺寸歸一化的0到1之間的數(shù)值.

          3. 執(zhí)行訓練

          python?~/src_repo/yolov5/train.py?--batch?16?--epochs?10?--data?~/src_repo/rat.yaml?--cfg?~/src_repo/yolov5/models/yolov5s.yaml?--weights?""

          其中

          • --data 參數(shù)后面需要填充的是訓練數(shù)據(jù)的說明文件.其中需要說明訓練集,測試集,種類數(shù)目和種類名稱等信息,具體格式可以參考yolov5/data/coco.yaml.
          • --cfg 為在訓練準備階段完成的模型結(jié)構(gòu)描述文件.
          • --weights 后面跟預訓練模型的路徑,如果是""則重新訓練一個模型.推薦使用預訓練模型繼續(xù)訓練,不使用該參數(shù)則默認使用預訓練模型.
          • --noautoanchor 該參數(shù)可選,使用該參數(shù)則禁止自適應anchor計算,使用--cfg文件中提供的原始錨框.

          模型轉(zhuǎn)換

          經(jīng)過訓練,模型的原始存儲格式為.pt格式,為了實現(xiàn)OpenVINO部署,需要首先轉(zhuǎn)換為.onnx的存儲格式,之后再轉(zhuǎn)化為OpenVINO需要的.xml和.bin的存儲格式.

          1. pt格式轉(zhuǎn)onnx格式

          這一步的轉(zhuǎn)換主要由yolov5/models/export.py腳本實現(xiàn).

          可以參考yolov5提供的簡單教程:https://github.com/ultralytics/yolov5/issues/251

          使用該教程中的方法可以獲取onnx模型,但直接按照官方方式獲取的onnx模型其中存在OpenVINO模型轉(zhuǎn)換中不支持的運算,因此,使用該腳本之前需要進行一些更改:

          • opset_version

          在/yolov5/models/export.py中

          torch.onnx.export(model,?img,?f,?verbose=False,?opset_version=12,?input_names=['images'],
          ??????????????????????????output_names=['classes',?'boxes']?if?y?is?None?else?['output'])

          opset_version=12,將導致后面的OpenVINO模型裝換時遇到未支持的運算 因此設(shè)置為opset_version=10.

          • Detect layer export
          model.model[-1].export?=?True??

          設(shè)置為True則Detect層(包含nms,錨框計算等)不會輸出到模型中.

          設(shè)置為False包含Detect層的模型無法通過onnx到OpenVINO格式模型的轉(zhuǎn)換.

          需要執(zhí)行如下指令:

          python?./models/export.py?--weight?.pt文件路徑?--img?640?--batch?1

          需要注意的是在填入的.pt文件路徑不存在時,該程序會自動下載官方預訓練的模型作為轉(zhuǎn)換的原始模型,轉(zhuǎn)換完成則獲得onnx格式的模型.

          轉(zhuǎn)換完成后可以使用Netron:https://github.com/lutzroeder/netron.git 進行可視化.對于陌生的模型,該可視化工具對模型結(jié)構(gòu)的認識有很大的幫助.

          net.jpg

          2. onnx格式轉(zhuǎn)換OpenVINO的xml和bin格式

          OpenVINO是一個功能豐富的跨平臺邊緣加速工具箱,本文用到了其中的模型優(yōu)化工具和推理引擎兩部分內(nèi)容.

          OpenVINO的安裝配置可以參考https://docs.openvinotoolkit.org/2019_R2/_docs_install_guides_installing_openvino_linux.html ,本文的所有實現(xiàn)基于2020.4版本,為確保可用,建議下載2020.4版本的OpenVINO.

          安裝完成后在~/.bashrc文件中添加如下內(nèi)容,用于在終端啟動時配置環(huán)境變量.

          source?/opt/intel/openvino/bin/setupvars.sh
          source?/opt/intel/openvino/opencv/setupvars.sh

          安裝完成后運行如下腳本實現(xiàn)onnx模型到xml bin模型的轉(zhuǎn)換.

          python?/opt/intel/openvino/deployment_tools/model_optimizer/mo_onnx.py?--input_model?.onnx文件路徑??--output_dir?期望模型輸出的路徑

          運行成功之后會獲得.xml和.bin文件,xml和bin是OpenVINO中的模型存儲方式,后續(xù)將基于bin和xml文件進行部署.該模型轉(zhuǎn)換工具還有定點化等模型優(yōu)化功能,有興趣可以自己試試.

          使用OpenVINO進行推理部署

          OpenVINO除了模型優(yōu)化工具外,還提供了一套運行時推理引擎.

          想使用OpenVINO的模型進行推理部署,有兩種方式,第一種方式是使用OpenVINO原生的sdk,另外一種方式是使用支持OpenVINO的opencv(比如OpenVINO自帶的opencv)進行部署,本文對原生sdk的部署方式進行介紹.

          OpenVINO提供了相對豐富的例程,本文中實現(xiàn)的yolov5的部署參考了/opt/intel/openvino/deployment_tools/inference_engine/demos/object_detection_demo_yolov3_async文件夾中yolov3的實現(xiàn)方式.

          1. 推理引擎的初始化

          首先需要進行推理引擎的初始化,此部分代碼封裝在detector.cpp的init函數(shù).

          主要流程如下:

          Core?ie;
          //讀入xml文件,該函數(shù)會在xml文件的目錄下自動讀取相應的bin文件,無需手動指定
          auto?cnnNetwork?=?ie.ReadNetwork(_xml_path);?
          //從模型中獲取輸入數(shù)據(jù)的格式信息
          InputsDataMap?inputInfo(cnnNetwork.getInputsInfo());
          InputInfo::Ptr&?input?=?inputInfo.begin()->second;
          _input_name?=?inputInfo.begin()->first;
          input->setPrecision(Precision::FP32);
          input->getInputData()->setLayout(Layout::NCHW);
          ICNNNetwork::InputShapes?inputShapes?=?cnnNetwork.getInputShapes();
          SizeVector&?inSizeVector?=?inputShapes.begin()->second;
          cnnNetwork.reshape(inputShapes);
          //從模型中獲取推斷結(jié)果的格式
          _outputinfo?=?OutputsDataMap(cnnNetwork.getOutputsInfo());
          for?(auto?&output?:?_outputinfo)?{
          ????output.second->setPrecision(Precision::FP32);
          }
          //獲取可執(zhí)行網(wǎng)絡(luò),這里的CPU指的是推斷運行的器件,可選的還有"GPU",這里的GPU指的是intel芯片內(nèi)部的核顯
          //配置好核顯所需的GPU運行環(huán)境,使用GPU模式進行的推理速度上有很大提升,這里先拿CPU部署后面會提到GPU環(huán)境的配置方式
          _network?=??ie.LoadNetwork(cnnNetwork,?"CPU");

          2. 數(shù)據(jù)準備

          為了適配網(wǎng)絡(luò)的輸入數(shù)據(jù)格式要求,需要對原始的opencv讀取的Mat數(shù)據(jù)進行預處理.

          • resize

          最簡單的方式是將輸入圖像直接resize到640*640尺寸,此種方式會造成部分物體失真變形,識別準確率會受到部分影響,簡單起見,在demo代碼里使用了該方式.

          在競賽代碼中,為了追求正確率,圖像縮放的時候需要按圖像原始比例將圖像的長或?qū)捒s放到640.假設(shè)長被放大到640,寬按照長的變換比例無法達到640,則在圖像的兩邊填充黑邊確保輸入圖像總尺寸為640*640.競賽代碼中使用了該種縮放方式,需要注意的是如果使用該種縮放方式,在獲取結(jié)果時需要將結(jié)果轉(zhuǎn)換為在原始圖像中的坐標.

          • 顏色通道轉(zhuǎn)換

          鑒于opencv和pytorch的顏色通道差異,opencv是BGR通道,pytorch是RGB,在輸入網(wǎng)絡(luò)之前,需要進行通道轉(zhuǎn)換.

          • 推斷請求和blob填充
          InferRequest::Ptr?infer_request?=?_network.CreateInferRequestPtr();
          Blob::Ptr?frameBlob?=?infer_request->GetBlob(_input_name);
          InferenceEngine::LockedMemory<void>?blobMapped?=?InferenceEngine::as(frameBlob)->wmap();
          float*?blob_data?=?blobMapped.as<float*>();
          //nchw
          for(size_t?row?=0;row<640;row++){
          ????for(size_t?col=0;col<640;col++){
          ????????for(size_t?ch?=0;ch<3;ch++){
          ????????????//將圖像轉(zhuǎn)換為浮點型填入模型
          ????????????blob_data[img_size*ch?+?row*640?+?col]?=?float(inframe.at(row,col)[ch])/255.0f;
          ????????}
          ????}
          }

          3. 推斷執(zhí)行與解析

          • 推斷執(zhí)行
          infer_request->Infer();

          • 獲取推斷結(jié)果

          從Netron的可視化結(jié)果可知

          output.png

          網(wǎng)絡(luò)只包含到輸出三個檢測頭的部分,三個檢測頭分別對應80,40,和20的柵格尺寸,因此需要對三種尺寸的檢測頭輸出結(jié)果依次解析,具體的解析過程在parse_yolov5函數(shù)中進行了實現(xiàn):

          //獲取各層結(jié)果
          vector?origin_rect;?????????????????????//保存原始的框信息
          vector<float>?origin_rect_cof;????????????//保存框?qū)闹眯哦刃畔?/span>
          int?s[3]?=?{80,40,20};
          int?i=0;
          for?(auto?&output?:?_outputinfo)?{
          ????auto?output_name?=?output.first;
          ????Blob::Ptr?blob?=?infer_request->GetBlob(output_name);
          ????parse_yolov5(blob,s[i],_cof_threshold,origin_rect,origin_rect_cof);
          ????++i;
          }

          • 對檢測頭的內(nèi)容進行解析

          這部分主要是使用c++將yolov5代碼中的detect層內(nèi)容重新實現(xiàn)一下,主要代碼實現(xiàn)如下:

          //注意此處的閾值是框和物體prob乘積的閾值
          bool?Detector::parse_yolov5(const?Blob::Ptr?&blob,int?net_grid,float?cof_threshold,
          ????vector&?o_rect,vector<float>&?o_rect_cof)
          {
          ????vector<int>?anchors?=?get_anchors(net_grid);
          ????LockedMemory<const?void>?blobMapped?=?as(blob)->rmap();
          ????const?float?*output_blob?=?blobMapped.as<float?*>();
          ????//80個類是85,一個類是6,n個類是n+5
          ????//int?item_size?=?6;
          ????int?item_size?=?85;
          ????size_t?anchor_n?=?3;
          ????for(int?n=0;n????????for(int?i=0;i????????????for(int?j=0;j????????????{
          ????????????????double?box_prob?=?output_blob[n*net_grid*net_grid*item_size?+?i*net_grid*item_size?+?j?*item_size+?4];
          ????????????????box_prob?=?sigmoid(box_prob);
          ????????????????//框置信度不滿足則整體置信度不滿足
          ????????????????if(box_prob?????????????????????continue;
          ????????????????
          ????????????????//注意此處輸出為中心點坐標,需要轉(zhuǎn)化為角點坐標
          ????????????????double?x?=?output_blob[n*net_grid*net_grid*item_size?+?i*net_grid*item_size?+?j*item_size?+?0];
          ????????????????double?y?=?output_blob[n*net_grid*net_grid*item_size?+?i*net_grid*item_size?+?j*item_size?+?1];
          ????????????????double?w?=?output_blob[n*net_grid*net_grid*item_size?+?i*net_grid*item_size?+?j*item_size?+?2];
          ????????????????double?h?=?output_blob[n*net_grid*net_grid*item_size?+?i*net_grid*item_size?+?j?*item_size+?3];
          ???????????????
          ????????????????double?max_prob?=?0;
          ????????????????int?idx=0;
          ????????????????for(int?t=5;t<85;++t){
          ????????????????????double?tp=?output_blob[n*net_grid*net_grid*item_size?+?i*net_grid*item_size?+?j?*item_size+?t];
          ????????????????????tp?=?sigmoid(tp);
          ????????????????????if(tp?>?max_prob){
          ????????????????????????max_prob?=?tp;
          ????????????????????????idx?=?t;
          ????????????????????}
          ????????????????}
          ????????????????float?cof?=?box_prob?*?max_prob;????????????????
          ????????????????//對于邊框置信度小于閾值的邊框,不關(guān)心其他數(shù)值,不進行計算減少計算量
          ????????????????if(cof?????????????????????continue;

          ????????????????x?=?(sigmoid(x)*2?-?0.5?+?j)*640.0f/net_grid;
          ????????????????y?=?(sigmoid(y)*2?-?0.5?+?i)*640.0f/net_grid;
          ????????????????w?=?pow(sigmoid(w)*2,2)?*?anchors[n*2];
          ????????????????h?=?pow(sigmoid(h)*2,2)?*?anchors[n*2?+?1];

          ????????????????double?r_x?=?x?-?w/2;
          ????????????????double?r_y?=?y?-?h/2;
          ????????????????Rect?rect?=?Rect(round(r_x),round(r_y),round(w),round(h));
          ????????????????o_rect.push_back(rect);
          ????????????????o_rect_cof.push_back(cof);
          ????????????}
          ????if(o_rect.size()?==?0)?return?false;
          ????else?return?true;
          }

          這一部分最艱難的是搞清楚輸出數(shù)據(jù)的排列方式,一開始我也試了很多次,最后才得到了正確的輸出.

          需要注意的一點是,按照輸出排列方式讀取的數(shù)值不是最終我們需要的結(jié)果,需要進行一些計算來進行轉(zhuǎn)換,

          轉(zhuǎn)換的依據(jù)可以參考yolov5/models/yolo.py中forward函數(shù)的實現(xiàn).

          注意這里有一個參數(shù)cof_threshold,其計算方式是框置信度乘以物品置信度,如果識別效果不佳,則需要對該數(shù)值進行調(diào)整.

          • NMS獲取最終結(jié)果

          經(jīng)過以上步驟,原始的框信息存儲在origin_rect變量中,還需要通過NMS去除同一個物體多余的框.

          OpenVNIO自帶的opencv提供了NMS的一種實現(xiàn),因而直接進行調(diào)用.

          ?vector<int>?final_id;
          ????dnn::NMSBoxes(origin_rect,origin_rect_cof,_cof_threshold,_nms_area_threshold,final_id);
          ????//根據(jù)final_id獲取最終結(jié)果
          ????for(int?i=0;i????????Rect?resize_rect=?origin_rect[final_id[i]];
          ????????detected_objects.push_back(Object{
          ????????????origin_rect_cof[final_id[i]],
          ????????????"",resize_rect
          ????????});
          ????}

          其中origin_rect為原始矩形,origin_rect_cof為矩形對應的置信度,_cof_threshold為置信度(框置信度乘以物品置信度)閾值,_nms_area_threshold是重疊百分比多少則算為一個物體的閾值,final_id為目標矩形在origin_rect中的下標.

          4. 性能測試

          計時實現(xiàn)如下:

          auto?start?=?chrono::high_resolution_clock::now();
          auto?end?=?chrono::high_resolution_clock::now();
          std::chrono::duration<double>?diff?=?end?-?start;
          cout<<"use?"<"?s"?<endl;

          原始的未經(jīng)優(yōu)化的CPU運行的yolov5,推理時間在240ms左右,測試平臺為intel corei7 6700hq.

          檢測結(jié)果如下:

          檢測結(jié)果

          推理加速

          • 使用核顯GPU進行計算

          _network?=??ie.LoadNetwork(cnnNetwork,?"CPU");

          改為

          _network?=??ie.LoadNetwork(cnnNetwork,?"GPU");

          如果OpenVINO環(huán)境配置設(shè)置無誤程序應該可以直接運行.

          檢測環(huán)境是否配置無誤的方法是運行:

          /opt/intel/openvino/deployment_tools/demo中的./demo_security_barrier_camera.sh

          若成功運行則cpu環(huán)境正常.

          ./demo_security_barrier_camera.sh -d GPU 運行正常則gpu環(huán)境運行正常.

          • 使用openmp進行并行化

          在推理之外的數(shù)據(jù)預處理和解析中存在大量循環(huán),這些循環(huán)都可以利用openmp進行并行優(yōu)化.

          • 模型優(yōu)化如定點化為int8類型

          在模型轉(zhuǎn)換時通過設(shè)置參數(shù)可以實現(xiàn)模型的定點化.

          git項目使用

          項目地址:https://github.com/fb029ed/yolov5_cpp_openvino

          • demo部分完成了yolov5原始模型的部署

          使用方法為依次執(zhí)行

          cd?./demo
          mkdir?build?
          cd?build
          cmake?..
          make?
          ./detect_test

          • cvmart_competition部分為開發(fā)者榜單競賽的參賽代碼,不能直接運行僅供參考

          歡迎關(guān)注GiantPandaCV, 在這里你將看到獨家的深度學習分享,堅持原創(chuàng),每天分享我們學習到的新鮮知識。( ? ?ω?? )?

          有對文章相關(guān)的問題,或者想要加入交流群,歡迎添加BBuf微信:

          二維碼

          為了方便讀者獲取資料以及我們公眾號的作者發(fā)布一些Github工程的更新,我們成立了一個QQ群,二維碼如下,感興趣可以加入。

          公眾號QQ交流群


          瀏覽 64
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色电影小视频 | 操操操操黄片免费看 | 成人网在线影片 | 国产AV黄片 | 熟女老阿8888AV |