c++版使用opencv中的dnn模块部署yolov5

c++版使用opencv中的dnn模块部署yolov5

1使用python demo验证模型是否能正常推理

1.1 开发环境

win11、opencv4.7、vs2022

1.2 模型准备

使用的yolov5-6.1版本,调用export.py导出onnx模型。注:一定要开启简化–simplify,不然使用dnn模块运行时会报错。

1.3 python demo测试代码

验证导出的onnx格式的模型是否能正常推理。

import time
import cv2
import numpy as np
import argparse
class Yolov5():
    def __init__(self,model_path,class_file_path,obj_thres,conf_thres,nms_thres):
        with open(class_file_path,'rt') as f:
            self.classes=f.read().rstrip('\n').split('\n')
        self.num_classes=len(self.classes)
        self.net=cv2.dnn.readNet(model_path)
        self._inputNames=''
        self.obj_thres=obj_thres
        self.conf_thres=conf_thres
        self.nms_thres=nms_thres

    def letterbox(self,image, resize_shape=640, pad=True, stride=32, color=(114, 114, 114)):
        shape = image.shape[:2]
        if isinstance(resize_shape, int):  # resize_shape=640和resize_shape=(640,640)都可以
            resize_shape = (resize_shape, resize_shape)
        r = min(resize_shape[0] / shape[0], resize_shape[1] / shape[1])  # 比例
        ratio = r, r
        new_shape = int(round(shape[1] * r)), int(round(shape[0] * r))
        dw, dh = resize_shape[1] - new_shape[0], resize_shape[0] - new_shape[1]
        # 是否填充灰条 True表示是
        if not pad:
            dw, dh = np.mod(dw, stride), np.mod(dh, stride)
        dw /= 2
        dh /= 2
        if shape[::-1] != new_shape:
            image = cv2.resize(image, new_shape, interpolation=cv2.INTER_LINEAR)
        top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
        left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
        image = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
        return image, ratio, (dw, dh)

    #归一化处理
    def pre_process(self,image):
        # image=cv2.imread(image_path)
        image=cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
        image=image.astype(np.float32)/255.0
        return image
    #非极大抑制
    def pynms(self,dets,nms_thres):
        x1 = dets[:, 0]
        y1 = dets[:, 1]
        x2 = dets[:, 2]
        y2 = dets[:, 3]
        areas = (y2 - y1 + 1) * (x2 - x1 + 1)
        scores = dets[:, 4]
        keep = []
        index = scores.argsort()[::-1]  # 置信度从大到小排序(下标)

        while index.size > 0:
            i = index[0]
            keep.append(i)

            x11 = np.maximum(x1[i], x1[index[1:]])  # 计算相交面积
            y11 = np.maximum(y1[i], y1[index[1:]])
            x22 = np.minimum(x2[i], x2[index[1:]])
            y22 = np.minimum(y2[i], y2[index[1:]])

            w = np.maximum(0, x22 - x11 + 1)  # 当两个框不想交时x22 - x11或y22 - y11 为负数,
            # 两框不相交时把相交面积置0
            h = np.maximum(0, y22 - y11 + 1)  #

            overlaps = w * h
            ious = overlaps / (areas[i] + areas[index[1:]] - overlaps)  # 计算IOU

            idx = np.where(ious <= nms_thres)[0]  # IOU小于thresh的框保留下来
            index = index[idx + 1]  # 下标以1开始
            # print(index)

        return keep
    #后处理部分,还原letterbox的坐标
    def post_process(self,outputs,ratio,padding_size,obj_thres,conf_thres,nms_thres):

        outputs=np.squeeze(outputs)
        box_confs=[]
        for output in outputs:
            if output[4]>=obj_thres:
                scores=output[5:]
                class_index=np.argmax(scores)
                confidence=output[4]*scores[class_index]
                if confidence>conf_thres:
                    x_center=int((output[0]-padding_size[0])/ratio[0])
                    y_center=int((output[1]-padding_size[1])/ratio[1])
                    width=int(output[2]/ratio[0])
                    height=int(output[3]/ratio[1])
                    left=int(x_center-width*0.5)
                    top=int(y_center-height*0.5)
                    right=left+width
                    bottom=top+height
                    box_conf=[left,top,right,bottom,confidence,class_index]
                    box_confs.append(box_conf)

        out_box_confs=[]
        box_confs=np.array(box_confs)
        # print(box_confs.shape)
        nms_index=self.pynms(box_confs,nms_thres)
        # print("nms_index",nms_index)
        for k in nms_index:
            out_box_confs.append(box_confs[k])
        # print(out_box_confs)

        return np.array(out_box_confs)

    def draw(self,image,box_conf_score):
        boxes = box_conf_score[..., :4].astype(np.int32)  # 取整方便画框
        scores = box_conf_score[..., 4]
        classes = box_conf_score[..., 5].astype(np.int32)  # 下标取整

        for box, score, cl in zip(boxes, scores, classes):
            top, left, right, bottom = box
            # print('class: {}, score: {}'.format(self.classes[cl], score))
            # print('box coordinate left,top,right,down: [{}, {}, {}, {}]'.format(top, left, right, bottom))

            cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
            cv2.putText(image, '{0} {1:.2f}'.format(self.classes[cl], score),
                        (top, left),
                        cv2.FONT_HERSHEY_SIMPLEX,
                        0.6, (0, 0, 255), 2)
    def detect(self,image):
        input,ratio,pading_size=self.letterbox(image)
        input=self.pre_process(input)
        blob = cv2.dnn.blobFromImage(input, scalefactor=1, swapRB=True)
        self.net.setInput(blob, self._inputNames)
        outs = self.net.forward(self.net.getUnconnectedOutLayersNames())[-1].squeeze(axis=0)
        out_box_confs=self.post_process(outs,ratio,pading_size,self.obj_thres,self.conf_thres,self.nms_thres)
        model.draw(image,out_box_confs)
        return image

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--imagepath', type=str, default='./image/bus.jpg', help="图片路径")
    parser.add_argument('--modelpath', type=str, default='./model/sim_yolov5s.onnx')
    parser.add_argument('--class_file', type=str, default='./model/class.names', help="标签类别")
    parser.add_argument('--obj_thres', default=0.3, type=float, help='obj confidence')
    parser.add_argument('--conf_thres', default=0.3, type=float, help='conf score')
    parser.add_argument('--nms_thres', default=0.5, type=float, help='nms iou thresh')
    args = parser.parse_args()

    model=Yolov5(args.modelpath,args.class_file,args.obj_thres,args.conf_thres,args.nms_thres)
    #视频推理
    cap=cv2.VideoCapture(0)
    if not cap.isOpened():
        print("Error opening video stream")
        exit()
    frame_count = 0
    stat_time=time.time()
    pre_time=stat_time
    fps=0
    while (cap.isOpened()):
        ret, frame = cap.read()
        if ret == True:

            res_image = model.detect(frame)

            frame_count += 1
            current_time = time.time()
            if (current_time - pre_time) >= 1:
                fps = frame_count / (current_time - pre_time)
                print(f"估算的每秒帧数: {fps:.2f}")
                frame_count = 0
                pre_time = current_time

            cv2.putText(res_image, 'Fps: {}'.format(round(fps)), (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 0, 0), 2,
                        cv2.LINE_AA)
            # 显示结果帧
            cv2.imshow('Frame', res_image)
            # 等待键盘输入,如果按下'q'键则退出循环
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        else:
            break
    cap.release()
    cv2.destroyAllWindows()

    # model=Yolov5(args.modelpath,args.class_file,args.obj_thres,args.conf_thres,args.nms_thres)
    # image=cv2.imread(args.imagepath)
    # res_image = model.detect(image)
    # cv2.imshow("res", res_image)
    # cv2.waitKey(0)

2.使用c++完成模型部署

2.1 VS2022配置opencv

(1) opencv下载地址opecv,根据自己的操作系统下载安装。
在这里插入图片描述
(2) 本文使用win11系统,双击下载下来的exe文件,修改安装路径(一般安装在D盘),其他保持默认直到完成安装。
在这里插入图片描述
(3)配置系统环境变量,添加D:\opencv\opencv\build\x64\vc16\bin和D:\opencv\opencv\build\x64\vc16\lib(改成自己的opencv安装路径)
在这里插入图片描述
(4)启动VS2022创建一个c++空项目,创建yolo.h、yolo.cpp、main.cpp。
在这里插入图片描述
(5)配置vs2022项目属性。
在这里插入图片描述
(6)配置包含目录,一般为include目录路径
在这里插入图片描述
(7)配置库目录,一般为动态库的目录路径
在这里插入图片描述
(8)配置链接器,将OpenCV中lib库里的opencv_world470d.lib文件名复制到附加依赖项中。(要进行debug要选择带d的lib库,不然会报错)
在这里插入图片描述

2.2c++模型推理代码

头文件yolo.h
#pragma once
#include<iostream>
#include<opencv2/opencv.hpp>

struct Output {
	int id;             //目标类别下表
	float confidence;   //结果置信度分数
	cv::Rect box;       //矩形框坐标
};

class Yolov5 {
public:
	Yolov5() {}
	~Yolov5() {}
	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);

	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" };

private:

	void LetterBox(const cv::Mat& image, cv::Mat& outImage,
		cv::Vec4d& params, //[ratiox,ratioy,dw,dh]
		const cv::Size& newShape = cv::Size(640, 640),
		bool autoShape = false,
		bool scaleFill = false,
		bool scaleUp = true,
		int stride = 32,
		const cv::Scalar& color = cv::Scalar(114, 114, 114));

	const int _netWidth = 640;   
	const int _netHeight = 640; 
	float _classThreshold = 0.25;
	float _nmsThreshold = 0.45;
};
源文件yolov5.cpp
	#include"yolo1.h"
using namespace std;
using namespace cv;
using namespace cv::dnn;

void Yolov5::LetterBox(const cv::Mat& image, cv::Mat& outImage, cv::Vec4d& params, const cv::Size& newShape,
	bool autoShape, bool scaleFill, bool scaleUp, int stride, const cv::Scalar& color)
{
	cv::Size shape = image.size();
	float r = std::min((float)newShape.height / (float)shape.height,
					(float)newShape.width / (float)shape.width);
	if (!scaleUp)
		r = std::min(r, 1.0f);

	float ratio[2]{ r, r };
	int new_un_pad[2] = { (int)std::round((float)shape.width * r),(int)std::round((float)shape.height * r) };

	auto dw = (float)(newShape.width - new_un_pad[0]);
	auto dh = (float)(newShape.height - new_un_pad[1]);

	if (autoShape)
	{
		dw = (float)((int)dw % stride);
		dh = (float)((int)dh % stride);
	}
	else if (scaleFill)
	{
		dw = 0.0f;
		dh = 0.0f;
		new_un_pad[0] = newShape.width;
		new_un_pad[1] = newShape.height;
		ratio[0] = (float)newShape.width / (float)shape.width;
		ratio[1] = (float)newShape.height / (float)shape.height;
	}

	dw /= 2.0f;
	dh /= 2.0f;

	if (shape.width != new_un_pad[0] && shape.height != new_un_pad[1])
	{
		cv::resize(image, outImage, cv::Size(new_un_pad[0], new_un_pad[1]));
	}
	else {
		outImage = image.clone();
	}

	int top = int(std::round(dh - 0.1f));
	int bottom = int(std::round(dh + 0.1f));
	int left = int(std::round(dw - 0.1f));
	int right = int(std::round(dw + 0.1f));
	params[0] = ratio[0];
	params[1] = ratio[1];
	params[2] = left;
	params[3] = top;
	cv::copyMakeBorder(outImage, outImage, top, bottom, left, right, cv::BORDER_CONSTANT, color);
}



bool Yolov5::readModel(Net& net, string& netPath, bool isCuda = false) {
	try {

		net = readNet(netPath);
		net.enableWinograd(false);  //目前opencv4.7.0的版本有问题(https://github.com/opencv/opencv/issues/23080) 
		//如果你的CPU不支持AVX2指令集,则需要在net.forward() 前面加上net.enableWinograd(false);来关闭Winograd加速
	}
	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 Yolov5::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();
	Vec4d params;
	LetterBox(SrcImg, netInputImg, params, cv::Size(_netWidth, _netHeight));
	//图像归一化处理
	blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(_netWidth, _netHeight), cv::Scalar(0, 0, 0), true, false);
	
	net.setInput(blob);
	std::vector<cv::Mat> netOutputImg;
	std::vector<cv::String> names=net.getUnconnectedOutLayersNames();	//输出节点name={"345","403", "461","output" }
	//cout << "================================" << names[3] << endl;
	//模型推理,模型推理时间
	auto start = std::chrono::high_resolution_clock::now();
	net.forward(netOutputImg, names[3]);
	auto end = std::chrono::high_resolution_clock::now();
	auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
	std::cout << "单张图片模型推理时间:" << duration << "ms" << std::endl;

	std::vector<int> classIds;//目标类别数组
	std::vector<float> confidences;//结果每个目标类别对应置信度数组
	std::vector<cv::Rect> boxes;//每个目标的矩形框位置信息[x,y,w,h]
	int net_width = _className.size() + 5; //输出维度[25200,85]
	float* pdata = (float*)netOutputImg[0].data;
	for (int r = 0; r < netOutputImg[0].size[1]; ++r) { //25200
		float box_score = pdata[4]; ;//获取每一行的box框中含有某个物体的概率
		if (box_score >= _classThreshold) {
			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 = max_class_socre * box_score;
			if (max_class_socre >= _classThreshold) {
				//坐标还原
				float x = (pdata[0] - params[2]) / params[0];
				float y = (pdata[1] - params[3]) / params[1];
				float w = pdata[2] / params[0];
				float h = pdata[3] / params[1];
				int left = MAX(round(x - 0.5 * w + 0.5), 0);
				int top = MAX(round(y - 0.5 * h + 0.5), 0);
				classIds.push_back(classIdPoint.x);
				confidences.push_back(max_class_socre);
				boxes.push_back(Rect(left, top, int(w + 0.5), int(h + 0.5)));
			}
		}
		pdata += net_width;

	}

	//非最大抑制(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;
	else
		return false;
}

void Yolov5::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);
		//conf保留两位小数显示
		int conf = (int)std::round(result[i].confidence * 100);
		string label = _className[result[i].id] + ":0." + to_string(conf);

		int baseLine;
		Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
		top = max(top, labelSize.height);
		putText(img, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 1, color[result[i].id], 2);
	}
	//imshow("detect_img", img);
	//waitKey(0);
}
主程序main.cpp

#include "yolo1.h"
#include <iostream>
#include<opencv2//opencv.hpp>
#include<math.h>
#include<chrono>
#include<windows.h>

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

int main()
{
	string img_path = "bus.jpg";
	string model_path = "sim_yolov5s.onnx";
	bool is_cuda = false;
	int num_devices = cv::cuda::getCudaEnabledDeviceCount();
	if (num_devices <= 0) {
		cerr << "no cuda,use cpu" << endl;
		is_cuda = false;
		//return -1;
	}
	else {
		cout << "use cuda:" << num_devices << endl;
		is_cuda = true;
	}

	Yolov5 yolo;
	Net net;
	if (yolo.readModel(net, model_path, is_cuda)) {
		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 (yolo.Detect(img, net, result)) {
	//	yolo.drawPred(img, result, color);
	//	imshow("detect_img", img);
	//	waitKey(0);
	//}
	//else {
	//	cout << "Detect Failed!" << endl;
	//}

	//使用自己的摄像头
	cv::VideoCapture capture(0);
	if (!capture.isOpened())
	{
		std::cerr << "Error opening video file\n";
		return -1;
	}

    auto start = std::chrono::high_resolution_clock::now();
    int frame_count = 0;
    float fps = 0;

    while (true)
    {
		cv::Mat frame;
        capture.read(frame);
        if (frame.empty())
        {
            std::cout << "End of stream\n";
            break;
        }
		vector<Output> result;
		if (yolo.Detect(frame, net, result)) {
			yolo.drawPred(frame, result, color);
		}
        frame_count++;
		//计算FPS
        if (frame_count >= 30)
        {
            auto end = std::chrono::high_resolution_clock::now();
            fps = frame_count * 1000.0 / std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();

            frame_count = 0;
            start = std::chrono::high_resolution_clock::now();
        }
		//显示推理FPS
        if (fps > 0)
        {
            std::ostringstream fps_label;
            fps_label << std::fixed << std::setprecision(2);
            fps_label << "FPS: " << fps;
            std::string fps_label_str = fps_label.str();
            cv::putText(frame, fps_label_str.c_str(), cv::Point(10, 25), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
        }
        cv::imshow("output", frame);
        if (cv::waitKey(1) != -1)
        {
            capture.release();
            std::cout << "finished by user\n";
            break;
        }
    }
	system("pause");
	return 0;
}

问题总结

在自己win11电脑运行时,发现同样的模型python demo比c++ demo快很多,问题未知,望大佬指点。
python demo推理一张图片耗时150ms
c++ demo推理一张图片耗时 560ms

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
yolov5是一种目标检测算法,而OpenCV是一个开源计算机视觉库。在使用yolov5模型进行目标检测时,可以使用OpenCV来进行模型的部署。具体的步骤如下: 1. 首先,确保你已经安装了OpenCV库。你可以从官方网站下载并安装OpenCV,也可以使用包管理器来进行安装。 2. 下载yolov5模型的权重文件和配置文件。你可以从官方的yolov5仓库获取这些文件。 3. 使用OpenCVDNN模块加载yolov5模型。首先,你需要使用dnn::readNet()函数加载模型的配置文件和权重文件。然后,你可以通过设置模型的前端和后端来选择使用哪种深度学习框架和硬件加速器。最后,使用模型的forward()函数对图像进行推理,得到目标检测的结果。 4. 处理目标检测的结果。OpenCV提供了一系列的函数来解析和绘制目标检测的结果,例如获取检测到的目标的坐标、类别和置信度,并将其绘制在图像上。 下面是一个示例代码,展示了如何使用OpenCVDNN模块进行yolov5模型的部署: ``` #include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/dnn.hpp> using namespace std; using namespace cv; using namespace dnn; int main() { // 加载模型 Net net = readNet("yolov5.weights", "yolov5.cfg"); // 设置前端和后端 net.setPreferableBackend(DNN_BACKEND_DEFAULT); net.setPreferableTarget(DNN_TARGET_CPU); // 读取图像 Mat image = imread("image.jpg"); // 对图像进行预处理 Mat blob = blobFromImage(image, 1.0, Size(416, 416), Scalar(0, 0, 0), true, false); // 设置输入 net.setInput(blob); // 进行推理 Mat detection = net.forward(); // 处理目标检测结果 for (int i = 0; i < detection.rows; i++) { float confidence = detection.at<float>(i, 4); if (confidence > 0.5) { int x = detection.at<float>(i, 0) * image.cols; int y = detection.at<float>(i, 1) * image.rows; int width = detection.at<float>(i, 2) * image.cols; int height = detection.at<float>(i, 3) * image.rows; rectangle(image, Point(x, y), Point(x + width, y + height), Scalar(0, 255, 0), 2); } } // 显示结果 imshow("Detection", image); waitKey(0); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值