Bubbliiiing版本yolov7 c++opencv dnn部署

这篇博客详细介绍了如何使用Bubbliiiing的yolov7代码进行模型训练,并指导读者如何将训练好的模型转换为ONNX格式,以便在VS Studio中部署。作者提供了关键代码修改和部署步骤,包括输出层的调整和最终的预测结果展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用B导的yolov7代码部署,代码地址:https://github.com/bubbliiiing/yolov7-pytorch

模型的的训练看B导即可,up主地址:Bubbliiiing的博客_优快云博客-神经网络学习小记录,睿智的目标检测,有趣的数据结构算法领域博主

模型训练完成之后,在predict.py中设置mode = "export_onnx"即可生成。

注意,此处有个坑,B导的yolov7代码输出的onnx只有1*class.size*20*20这一层,需要在nets/yolo.py文件中修改一下。

修改之前:(在yolo.py的最下面)

 #---------------------------------------------------#
 #   第三个特征层
 #   y3=(batch_size, 36, 80, 80)
 #---------------------------------------------------#
 out2 = self.yolo_head_P3(P3)
 #---------------------------------------------------#
 #   第二个特征层
 #   y2=(batch_size, 36, 40, 40)
 #---------------------------------------------------#
 out1 = self.yolo_head_P4(P4)
 #---------------------------------------------------#
 #   第一个特征层
 #   y1=(batch_size, 36, 20, 20)
 #---------------------------------------------------#
 out0 = self.yolo_head_P5(P5)
 return [out0, out1, out2]

修改之后:

#---------------------------------------------------#
#   第三个特征层
#   y3=(batch_size, 36, 80, 80)
#---------------------------------------------------#
out2 = self.yolo_head_P3(P3)
bs, _, ny, nx = out2.shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
out2 = out2.view(bs, 3, 12, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
out2 = out2.view(bs * 3 * ny * nx, 12).contiguous()
#---------------------------------------------------#
#   第二个特征层
#   y2=(batch_size, 36, 40, 40)
#---------------------------------------------------#
out1 = self.yolo_head_P4(P4)
bs, _, ny, nx = out1.shape
out1 = out1.view(bs, 3, 12, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
out1 = out1.view(bs * 3 * ny * nx, 12).contiguous()
#---------------------------------------------------#
#   第一个特征层
#   y1=(batch_size, 36, 20, 20)
#---------------------------------------------------#
out0 = self.yolo_head_P5(P5)
bs, _, ny, nx = out0.shape
out0 = out0.view(bs, 3, 12, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
out0 = out0.view(bs * 3 * ny * nx, 12).contiguous()

#return [out0, out1, out2]
return torch.cat((out2,out1,out0))

这样我们可以看到输出的shape已经变成了25200*12了!

解释下这个数据:网络原本的输出是1*36*80*80,1*36*40*40,1*36*20*20,36是因为我的模型的类别数为7,36=(5+7)*3,5为四个位置信息加置信度,3为anchor数,经过上述代码的操作就把所有的输出拼接起来了,结果为25200*12,一共有25200个预测结果与每个结果对应12个信息。

之后我们就可以利用生成的onnx在vs studio中进行部署啦。

main.cpp:

#include "yolo.h"
#include <iostream>
#include<opencv2//opencv.hpp>
#include<math.h>

using namespace std;
using namespace cv;
using namespace dnn;

int main()
{
	string img_path = "3.jpg";
	string model_path = "models.onnx";

	Yolo test;
	Net net;
    //加载onnx模型
	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;
}

yolo.h:

#pragma once
#include<iostream>
#include<math.h>
#include<opencv2/opencv.hpp>
struct Output {
	int id;//结果类别id
	float confidence;//结果置信度
	cv::Rect box;//矩形框
};

class Yolo
{
public:
	Yolo() {}
	~Yolo(){}
	bool readModel(cv::dnn::Net& net, std::string& netPath, bool isCuda);
	bool Detect(cv::Mat& SrcImg, cv::dnn::Net& net, std::vector<Output>& output);
	void drawPred(cv::Mat& img, std::vector<Output> result, std::vector<cv::Scalar> color);

private:
	//计算归一化函数
	float Sigmoid(float x) {
		return static_cast<float>(1.f / (1.f + exp(-x)));
	}
	//anchors
	const float netAnchors[3][6] = { { 12.0, 16.0,  19.0, 36.0,  40.0, 28.0 },{ 36.0, 75.0,  76.0, 55.0,  72.0, 146.0 },{ 142.0, 110.0,  192.0, 243.0,  459.0, 401.0 } };
	//stride
	const 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 = { "scratch","Exposed components","Reverse printing","Missing print","6.8CA","D7","TB20K"};

};

 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;
	}
	//cuda
	if (isCuda) {
		net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
		net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
	}
	//cpu
	else {
		net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
		net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
	}
	return true;
}


bool 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);
	//blob = blobFromImage(netInputImg, 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());
	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;  //输出的网络宽度是类别数+5
	float* pdata = (float*)netOutputImg[0].data;

	for (int stride = 0; stride < 3; stride++) {    //stride
		int grid_x = (int)(netWidth / netStride[stride]);
		int grid_y = (int)(netHeight / netStride[stride]);
		for (int anchor = 0; anchor < 3; anchor++) { //anchors
			const float anchor_w = netAnchors[stride][anchor * 2];
			const float anchor_h = netAnchors[stride][anchor * 2 + 1];
			for (int i = 0; i < grid_x; 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;
						//cout << scores << endl;
						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];  //x
							float y = (Sigmoid(pdata[1]) * 2.f - 0.5f + i) * netStride[stride];   //y
							float w = powf(Sigmoid(pdata[2]) * 2.f, 2.f) * anchor_w;   //w
							float h = powf(Sigmoid(pdata[3]) * 2.f, 2.f) * anchor_h;  //h
							int 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;//指针移到下一行
				}
			}
		}
	}
	vector<int> nms_result;
	dnn::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;
	else
		return false;
}
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);
	waitKey();
}

预测结果:

 大功告成啦,不得不说yolov7的效果相当的好,也感谢B导大大啦

<think>好的,我现在要解决用户的问题:在C++中使用YOLOv8OBB与OpenCV DNN进行目标检测,并请求示例代码。首先,我需要理清YOLOv8OBB是什么,以及它与普通YOLOv8的区别。YOLOv8OBB应该是用于旋转框检测的版本,即Oriented Bounding Boxes,常用于检测倾斜或旋转的物体,比如遥感图像中的车辆或卫星图像中的建筑。 接下来,用户提到使用OpenCV DNN模块。我需要回忆一下OpenCV DNN是否支持旋转框的输出。通常,OpenCVDNN模块在处理目标检测模型时,输出的是矩形框(x, y, w, h)或者带有角度的形式吗?可能需要查看YOLOv8OBB的输出层结构。根据引用[3]中的信息,用户提供的GitHub链接可能包含YOLOv8-Pose的Python实现,但这里需要的是C++的实现,特别是OBB版本。 首先,用户需要C++的示例代码。我需要考虑如何将YOLOv8OBB的模型转换为ONNX格式,因为OpenCV DNN通常需要ONNX或其他支持的模型格式。然后,在C++中使用OpenCVdnn::readNetFromONNX加载模型。接下来是预处理图像,设置输入,前向推理,解析输出。 问题在于YOLOv8OBB的输出结构可能不同于普通YOLOv8。普通YOLOv8检测的输出通常是(batch, num_anchors, 4 + 1 + num_classes),其中4是框的坐标,1是置信度,num_classes是类别概率。而OBB版本可能需要额外的参数,比如角度,或者四个角点的坐标。因此,解析输出时需要调整,可能需要处理每个检测结果的更多维度。 另外,用户提到的引用[2]中的错误是关于OpenCV DNN的断言失败,可能与模型结构或层融合有关。这可能提示在加载模型时需要特别注意某些层的配置,或者需要确保使用的OpenCV版本支持相关操作。用户提到OpenCV4.10.0出现错误,可能需要检查是否有已知的问题,或者是否模型转换时有特殊步骤。 接下来,我需要参考现有的YOLOv8在C++中使用OpenCV DNN的实现,例如引用[1]中的项目,它使用ONNX Runtime和OpenCV进行检测和实例分割。或许可以借鉴其模型加载和预处理步骤,但需要调整输出解析部分以适应OBB的输出。 步骤可能包括: 1. 导出YOLOv8OBB模型为ONNX格式,确保输出层包含旋转框的信息。 2. 在C++中使用OpenCV加载ONNX模型。 3. 预处理输入图像(调整大小,归一化,转换为blob)。 4. 前向传播获取输出。 5. 解析输出,提取旋转框的坐标、角度、类别和置信度。 6. 后处理,如非极大值抑制(NMS),考虑旋转框的交并比计算。 需要注意的问题包括:模型输出层的正确解析,旋转框的表示方式(如中心点、长宽、角度,或者四个角点),以及NMS的实现是否支持旋转框。OpenCV自带的NMS可能只支持轴对齐的矩形框,因此可能需要自定义NMS方法。 此外,用户可能需要处理模型的输入尺寸,例如640x640,以及如何将检测结果映射回原图坐标。 现在,我需要整理这些步骤,写出示例代码的框架,并确保关键部分如模型加载、预处理、输出解析正确。同时,要提醒用户可能的陷阱,比如模型导出时的配置,OpenCV版本兼容性,以及旋转框NMS的实现。</think>在C++中使用YOLOv8OBB与OpenCV DNN实现旋转目标检测,需注意模型格式转换、输出解析和旋转框处理。以下是分步说明及示例代码: --- ### 一、实现步骤 1. **模型准备** 使用Ultralytics官方工具将YOLOv8OBB模型导出为ONNX格式: ```bash yolo export model=yolov8n-obb.pt format=onnx imgsz=640 ``` 确保导出时包含旋转框参数(角度或四角坐标)[^3]。 2. **OpenCV环境配置** 需使用支持ONNX解析的OpenCV版本(建议4.5.4+),避免出现层融合错误[^2]。 3. **输出解析逻辑** YOLOv8OBB输出维度为$[batch, num_anchors, 6+num_classes]$,其中6对应$(cx, cy, w, h, angle, conf)$。 --- ### 二、C++代码示例 ```cpp #include <opencv2/opencv.hpp> #include <opencv2/dnn.hpp> #include <iostream> using namespace cv; using namespace dnn; struct OBBResult { cv::RotatedRect box; int class_id; float confidence; }; std::vector<OBBResult> parseOBBOutput(const Mat& output, float conf_thresh = 0.5) { std::vector<OBBResult> results; const int dimensions = 6 + 80; // 假设80类目标检测 const int rows = output.size[2]; for (int i = 0; i < rows; ++i) { const float* data = output.ptr<float>(0, i); float confidence = data[4]; if (confidence < conf_thresh) continue; // 解析旋转框参数 float cx = data[0], cy = data[1]; float w = data[2], h = data[3]; float angle = data[4] * CV_PI / 180; // 角度转弧度 // 获取类别 Mat scores = Mat(1, 80, CV_32F, (void*)(data + 5)); Point class_id_point; double max_score; minMaxLoc(scores, 0, &max_score, 0, &class_id_point); if (max_score > 0.5) { OBBResult res; res.box = RotatedRect(Point2f(cx, cy), Size2f(w, h), angle); res.class_id = class_id_point.x; res.confidence = confidence * max_score; results.push_back(res); } } return results; } int main() { // 加载模型 Net net = readNetFromONNX("yolov8n-obb.onnx"); net.setPreferableBackend(DNN_BACKEND_OPENCV); net.setPreferableTarget(DNN_TARGET_CPU); // 图像预处理 Mat img = imread("test.jpg"); Mat blob; blobFromImage(img, blob, 1/255.0, Size(640, 640), Scalar(), true, false); // 推理 net.setInput(blob); Mat output = net.forward(); // 解析输出 auto detections = parseOBBOutput(output); // 绘制结果(需自定义旋转框绘制函数) for (const auto& det : detections) { Point2f vertices[4]; det.box.points(vertices); for (int j = 0; j < 4; j++) line(img, vertices[j], vertices[(j+1)%4], Scalar(0,255,0), 2); } imshow("Result", img); waitKey(0); return 0; } ``` --- ### 三、关键问题说明 1. **旋转框NMS处理** OpenCV自带的NMSBoxes仅支持轴对齐矩形,需改用旋转框IoU计算: ```cpp // 自定义旋转框IoU计算(可使用opencv_contrib中的rotatedRectangleIntersection) ``` 2. **坐标映射** 输出坐标基于640x640输入,需根据原始图像尺寸进行反归一化: ```cpp cx = cx * (orig_width / 640.0); cy = cy * (orig_height / 640.0); ``` 3. **性能优化** 使用OpenVINO加速推理: ```cpp net.setPreferableBackend(DNN_BACKEND_INFERENCE_ENGINE); ``` ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值