{"id":13438520,"url":"https://github.com/fb029ed/yolov5_cpp_openvino","last_synced_at":"2025-03-20T06:30:44.821Z","repository":{"id":37396452,"uuid":"318162654","full_name":"fb029ed/yolov5_cpp_openvino","owner":"fb029ed","description":"用c++实现了yolov5使用openvino的部署","archived":false,"fork":false,"pushed_at":"2021-04-11T09:59:07.000Z","size":25721,"stargazers_count":268,"open_issues_count":18,"forks_count":61,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-28T00:23:14.135Z","etag":null,"topics":["cpp","openvino","yolov5"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fb029ed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-03T10:54:06.000Z","updated_at":"2024-09-18T10:25:26.000Z","dependencies_parsed_at":"2022-07-08T00:20:34.635Z","dependency_job_id":null,"html_url":"https://github.com/fb029ed/yolov5_cpp_openvino","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fb029ed%2Fyolov5_cpp_openvino","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fb029ed%2Fyolov5_cpp_openvino/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fb029ed%2Fyolov5_cpp_openvino/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fb029ed%2Fyolov5_cpp_openvino/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fb029ed","download_url":"https://codeload.github.com/fb029ed/yolov5_cpp_openvino/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244564956,"owners_count":20473165,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","openvino","yolov5"],"created_at":"2024-07-31T03:01:06.193Z","updated_at":"2025-03-20T06:30:39.812Z","avatar_url":"https://github.com/fb029ed.png","language":"C++","funding_links":[],"categories":["C++","Lighter and Deployment Frameworks"],"sub_categories":[],"readme":"# c++实现yolov5的OpenVINO部署\n\n\u003e 本文介绍了一种使用c++实现的,使用OpenVINO部署yolov5的方法.\n\u003e\n\u003e 此方法在2020年9月结束的极市开发者榜单中取得后厨老鼠识别赛题第四名.\n\u003e\n\u003e 2020年12月,注意到yolov5有了许多变化,对部署流程重新进行了测试,并进行了整理.\n\u003e\n\u003e 希望能给需要的朋友一些参考,节省一些踩坑的时间.\n\u003e \n\u003e 2021.2.27\n\u003e 注意到yolov5更新了4.0版本的release,该仓库不能直接使用，请git reset到v3.1对应的版本下使用.\n\u003e 如果有改好的兼容4.0版本的实现，欢迎提merge.\n\n\n\n## 模型训练\n\n### 1. 首先获取yolov5工程\n\n```shell\ngit clone https://github.com/ultralytics/yolov5.git\n```\n\n本文编辑的时间是2020年12月3日,官方最新的releases是v3.1,在v3.0的版本中,官网有如下的声明\n\n\u003e * August 13, 2020**: [v3.0 release](https://github.com/ultralytics/yolov5/releases/tag/v3.0): nn.Hardswish() activations, data autodownload, native AMP.\n\nyolov5训练获得的原始的模型以.pt文件方式存储,要转换为OpenVINO的.xml和.bin的模型存储方式,需要经历两次转换.\n\n两次转换所用到的工具无法同时支持nn.Hardswish()函数的转换,v3.0版本时需要切换到v2.0版本替换掉nn.Hardswish()函数才能够完成两次模型转换,当时要完成模型转换非常的麻烦.\n\n在v3.1版本的yolov5中用于进行pt模型转onnx模型的程序对nn.Hardswish()进行了兼容,模型转换过程大为化简.\n后续实现基于v3.1版本.\n\n### 2. 训练准备\n\nyolov5官方的指南: https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data\n\n#### 描述信息准备\n\n在yolov5的文件夹下/yolov5/models/目录下可以找到以下文件\n\n\u003e yolov5s.yaml\n\u003e\n\u003e yolov5m.yaml\n\u003e\n\u003e yolov5l.yaml\n\n这三个文件分别对应s(小尺寸模型),ｍ(中尺寸模型)和l(大尺寸模型)的结构描述信息\n\n其中为了实现自己的训练常常需要更改以下两个参数\n\n* nc\n\n  需要识别的类别数量,yolov5原始的默认类别数量为80\n\n* anchors\n\n  通过kmeans等算法根据自己的数据集得出合适的锚框．\n  这里需要注意:yolov5内部实现了锚框的自动计算训练过程默认使用自适应锚框计算.\n\n  经过实际测试，自己通过kmeans算法得到的锚框在特定数据集上能取得更好的性能\n\n  在3.执行训练中将提到禁止自动锚框计算的方法.\n\n#### 数据准备\n\n参考官方指南的\n\n* Create Labels\n* Organize Directories\n\n部分的数据要求\n\n注意标注格式是class x_center y_center width height,其中x_center y_center width height均是根据图像尺寸归一化的0到1之间的数值.\n\n### 3. 执行训练\n\n```python\npython ~/src_repo/yolov5/train.py --batch 16 --epochs 10 --data ~/src_repo/rat.yaml --cfg ~/src_repo/yolov5/models/yolov5s.yaml --weights \"\"\n```\n\n其中\n\n* --data　参数后面需要填充的是训练数据的说明文件．其中需要说明训练集，测试集，种类数目和种类名称等信息，具体格式可以参考yolov5/data/coco.yaml.\n* --cfg　为在训练准备阶段完成的模型结构描述文件.\n* --weights　后面跟预训练模型的路径,如果是\"\"则重新训练一个模型.推荐使用预训练模型继续训练,不使用该参数则默认使用预训练模型.\n* --noautoanchor　该参数可选，使用该参数则禁止自适应anchor计算，使用--cfg文件中提供的原始锚框.\n\n\n\n## 模型转换\n\n经过训练,模型的原始存储格式为.pt格式，为了实现OpenVINO部署，需要首先转换为.onnx的存储格式，之后再转化为OpenVINO需要的.xml和.bin的存储格式.\n\n### 1. pt格式转onnx格式\n\n这一步的转换主要由yolov5/models/export.py脚本实现.\n\n可以参考yolov5提供的简单教程:https://github.com/ultralytics/yolov5/issues/251\n\n使用该教程中的方法可以获取onnx模型,但直接按照官方方式获取的onnx模型其中存在OpenVINO模型转换中不支持的运算,因此,使用该脚本之前需要进行一些更改:\n\n* opset_version\n\n在/yolov5/models/export.py中\n\n```python\ntorch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'],\n                          output_names=['classes', 'boxes'] if y is None else ['output'])\n```\n\nopset_version=12,将导致后面的OpenVINO模型装换时遇到未支持的运算\n因此设置为opset_version=10.\n\n* Detect layer export\n\n```python\nmodel.model[-1].export = True  \n```\n\n设置为True则Detect层(包含nms,锚框计算等)不会输出到模型中.\n\n设置为False包含Detect层的模型无法通过onnx到OpenVINO格式模型的转换.\n\n需要执行如下指令:\n\n```shell\npython ./models/export.py --weight .pt文件路径 --img 640 --batch 1\n```\n\n需要注意的是在填入的.pt文件路径不存在时,该程序会自动下载官方预训练的模型作为转换的原始模型,转换完成则获得onnx格式的模型.\n\n转换完成后可以使用Netron:https://github.com/lutzroeder/netron.git 进行可视化.对于陌生的模型,该可视化工具对模型结构的认识有很大的帮助.\n\n![net](https://github.com/fb029ed/yolov5_cpp_openvino/blob/master/img/net.png)\n\n### 2. onnx格式转换OpenVINO的xml和bin格式\n\nOpenVINO是一个功能丰富的跨平台边缘加速工具箱,本文用到了其中的模型优化工具和推理引擎两部分内容.\n\nOpenVINO的安装配置可以参考https://docs.openvinotoolkit.org/2019_R2/_docs_install_guides_installing_openvino_linux.html ,本文的所有实现基于2020.4版本,为确保可用,建议下载2020.4版本的OpenVINO.\n\n安装完成后在~/.bashrc文件中添加如下内容,用于在终端启动时配置环境变量.\n\n```shell\nsource /opt/intel/openvino/bin/setupvars.sh\nsource /opt/intel/openvino/opencv/setupvars.sh\n```\n\n安装完成后运行如下脚本实现onnx模型到xml bin模型的转换.\n\n```shell\npython /opt/intel/openvino/deployment_tools/model_optimizer/mo_onnx.py --input_model .onnx文件路径  --output_dir 期望模型输出的路径\n```\n\n运行成功之后会获得.xml和.bin文件,xml和bin是OpenVINO中的模型存储方式,后续将基于bin和xml文件进行部署.该模型转换工具还有定点化等模型优化功能,有兴趣可以自己试试.\n\n\n\n## 使用OpenVINO进行推理部署\n\nOpenVINO除了模型优化工具外,还提供了一套运行时推理引擎.\n\n想使用OpenVINO的模型进行推理部署,有两种方式,第一种方式是使用OpenVINO原生的sdk,另外一种方式是使用支持OpenVINO的opencv(比如OpenVINO自带的opencv)进行部署,本文对原生sdk的部署方式进行介绍.\n\nOpenVINO提供了相对丰富的例程,本文中实现的yolov5的部署参考了/opt/intel/openvino/deployment_tools/inference_engine/demos/object_detection_demo_yolov3_async文件夹中yolov3的实现方式.\n\n### 1. 推理引擎的初始化\n\n首先需要进行推理引擎的初始化,此部分代码封装在detector.cpp的init函数.\n\n主要流程如下:\n\n```c++\nCore ie;\n//读入xml文件,该函数会在xml文件的目录下自动读取相应的bin文件,无需手动指定\nauto cnnNetwork = ie.ReadNetwork(_xml_path); \n//从模型中获取输入数据的格式信息\nInputsDataMap inputInfo(cnnNetwork.getInputsInfo());\nInputInfo::Ptr\u0026 input = inputInfo.begin()-\u003esecond;\n_input_name = inputInfo.begin()-\u003efirst;\ninput-\u003esetPrecision(Precision::FP32);\ninput-\u003egetInputData()-\u003esetLayout(Layout::NCHW);\nICNNNetwork::InputShapes inputShapes = cnnNetwork.getInputShapes();\nSizeVector\u0026 inSizeVector = inputShapes.begin()-\u003esecond;\ncnnNetwork.reshape(inputShapes);\n//从模型中获取推断结果的格式\n_outputinfo = OutputsDataMap(cnnNetwork.getOutputsInfo());\nfor (auto \u0026output : _outputinfo) {\n    output.second-\u003esetPrecision(Precision::FP32);\n}\n//获取可执行网络,这里的CPU指的是推断运行的器件,可选的还有\"GPU\",这里的GPU指的是intel芯片内部的核显\n//配置好核显所需的GPU运行环境,使用GPU模式进行的推理速度上有很大提升,这里先拿CPU部署后面会提到GPU环境的配置方式\n_network =  ie.LoadNetwork(cnnNetwork, \"CPU\");\n```\n\n### 2. 数据准备\n\n为了适配网络的输入数据格式要求,需要对原始的opencv读取的Mat数据进行预处理.\n\n* resize\n\n最简单的方式是将输入图像直接resize到640*640尺寸,此种方式会造成部分物体失真变形,识别准确率会受到部分影响,简单起见,在demo代码里使用了该方式.\n\n在竞赛代码中,为了追求正确率,图像缩放的时候需要按图像原始比例将图像的长或宽缩放到640.假设长被放大到640,宽按照长的变换比例无法达到640,则在图像的两边填充黑边确保输入图像总尺寸为640*640.竞赛代码中使用了该种缩放方式,需要注意的是如果使用该种缩放方式,在获取结果时需要将结果转换为在原始图像中的坐标.\n\n* 颜色通道转换\n\n鉴于opencv和pytorch的颜色通道差异,opencv是BGR通道,pytorch是RGB,在输入网络之前,需要进行通道转换.\n\n* 推断请求和blob填充\n\n```c++\nInferRequest::Ptr infer_request = _network.CreateInferRequestPtr();\nBlob::Ptr frameBlob = infer_request-\u003eGetBlob(_input_name);\nInferenceEngine::LockedMemory\u003cvoid\u003e blobMapped = InferenceEngine::as\u003cInferenceEngine::MemoryBlob\u003e(frameBlob)-\u003ewmap();\nfloat* blob_data = blobMapped.as\u003cfloat*\u003e();\n//nchw\nfor(size_t row =0;row\u003c640;row++){\n    for(size_t col=0;col\u003c640;col++){\n        for(size_t ch =0;ch\u003c3;ch++){\n            //将图像转换为浮点型填入模型\n            blob_data[img_size*ch + row*640 + col] = float(inframe.at\u003cVec3b\u003e(row,col)[ch])/255.0f;\n        }\n    }\n}\n```\n\n### 3. 推断执行与解析\n\n*　推断执行\n\n```c++\ninfer_request-\u003eInfer();\n```\n\n* 获取推断结果\n\n从Netron的可视化结果可知\n\n![output](https://github.com/fb029ed/yolov5_cpp_openvino/blob/master/img/output.png)\n\n网络只包含到输出三个检测头的部分，三个检测头分别对应80,40,和20的栅格尺寸,因此需要对三种尺寸的检测头输出结果依次解析,具体的解析过程在parse_yolov5函数中进行了实现:\n\n```c++\n//获取各层结果\nvector\u003cRect\u003e origin_rect;                     //保存原始的框信息\nvector\u003cfloat\u003e origin_rect_cof;            //保存框对应的置信度信息\nint s[3] = {80,40,20};\nint i=0;\nfor (auto \u0026output : _outputinfo) {\n    auto output_name = output.first;\n    Blob::Ptr blob = infer_request-\u003eGetBlob(output_name);\n    parse_yolov5(blob,s[i],_cof_threshold,origin_rect,origin_rect_cof);\n    ++i;\n}\n```\n\n* 对检测头的内容进行解析\n\n这部分主要是使用c++将yolov5代码中的detect层内容重新实现一下,主要代码实现如下:\n\n```c++\n//注意此处的阈值是框和物体prob乘积的阈值\nbool Detector::parse_yolov5(const Blob::Ptr \u0026blob,int net_grid,float cof_threshold,\n    vector\u003cRect\u003e\u0026 o_rect,vector\u003cfloat\u003e\u0026 o_rect_cof){\n    vector\u003cint\u003e anchors = get_anchors(net_grid);\n    LockedMemory\u003cconst void\u003e blobMapped = as\u003cMemoryBlob\u003e(blob)-\u003ermap();\n    const float *output_blob = blobMapped.as\u003cfloat *\u003e();\n    //80个类是85,一个类是6,n个类是n+5\n    //int item_size = 6;\n    int item_size = 85;\n    size_t anchor_n = 3;\n    for(int n=0;n\u003canchor_n;++n)\n        for(int i=0;i\u003cnet_grid;++i)\n            for(int j=0;j\u003cnet_grid;++j)\n            {\n                double box_prob = output_blob[n*net_grid*net_grid*item_size + i*net_grid*item_size + j *item_size+ 4];\n                box_prob = sigmoid(box_prob);\n                //框置信度不满足则整体置信度不满足\n                if(box_prob \u003c cof_threshold)\n                    continue;\n                \n                //注意此处输出为中心点坐标,需要转化为角点坐标\n                double x = output_blob[n*net_grid*net_grid*item_size + i*net_grid*item_size + j*item_size + 0];\n                double y = output_blob[n*net_grid*net_grid*item_size + i*net_grid*item_size + j*item_size + 1];\n                double w = output_blob[n*net_grid*net_grid*item_size + i*net_grid*item_size + j*item_size + 2];\n                double h = output_blob[n*net_grid*net_grid*item_size + i*net_grid*item_size + j *item_size+ 3];\n               \n                double max_prob = 0;\n                int idx=0;\n                for(int t=5;t\u003c85;++t){\n                    double tp= output_blob[n*net_grid*net_grid*item_size + i*net_grid*item_size + j *item_size+ t];\n                    tp = sigmoid(tp);\n                    if(tp \u003e max_prob){\n                        max_prob = tp;\n                        idx = t;\n                    }\n                }\n                float cof = box_prob * max_prob;                \n                //对于边框置信度小于阈值的边框,不关心其他数值,不进行计算减少计算量\n                if(cof \u003c cof_threshold)\n                    continue;\n\n                x = (sigmoid(x)*2 - 0.5 + j)*640.0f/net_grid;\n                y = (sigmoid(y)*2 - 0.5 + i)*640.0f/net_grid;\n                w = pow(sigmoid(w)*2,2) * anchors[n*2];\n                h = pow(sigmoid(h)*2,2) * anchors[n*2 + 1];\n\n                double r_x = x - w/2;\n                double r_y = y - h/2;\n                Rect rect = Rect(round(r_x),round(r_y),round(w),round(h));\n                o_rect.push_back(rect);\n                o_rect_cof.push_back(cof);\n            }\n    if(o_rect.size() == 0) return false;\n    else return true;\n}\n```\n\n这一部分最艰难的是搞清楚输出数据的排列方式,一开始我也试了很多次,最后才得到了正确的输出.\n\n需要注意的一点是,按照输出排列方式读取的数值不是最终我们需要的结果,需要进行一些计算来进行转换,\n\n转换的依据可以参考yolov5/models/yolo.py中forward函数的实现.\n\n注意这里有一个参数cof_threshold,其计算方式是框置信度乘以物品置信度,如果识别效果不佳,则需要对该数值进行调整.\n\n* NMS获取最终结果\n\n经过以上步骤,原始的框信息存储在origin_rect变量中,还需要通过NMS去除同一个物体多余的框.\n\nOpenVNIO自带的opencv提供了NMS的一种实现,因而直接进行调用.\n\n```c++\n vector\u003cint\u003e final_id;\n    dnn::NMSBoxes(origin_rect,origin_rect_cof,_cof_threshold,_nms_area_threshold,final_id);\n    //根据final_id获取最终结果\n    for(int i=0;i\u003cfinal_id.size();++i){\n        Rect resize_rect= origin_rect[final_id[i]];\n        detected_objects.push_back(Object{\n            origin_rect_cof[final_id[i]],\n            \"\",resize_rect\n        });\n    }\n```\n\n其中origin_rect为原始矩形,origin_rect_cof为矩形对应的置信度,_cof_threshold为置信度(框置信度乘以物品置信度)阈值,_nms_area_threshold是重叠百分比多少则算为一个物体的阈值,final_id为目标矩形在origin_rect中的下标.\n\n### 4. 性能测试\n\n计时实现如下:\n\n```c++\nauto start = chrono::high_resolution_clock::now();\nauto end = chrono::high_resolution_clock::now();\nstd::chrono::duration\u003cdouble\u003e diff = end - start;\ncout\u003c\u003c\"use \"\u003c\u003cdiff.count()\u003c\u003c\" s\" \u003c\u003c endl;\n```\n\n原始的未经优化的CPU运行的yolov5,推理时间在240ms左右,测试平台为intel corei7 6700hq.\n\n检测结果如下:\n\n![result](https://github.com/fb029ed/yolov5_cpp_openvino/blob/master/img/result.png)\n\n\n\n## 推理加速\n\n* 使用核显GPU进行计算\n\n将\n\n```c++\n_network =  ie.LoadNetwork(cnnNetwork, \"CPU\");\n```\n\n改为\n\n```c++\n_network =  ie.LoadNetwork(cnnNetwork, \"GPU\");\n```\n\n如果OpenVINO环境配置设置无误程序应该可以直接运行.\n\n检测环境是否配置无误的方法是运行:\n\n/opt/intel/openvino/deployment_tools/demo中的./demo_security_barrier_camera.sh\n\n若成功运行则cpu环境正常.\n\n./demo_security_barrier_camera.sh -d GPU 运行正常则gpu环境运行正常.\n\n* 使用openmp进行并行化\n\n在推理之外的数据预处理和解析中存在大量循环,这些循环都可以利用openmp进行并行优化.\n\n* 模型优化如定点化为int8类型\n\n在模型转换时通过设置参数可以实现模型的定点化.\n\n\n\n## git项目使用\n\n项目地址:https://github.com/fb029ed/yolov5_cpp_openvino\n\n* demo部分完成了yolov5原始模型的部署\n\n使用方法为依次执行\n\n```shell\ncd ./demo\nmkdir build \ncd build\ncmake ..\nmake \n./detect_test\n```\n\n* cvmart_competition部分为开发者榜单竞赛的参赛代码,不能直接运行仅供参考\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffb029ed%2Fyolov5_cpp_openvino","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffb029ed%2Fyolov5_cpp_openvino","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffb029ed%2Fyolov5_cpp_openvino/lists"}