YOLOV5(二):将pt转为onnx模型并用opencv部署

本文详细介绍了如何将YOLOV5模型转换为ONNX格式,并在OpenCV环境中进行部署。首先,针对NVIDIA-TensorRT安装失败的问题,提供了从源码安装的步骤,包括安装CUDNN、CUDA Toolkit以及设置环境变量。接着,利用YOLOV5的export.py脚本将.pt模型转换为.onnx,特别提到在OpenCV 4.70环境下,需要设置opset参数为12。最后,文章讨论了模型转换后的测试过程,包括解决ONNXRuntime报错和使用OpenCV的dnn模块进行目标检测的注意事项。

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

将模型转为onnx、RT等用于部署

yolov5s 6.0自带export.py程序可将.pt转为.onnx等,只需配置需要的环境即可。

1. 安装环境

activate pytorch
pip install -r requirements.txt coremltools onnx onnx-simplifier onnxruntime openvino-dev tensorflow-cpu  # CPU
pip install -r requirements.txt coremltools onnx onnx-simplifier onnxruntime-gpu openvino-dev tensorflow  # GPU

报错:NVIDIA-tensorrt安装失败!
解决:从源码安装TensorRt:
①安装CUDNN和CudaToolKit等GPU配置
②从官网下载需要的rt版本:https://developer.nvidia.com/nvidia-tensorrt-8x-download
在这里插入图片描述在这里插入图片描述
③解压后,将lib文件夹添加到环境变量path中
在这里插入图片描述
④将lib中全部文件复制到CUDA的lib\x64文件夹下,并将该x64文件夹路径添加到环境变量path中
在这里插入图片描述
⑤安装RT依赖:

activate pytorch
pip install E:\Anaconda3.5\TensorRT-8.6.1.6.Windows10.x86_64.cuda-11.8\TensorRT-8.6.1.6\python\tensorrt-8.6.1-cp39-none-win_amd64.whl

安装完成后会出现successfully的字样,到这里tensorrt已经安装结束。

2. pt转onnx

yolov5自带的export.py可进行转换。yolov5 v6.0版本以后的支持opencv,不需要更改网络层。
命令行启动:

python export.py --weights yolov5s.pt --include onnx --opset 12

程序启动修改处:
在这里插入图片描述
*** opset参数与protobuf有关,当前使用的opencv470,需要设定opset参数为12时才能识别对应的onnx文件。按F5启动程序后,会在原pt文件夹下生成同名的onnx文件。***
测试①:
用yolov5自带的detect.py检测生成的onnx文件,若报错:“onnxruntime::Model::Model Unknown model file format version“
解决:降低onnxruntime版本:

pip install onnxruntime-gpu==1.14 -i  https://pypi.tuna.tsinghua.edu.cn/simple

测试②:
重新配置C++、opencv4.7环境,用opencv的dnn接口读取onnx并检测目标。
:1.用断点调试,测试net读取模型(net = readNet(netPath))能否成功,也有readNetFromONNX等。
2.注意绘图时的边界问题。
3.修改类别、图像路径、权重路径。

//main:

#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <highgui/highgui_c.h>
#include <iostream>
#include "yolov5.h"
using namespace cv;
using namespace cv::dnn;
using namespace std;
int main()
{
    string img_path = "F:/vscode/006.jpg";
    string model_path = "F:/Object Detect/network/yolov5master/weights/pt/best.onnx";

    // int num_devices = cv::cuda::getCudaEnabledDeviceCount();
    // if (num_devices <= 0) {
    // 	cerr << "There is no cuda." << endl;
    // 	return -1;
    // }
    // else {
    // 	cout << num_devices << endl;
    // }

    Yolov5 test;
    Net net;
    if (test.readModel(net, model_path, false)) {
        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);
    resize(img,img,Size(480,640));

    if (test.Detect(img, net, result)) {
        test.drawPred(img, result, color);

    }
    else {
        cout << "Detect Failed!"<<endl;
    }

    system("pause");
    return 0;
}

//yolov5.h:

#pragma once
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace dnn;
using namespace std;
#define YOLO_P6 false //是否使用P6模型

struct Output {
	int id;             //结果类别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);

private:

	void LetterBox(const cv::Mat& image, cv::Mat& outImage,
		cv::Vec4d& params, //[ratio_x,ratio_y,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));

#if(defined YOLO_P6 && YOLO_P6==true)
	const int _netWidth = 1280;  //ONNX图片输入宽度
	const int _netHeight = 1280; //ONNX图片输入高度
#else

	const int _netWidth = 640;   //ONNX图片输入宽度
	const int _netHeight = 640;  //ONNX图片输入高度
#endif // YOLO_P6

	float _classThreshold = 0.25;
	float _nmsThreshold = 0.45;
public:
	std::vector<std::string> _className = {"fire"};
	// 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" };
};

//yolov5.cpp:

#include "yolov5.h"
#include<iostream>
#include <cassert>
#include <time.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)
{
	if (false) {
		int maxLen = MAX(image.rows, image.cols);
		outImage = Mat::zeros(Size(maxLen, maxLen), CV_8UC3);
		image.copyTo(outImage(Rect(0, 0, image.cols, image.rows)));
		params[0] = 1;
		params[1] = 1;
		params[3] = 0;
		params[2] = 0;
	}
	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);
#if CV_VERSION_MAJOR==4 &&CV_VERSION_MINOR==7&&CV_VERSION_REVISION==0
		net.enableWinograd(false);  //bug of opencv4.7.x in AVX only platform ,https://github.com/opencv/opencv/pull/23112 and https://github.com/opencv/opencv/issues/23080 
		//net.enableWinograd(true);		//If your CPU supports AVX2, you can set it true to speed up
#endif
	}
	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);
	//如果在其他设置没有问题的情况下但是结果偏差很大,可以尝试下用下面两句语句
	//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(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矩形框
	int net_width = _className.size() + 5;  //输出的网络宽度是类别数+5
	int net_out_width = netOutputImg[0].size[2];
	//assert(net_out_width == net_width, "Error Wrong number of _className");  //模型类别数目不对
	float* pdata = (float*)netOutputImg[0].data;
	int net_height = netOutputImg[0].size[1];
	for (int r = 0; r < net_height; ++r) {  
		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) {
				//rect [x,y,w,h]
				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);

		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("1", img);
	//imwrite("out.bmp", img);
	waitKey(0);
	//destroyAllWindows();
}

效果:
在这里插入图片描述

<think>嗯,用户遇到了ONNX动态输入模型换到TensorRT失败的问题。我需要先理解ONNX和TensorRT的关系。根据引用[1],ONNX-TensorRT项目主要是为了给ONNX模型提供TensorRT的支持,而TensorRT是NVIDIA的推理库,用来加速模型。动态输入可能是指输入形状不固定,这在处理不同尺寸的输入时很常见,比如图像或文本处理。 用户的问题可能出在动态维度上。TensorRT在换时需要明确的形状信息,而动态输入可能会导致换失败。引用[2]提到使用trtexec工具进行换,但可能需要额外的参数来指定动态维度。例如,用--minShapes、--optShapes和--maxShapes来定义输入的最小、最优和最大形状。这样TensorRT才能为动态输入生成优化引擎。 另外,引用[3]中提到了使用ONNX runtime来验证模型是否正确,这一步可能在换前也很重要。用户可能需要检查ONNX模型是否已经正确简化(比如使用onnx-simplifier),动态维度是否正确标记。如果ONNX模型本身存在问题,比如某些操作不被TensorRT支持,换也会失败。比如,某些自定义操作或动态控制流可能不被支持,需要替换为兼容的层。 还有,用户可能需要明确指定动态维度。在导出ONNX模型时,使用dynamic_axes参数来定义哪些维度是动态的。例如,在PyTorch中使用torch.onnx.export时设置dynamic_axes,确保导出的模型正确标记了动态维度。换时,trtexec需要这些形状参数来构建引擎。 此外,日志分析也很关键。启用verbose输出(如添加--verbose参数)可以帮助查看换过程中的具体错误,比如不支持的层或形状不匹配。用户需要根据错误信息调整模型结构或换参数。 可能还需要更新软件版本,比如确保ONNX、TensorRT、ONNX-TensorRT的版本兼容。有时新版本修复了旧版本的问题,或者支持更多的操作符。 总结步骤应该是:检查动态输入定义是否正确,使用trtexec指定动态形状,验证ONNX模型,处理不支持的算子,检查版本兼容性,查看详细日志定位问题。然后根据具体情况调整模型换参数。</think>### ONNX动态输入模型换至TensorRT失败的解决方案 #### 1. **明确动态输入的定义与限制** 在ONNX模型中,动态输入通常通过`dynamic_axes`参数定义,例如在PyTorch导出模型时指定可变维度。TensorRT对动态输入的支持有限,需满足以下条件: - 动态维度需在换时通过`--minShapes`、`--optShapes`、`--maxShapes`明确指定[^2]。 - 动态维度仅支持批量大小(batch size)或序列长度(sequence length),某些操作(如Reshape)可能因动态输入导致形状推导失败[^1]。 #### 2. **使用trtexec显式指定动态形状** 通过TensorRT命令行工具`trtexec`,显式定义输入形状的上下限: ```bash trtexec --onnx=model.onnx --saveEngine=model.trt \ --minShapes=input:1x3x224x224 \ --optShapes=input:4x3x224x224 \ --maxShapes=input:8x3x224x224 ``` 此命令为输入名为`input`的张量指定了最小、最优和最大形状,帮助TensorRT生成适配动态输入的引擎[^2]。 #### 3. **验证ONNX模型兼容性** 在换前,使用以下方法确保ONNX模型符合TensorRT要求: - **简化模型**:使用`onnx-simplifier`优化模型结构: ```bash python -m onnxsim input_model.onnx output_model.onnx ``` 这可以消除冗余操作(如Identity节点)并合并常量[^3]。 - **检查算子支持**:通过`polygraphy`工具验证模型算子是否被TensorRT支持: ```bash polygraphy inspect model model.onnx --mode=op ``` 若存在不支持的操作(如动态切片或循环结构),需替换为等效的静态实现。 #### 4. **处理动态维度相关的常见错误** - **错误示例**:`INVALID_ARGUMENT: getPluginCreator could not find plugin ... version 1` - **原因**:动态输入导致某些层无法推导形状。 - **解决方案**:在导出ONNX模型时,显式标记动态维度: ```python # PyTorch导出ONNX示例 torch.onnx.export(model, (dummy_input,), "model.onnx", dynamic_axes={"input": {0: "batch_size"}}) ``` 换时在`trtexec`中对应指定动态轴的范围[^3]。 #### 5. **版本兼容性检查** - **TensorRT与ONNX版本匹配**:确保`TensorRT`版本与`onnx-graphsurgeon`、`onnxruntime`等组件兼容。例如: - TensorRT 8.x需搭配ONNX 1.9+。 - 使用`trtexec --version`和`onnx.__version__`确认版本。 #### 6. **调试与日志分析** 启用`--verbose`参数获取详细换日志: ```bash trtexec --onnx=model.onnx --verbose > log.txt 2>&1 ``` 在日志中搜索`ERROR`或`WARNING`,例如: - **动态Reshape错误**:需固定输出形状或使用`IShuffleLayer`显式定义。 - **不支持的算子**:替换为TensorRT支持的等效实现(如自定义插件)。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值