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

相关文章

闲置电脑也能活出第二春?鲁大师AiNAS让你动动手指就能轻松部署

对于大多数人而言,在这个“数据爆炸”的时代或多或少都遇到过存储告急的情况,这使得“存储焦虑”不再是个别现象,而将会是随着软件的不断臃肿而越来越普遍的情况。从不少手机厂商都开始将存储上限提升至1TB可以见得,我们似乎正处在互联网信息飞速增长的阶段,对于存储的需求也将会不断扩大。对于苹果用户而言,这一问题愈发严峻,毕竟512GB和1TB版本的iPhone可不是人人都消费得起的,因此成熟的外置存储方案开

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数