2021.09.02更新说明 c++下使用opencv部署yolov5模型 (三)

2024-02-20 16:50

本文主要是介绍2021.09.02更新说明 c++下使用opencv部署yolov5模型 (三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

此篇文章接上篇,从上篇中导出的onnx模型的部署,此篇默认已经配置好opencv的环境了,包括如果需要使用cuda加速的环境opencv的cuda环境需要用cmake编译contrib包(此处也是个大坑),如果cuda没有安装好,opencv会默认切换cpu推理。

 -----2022.10.10 更新了下yolov5-seg实例分割的部署:

2022.09.29更新 c++下面使用opencv部署yolov5和yolov7实例分割模型(六)_爱晚乏客游的博客-CSDN博客

 -----2022.07.25 更新了下yolov7的部署,有需要的自取

2022.07.25 C++下使用opencv部署yolov7模型(五)_爱晚乏客游的博客-CSDN博客

 ------2021.11.01更新说明

由于yolov5在6.0版本增加了对opencv的支持,所以模型部署1-3适用于4.0和5.0版本的修改,6.0版本的可以看这里:

2021.11.01 c++下 opencv部署yolov5-6.0版本 (四)_爱晚乏客游的博客-CSDN博客

建议本篇文章加上第四篇的修改来达成最优的部署。

修改了置信度算法,原本使用最大类别,现在使用最大类别置信度乘以box的置信度,结果与python下更为一致。

confidences.push_back(max_class_socre*box_score);

-----2021.09.02更新

        目前YOLO版本已经迭代到第五个版本了,我之前部署的时候用的是第4个版本。目前来说大致粗略的测试一下第五版本,还是一样的修改方法,但是在dnnet=readFromONNX(f)这里,第五版会报错,但是按照前面的修改方法,c++下面的模型是可以正常读取的,如果你对这个有强迫症,那么建议你使用第4个版本的yolov5,这个版本可以在Releases · ultralytics/yolov5 · GitHub这里面找到并下载。

        而对于yolov5的P6模型,则需要根据使用的P6模型修改成对应的anchors数据,具体的数据可以在data/hub/下面找到对应模型的yaml里面找到(现在最新版变成了models/hub)。stride需要在后面添加上64步长的数据,然后将代码for里面的stride变成小于4(之前的模型是3),就可以通用其余剩下的代码。

        对于长宽比过大的图片,由于opencv的blobFromImage()函数在缩放的时候不是无损缩放,会导致图像变形严重导致结果错误或者漏检。虽然blobFromImage里面有个参数可以保持比例缩放,但是只会保留中间的部分,两边信息全部丢掉,所以如果你的目标全部在中间就可以无所谓,如果不是,那么需要简单的自己做个无损缩放,制作一张全黑的3通道正方形图片,边长为原图的长边,最后将原图放在(0,0)的位置上面,这样就可以直接输入blobFromImage里面就可以实现无损缩放了,而且不用对检测结果进行二次修正位置了。

-----2021.5.12更新:
需要注意的是:
我在写这篇文章的时候,opencv的版本是4.5.0+contrib,如果使用其他版本,不能保证不会出问题。
目前测试了3.4.x版本,需要使用3.4.13及其以上的版本,低于此版本会出现报错,报错信息指dnn模块支持CV_32S,而不支持模型中的CV_32F

-----2021.05.06 原始更新:

目录

零、新建一个头文件yolo.h和yolo.cpp,在头文件中定义一个Yolo类。

一、设置yolov5网络的一些参数。

二、opencv加载onnx模型。

三、获取网络输出结果

1、设置网络输入

2.遍历网络输出获取结果。。

3.非极大值抑制(NMS)

四、对结果进行画框显示输出

五、写个简单的调用代码测试下结果。


零、新建一个头文件yolo.h和yolo.cpp,在头文件中定义一个Yolo类。

//yolo.h#pragma once
#include<iostream>
#include<math.h>
#include<opencv2/opencv.hpp>class Yolo {
public:Yolo() {}~Yolo() {}
};

一、设置yolov5网络的一些参数。

对Yolo类加一点细节,设置一些必要的网络参数

//参数为私有参数,当然也可以是设置成公开或者保护。
private://计算归一化函数float Sigmoid(float x) {return static_cast<float>(1.f / (1.f + exp(-x)));}//anchorsconst float netAnchors[3][6] = { { 10.0, 13.0, 16.0, 30.0, 33.0, 23.0 },{ 30.0, 61.0, 62.0, 45.0, 59.0, 119.0 },{ 116.0, 90.0, 156.0, 198.0, 373.0, 326.0 } };//strideconst float netStride[3] = { 8.0, 16.0, 32.0 };const int netWidth = 640; //网络模型输入大小const int netHeight = 640;float nmsThreshold = 0.45; float boxThreshold = 0.35;float classThreshold = 0.35;//类名std::vector<std::string> className = { "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light","fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow","elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee","skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard","tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple","sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch","potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone","microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear","hair drier", "toothbrush" };

二、opencv加载onnx模型。

opencv的dnn模块提供了读取神经网络模型的函数接口readNetFromONNX(),该函数很简单。所以就可以用一句简单的语句就可以得到模型了。net = readNetFromONNX(netPath).为了整个工程,加一点点小细节,并定义一个标注位设置推理引擎用cpu或者gpu。

//在yolo.h中的 Yolo类中添加成员函数readModel:
bool readModel(cv::dnn::Net &net, std::string &netPath,bool isCuda)//yolo.cpp中实现readModel函数
//在yolo.cpp中使用命名空间
#include "yolo.h"
using namespace std;
using namespace cv;
using namespace dnn;bool Yolo::readModel(Net &net, string &netPath,bool isCuda = false) {try {net = readNetFromONNX(netPath);}catch (const std::exception&) {return false;}//cudaif (isCuda) {net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);}//cpuelse {net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);}return true;
}

三、获取网络输出结果

1、设置网络输入

推理过程就像煮饭,先准备材料(设置网络输入),然后放入锅中(送入网络中推理)等饭熟。当然煮饭还有一些细节,一起加上去,就不详细说了。

//yolo.h
//结果结构体
struct Output {int id;//结果类别idfloat confidence;//结果置信度cv::Rect box;//矩形框
};
bool Detect(cv::Mat &SrcImg,cv::dnn::Net &net, std::vector<Output> &output);//yolo.cppbool Yolo::Detect(Mat &SrcImg,Net &net,vector<Output> &output) {
Mat blob;int col = SrcImg.cols;int row = SrcImg.rows;int maxLen = MAX(col, row);Mat netInputImg = SrcImg.clone();if (maxLen > 1.2*col || maxLen > 1.2*row) {Mat resizeImg = Mat::zeros(maxLen, maxLen, CV_8UC3);SrcImg.copyTo(resizeImg(Rect(0, 0, col, row)));netInputImg = resizeImg;}blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(netWidth, netHeight), cv::Scalar(104, 117,123), true, false);//blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(netWidth, netHeight), cv::Scalar(0, 0,0), true, false);//如果训练集未对图片进行减去均值操作,则需要设置为这句//blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(netWidth, netHeight), cv::Scalar(114, 114,114), true, false);net.setInput(blob);std::vector<cv::Mat> netOutputImg;//vector<string> outputLayerName{"345","403", "461","output" };//net.forward(netOutputImg, outputLayerName[3]); //获取output的输出net.forward(netOutputImg, net.getUnconnectedOutLayersNames());......
}//这个括号是最末尾的,包括下面添加之后

2.遍历网络输出获取结果。。

这一步可以说是整个部署中最难的地方。看过上篇文章的话,就知道网络输出的是一个二维数组【25200*85】。这一步需要做的就是遍历每一行的长度为85的一维数组,并且获取符合条件的结果,先上代码,有些地方有注释,可以看看。

//接上面std::vector<int> classIds;//结果id数组std::vector<float> confidences;//结果每个id对应置信度数组std::vector<cv::Rect> boxes;//每个id矩形框float ratio_h = (float)netInputImg.rows / netHeight;float ratio_w = (float)netInputImg.cols / netWidth;int net_width = className.size() + 5;  //输出的网络宽度是类别数+5float* pdata = (float*)netOutputImg[0].data;for (int stride = 0; stride < 3; stride++) {    //strideint grid_x = (int)(netWidth / netStride[stride]);int grid_y = (int)(netHeight / netStride[stride]);for (int anchor = 0; anchor < 3; anchor++) { //anchorsconst float anchor_w = netAnchors[stride][anchor * 2];const float anchor_h = netAnchors[stride][anchor * 2 + 1];for (int i = 0; i < grid_y; i++) {for (int j = 0; j < grid_y; j++) {float box_score = Sigmoid(pdata[4]);//获取每一行的box框中含有某个物体的概率if (box_score > boxThreshold) {//为了使用minMaxLoc(),将85长度数组变成Mat对象cv::Mat scores(1,className.size(), CV_32FC1, pdata+5);Point classIdPoint;double max_class_socre;minMaxLoc(scores, 0, &max_class_socre, 0, &classIdPoint);max_class_socre = Sigmoid((float)max_class_socre);if (max_class_socre > classThreshold) {//rect [x,y,w,h]float x = (Sigmoid(pdata[0]) * 2.f - 0.5f + j) * netStride[stride];  //xfloat y = (Sigmoid(pdata[1]) * 2.f - 0.5f + i) * netStride[stride];   //yfloat w = powf(Sigmoid(pdata[2]) * 2.f, 2.f) * anchor_w;   //wfloat h = powf(Sigmoid(pdata[3]) * 2.f, 2.f) * anchor_h;  //hint left = (x - 0.5*w)*ratio_w;int top = (y - 0.5*h)*ratio_h;classIds.push_back(classIdPoint.x);confidences.push_back(max_class_socre*box_score);boxes.push_back(Rect(left, top, int(w*ratio_w), int(h*ratio_h)));}}pdata += net_width;//指针移到下一行}}}}

3.非极大值抑制(NMS)

上面网络会有很多的输出框重叠在一起,需要使用nms进行过滤重叠框。

//接上面执行非最大抑制以消除具有较低置信度的冗余重叠框(NMS)vector<int> nms_result;NMSBoxes(boxes, confidences, classThreshold, nmsThreshold, nms_result);for (int i = 0; i < nms_result.size(); i++) {int idx = nms_result[i];Output result;result.id = classIds[idx];result.confidence = confidences[idx];result.box = boxes[idx];output.push_back(result);}if (output.size())return true;elsereturn false;
未经过NMS(左图)和经过NMS结果对比(右图)

这里需要注意的是,opencv的nms是普通的nms网络,而yolov5中使用的是nms-giou,这里有点稍微不同,会导致检测结果出现一定的偏差。有强迫症的需要自己实现下nms的功能。也不会很难,可以将yolov5中计算iou的部分改写成c++的代码就可以实现nms-giou。

四、对结果进行画框显示输出

//yolo.hvoid drawPred(cv::Mat &img, std::vector<Output> result, std::vector<cv::Scalar> color);//yolo.cpp
//这里的color是颜色数组,对没一个id随机分配一种颜色
void Yolo::drawPred(Mat &img, vector<Output> result, vector<Scalar> color) {for (int i = 0; i < result.size(); i++) {int left, top;left = result[i].box.x;top = result[i].box.y;int color_num = i;rectangle(img, result[i].box, color[result[i].id], 2, 8);string label = className[result[i].id] +":" + to_string(result[i].confidence);int baseLine;Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);top = max(top, labelSize.height);//rectangle(frame, Point(left, top - int(1.5 * labelSize.height)), Point(left + int(1.5 * labelSize.width), top + baseLine), Scalar(0, 255, 0), FILLED);putText(img, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 1, color[result[i].id], 2);}imshow("res", img);//imwrite("./result.jpg", img);waitKey();//destroyAllWindows();
}

五、写个简单的调用代码测试下结果。

高端的食材往往只需要简单的烹饪方式,忙活了一天的林师傅,开始准备写调用代码的正餐了,只见他先焚香沐浴了半小时,终于成功的配置好了opencv的环境,然后的处理好的yolo.h头文件和yolo.cpp文件include进代码中,并且郑重的写下了第一行代码:

#include "yolo.h"
#include <iostream>
#include<opencv2//opencv.hpp>
#include<math.h>using namespace std;
using namespace cv;
using namespace dnn;int main()
{cout << "Hello World" << endl;return 0;
}

 林师傅尝了一口感觉不得劲,又加了点细节,

int main()
{cout << "Hello World" << endl;string img_path = "./test.jpg";string model_path = "./yolov5s.onnx";Yolo test;Net net;if (test.readModel(net, model_path, true)) {cout << "read net ok!" << endl;}else {return -1;}//生成随机颜色vector<Scalar> color;srand(time(0));for (int i = 0; i < 80; i++) {int b = rand() % 256;int g = rand() % 256;int r = rand() % 256;color.push_back(Scalar(b, g, r));}vector<Output> result;Mat img = imread(img_path);if (test.Detect(img, net, result)) {test.drawPred(img, result, color);}else {cout << "Detect Failed!"<<endl;}system("pause");return 0;
}

运行看下结果

エロ卡门

终于搞定了!

这篇关于2021.09.02更新说明 c++下使用opencv部署yolov5模型 (三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/728884

相关文章

Nginx更新SSL证书的实现步骤

《Nginx更新SSL证书的实现步骤》本文主要介绍了Nginx更新SSL证书的实现步骤,包括下载新证书、备份旧证书、配置新证书、验证配置及遇到问题时的解决方法,感兴趣的了解一下... 目录1 下载最新的SSL证书文件2 备份旧的SSL证书文件3 配置新证书4 验证配置5 遇到的http://www.cppc

java中4种API参数传递方式统一说明

《java中4种API参数传递方式统一说明》在Java中,我们可以使用不同的方式来传递参数给方法或函数,:本文主要介绍java中4种API参数传递方式的相关资料,文中通过代码介绍的非常详细,需要的... 目录1. 概述2. 参数传递方式分类2.1 Query Parameters(查询参数)2.2 Path

mysql_mcp_server部署及应用实践案例

《mysql_mcp_server部署及应用实践案例》文章介绍了在CentOS7.5环境下部署MySQL_mcp_server的步骤,包括服务安装、配置和启动,还提供了一个基于Dify工作流的应用案例... 目录mysql_mcp_server部署及应用案例1. 服务安装1.1. 下载源码1.2. 创建独立

Mysql中RelayLog中继日志的使用

《Mysql中RelayLog中继日志的使用》MySQLRelayLog中继日志是主从复制架构中的核心组件,负责将从主库获取的Binlog事件暂存并应用到从库,本文就来详细的介绍一下RelayLog中... 目录一、什么是 Relay Log(中继日志)二、Relay Log 的工作流程三、Relay Lo

使用Redis实现会话管理的示例代码

《使用Redis实现会话管理的示例代码》文章介绍了如何使用Redis实现会话管理,包括会话的创建、读取、更新和删除操作,通过设置会话超时时间并重置,可以确保会话在用户持续活动期间不会过期,此外,展示了... 目录1. 会话管理的基本概念2. 使用Redis实现会话管理2.1 引入依赖2.2 会话管理基本操作

Springboot请求和响应相关注解及使用场景分析

《Springboot请求和响应相关注解及使用场景分析》本文介绍了SpringBoot中用于处理HTTP请求和构建HTTP响应的常用注解,包括@RequestMapping、@RequestParam... 目录1. 请求处理注解@RequestMapping@GetMapping, @PostMappin

springboot3.x使用@NacosValue无法获取配置信息的解决过程

《springboot3.x使用@NacosValue无法获取配置信息的解决过程》在SpringBoot3.x中升级Nacos依赖后,使用@NacosValue无法动态获取配置,通过引入SpringC... 目录一、python问题描述二、解决方案总结一、问题描述springboot从2android.x

Nginx服务器部署详细代码实例

《Nginx服务器部署详细代码实例》Nginx是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务,:本文主要介绍Nginx服务器部署的相关资料,文中通过代码... 目录Nginx 服务器SSL/TLS 配置动态脚本反向代理总结Nginx 服务器Nginx是一个‌高性

SpringBoot整合AOP及使用案例实战

《SpringBoot整合AOP及使用案例实战》本文详细介绍了SpringAOP中的切入点表达式,重点讲解了execution表达式的语法和用法,通过案例实战,展示了AOP的基本使用、结合自定义注解以... 目录一、 引入依赖二、切入点表达式详解三、案例实战1. AOP基本使用2. AOP结合自定义注解3.

Python中Request的安装以及简单的使用方法图文教程

《Python中Request的安装以及简单的使用方法图文教程》python里的request库经常被用于进行网络爬虫,想要学习网络爬虫的同学必须得安装request这个第三方库,:本文主要介绍P... 目录1.Requests 安装cmd 窗口安装为pycharm安装在pycharm设置中为项目安装req