基于c++onnxruntime部署yolov5模型

计算机视觉


前言

最近因业务需要需要使用yolo模型来做目标检测的任务,翻了网上的各种博客没有比较完整的教程,在部署过程踩了不少坑,特别是在装英伟达驱动和cuda\cudnn版本时,甚至把ubuntu系统搞坏。于是想把训练及部署过程记录下来,并留给后来者方便使用。实验的系统是ubuntu20.04。

Yolo简介

作为一个经典且实用的目标检测模型,yolo的性能强大已无需多言,现在(2023.4.1)yolo模型已经推出到yolov8,但是推理速度上yolov5还是够快且够用,而且对各种外部硬件的适配性强,如oak相机支持的模型还在yolov5-yolov6,所以本文选择yolov5进行训练。

onnxruntime简介

onnxruntime是微软推出的一款推理框架,我们可以很方便的利用它运行一个onnx模型,而且它支持多种运行后端,包括CPU,GPU,TensorRT,DML等。onnxruntime可以说是对onnx模型最原生的支持,而且onnxruntime也有在C++上部署使用的相关库,所以我们选择onnxruntime作为我们的推理框架进行部署。

Yolov5模型训练及转换

如何训练Yolov5模型
github地址
选择中意的yolov5模型,本文使用的版本是https://github.com/ultralytics/yolov5/tree/v5.0

训练过程参考这个链接
这个博主对训练过程提供了非常详细的说明。按照这个博主的教程可以得到训练后的.pt文件,先别急着在models里export转换模型。如果在这里转换之后得到的output可能是这样的。
在这里插入图片描述
可以看到这个图片中有三个output,这样的结果是不能用的,我们需要的output是类似于[1,25200,7]这样的结果,并且这个结果必须在第一位上,通过观察可以知道25200是上图中三个输出结果中三个先验框大小和数量相乘得到的而我们训练使用的yolov5模型export的转换结果却不是这样的总和。

那怎么转换呢?
很简单,通过观察yolov5中yolo部分的代码发现
在这里插入图片描述
这个代码中的return是有问题的,返回结果不是我们想要的存在类似与[1,25200,7]的结果,而是详细细分的。这里我们最好不要改动yolo部分的代码,这可能会牵连其他部分从而产生错误。我们直接使用https://github.com/ultralytics/yolov5 这个模型来进行转换

首先先下载这个模型,用vscode打开后找到export.py
在vscode的终端中输入

python export.py --weights yolov5s.pt --include torchscript onnx openvino engine coreml tflite ...

注意yolov5s.pt处填自己训练结果的pt文件地址。然后我们使用netron查看得到的onnx模型
在这里插入图片描述
惊奇的发现我们得到了我们想要的output!(win!)(注意最后的85只是80个labels和5个参数,自己训练的模型有自己设置的labels所以数量可能不一样)
到这里我们已经完成了yolov5模型的训练和转换,请带好自己的随身物品(onnx模型),我们要赶往下一站了

利用cmake向C++部署该onnx模型

在这一步我们可能会遇到很多难题,但是不要着急,我来带着你一步步走

1.下载onnxruntime-gpu版本

博主这里使用的是onnxruntime-gpu-1.7
在这里插入图片描述
这里选择onnxruntime-linux-x64-gpu-1.7.0.tgz
下载后解压,然后放在主目录里,我们可以看到打开文件后有include和lib两个文件夹。这个我们之后要在cmakelist里链接。

2.下载vscode并创建cmake工程

这是main函数代码,里面包含了onnxruntime的使用和推理结果的处理

#pragma comment(lib, "k4a.lib")
#include <fstream>
#include <sstream>
#include <iostream>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <k4a/k4a.h>
#include <k4arecord/record.h>
#include <k4arecord/playback.h>
#include <k4a/k4a.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <opencv2/opencv.hpp>

#include <cstdlib>
#include <omp.h>
#include <opencv2/highgui/highgui_c.h>
#include "opencv2/imgproc/imgproc_c.h"
#include<opencv2/imgproc/types_c.h>

//onnxruntime
// #include <core/session/onnxruntime_cxx_api.h>
// #include <core/providers/cuda/cuda_provider_factory.h>
// #include <core/session/onnxruntime_c_api.h>
// #include <core/providers/tensorrt/tensorrt_provider_factory.h>
#include <onnxruntime_cxx_api.h>
#include <onnxruntime_c_api.h>
#include <cuda_provider_factory.h>
// 命名空间
using namespace std;
using namespace cv;
using namespace Ort;

// 自定义配置结构
struct Configuration
{
	public: 
	float confThreshold; // Confidence threshold置信度阈值
	float nmsThreshold;  // Non-maximum suppression threshold非最大抑制阈值
	float objThreshold;  //Object Confidence threshold对象置信度阈值
	string modelpath;
};

// 定义BoxInfo结构类型
typedef struct BoxInfo
{
	float x1;
	float y1;
	float x2;
	float y2;
	float score;
	int label;
} BoxInfo;

// int endsWith(string s, string sub) {
// 	return s.rfind(sub) == (s.length() - sub.length()) ? 1 : 0;
// }

// const float anchors_640[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} };

// const float anchors_1280[4][6] = { {19, 27, 44, 40, 38, 94},{96, 68, 86, 152, 180, 137},{140, 301, 303, 264, 238, 542},
// 					   {436, 615, 739, 380, 925, 792} };

class YOLOv5
{
public:
	YOLOv5(Configuration config);
	void detect(Mat& frame);
private:
	float confThreshold;
	float nmsThreshold;
	float objThreshold;
	int inpWidth;
	int inpHeight;
	int nout;
	int num_proposal;
	int num_classes;
	string classes[1] = {"tower"};
// string classes[80] = {"person", "bicycle", "car", "motorbike", "aeroplane", "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", "sofa", "pottedplant",
// 							"bed", "diningtable", "toilet", "tvmonitor", "laptop", "mouse",
// 							"remote", "keyboard", "cell phone", "microwave", "oven", "toaster",
// 							"sink", "refrigerator", "book", "clock", "vase", "scissors",
// 							"teddy bear", "hair drier", "toothbrush"};


	const bool keep_ratio = true;
	vector<float> input_image_;		// 输入图片
	void normalize_(Mat img);		// 归一化函数
	void nms(vector<BoxInfo>& input_boxes);  
	Mat resize_image(Mat srcimg, int *newh, int *neww, int *top, int *left);

	Env env = Env(ORT_LOGGING_LEVEL_ERROR, "yolov5-6.1"); // 初始化环境
	Session *ort_session = nullptr;    // 初始化Session指针选项
	SessionOptions sessionOptions = SessionOptions();  //初始化Session对象
	//SessionOptions sessionOptions;
	vector<char*> input_names;  // 定义一个字符指针vector
	vector<char*> output_names; // 定义一个字符指针vector
	vector<vector<int64_t>> input_node_dims; // >=1 outputs  ,二维vector
	vector<vector<int64_t>> output_node_dims; // >=1 outputs ,int64_t C/C++标准
};

YOLOv5::YOLOv5(Configuration config)
{
	this->confThreshold = config.confThreshold;
	this->nmsThreshold = config.nmsThreshold;
	this->objThreshold = config.objThreshold;
	this->num_classes = 1;//sizeof(this->classes)/sizeof(this->classes[0]);  // 类别数量
	this->inpHeight = 640;
	this->inpWidth = 640;
	
	string model_path = config.modelpath;
	//std::wstring widestr = std::wstring(model_path.begin(), model_path.end());  //用于UTF-16编码的字符

	//gpu, https://blog.csdn.net/weixin_44684139/article/details/123504222
	//CUDA加速开启

    //OrtSessionOptionsAppendExecutionProvider_Tensorrt(sessionOptions, 0);
    OrtSessionOptionsAppendExecutionProvider_CUDA(sessionOptions, 0);

	sessionOptions.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);  //设置图优化类型
	//ort_session = new Session(env, widestr.c_str(), sessionOptions);  // 创建会话,把模型加载到内存中
	//ort_session = new Session(env, (const ORTCHAR_T*)model_path.c_str(), sessionOptions); // 创建会话,把模型加载到内存中
	ort_session = new Session(env, (const char*)model_path.c_str(), sessionOptions);
	size_t numInputNodes = ort_session->GetInputCount();  //输入输出节点数量                         
	size_t numOutputNodes = ort_session->GetOutputCount(); 
	AllocatorWithDefaultOptions allocator;   // 配置输入输出节点内存
	for (int i = 0; i < numInputNodes; i++)
	{
		input_names.push_back(ort_session->GetInputName(i, allocator));		// 内存
		Ort::TypeInfo input_type_info = ort_session->GetInputTypeInfo(i);   // 类型
		auto input_tensor_info = input_type_info.GetTensorTypeAndShapeInfo();  // 
		auto input_dims = input_tensor_info.GetShape();    // 输入shape
		input_node_dims.push_back(input_dims);	// 保存
	}
	for (int i = 0; i < numOutputNodes; i++)
	{
		output_names.push_back(ort_session->GetOutputName(i, allocator));
		Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);
		auto output_tensor_info = output_type_info.GetTensorTypeAndShapeInfo();
		auto output_dims = output_tensor_info.GetShape();
		output_node_dims.push_back(output_dims);
	}
	this->inpHeight = input_node_dims[0][2];
	this->inpWidth = input_node_dims[0][3];
	this->nout = output_node_dims[0][2];      // 5+classes
	this->num_proposal = output_node_dims[0][1];  // pre_box

}

Mat YOLOv5::resize_image(Mat srcimg, int *newh, int *neww, int *top, int *left)  //修改图片大小并填充边界防止失真
{
	int srch = srcimg.rows, srcw = srcimg.cols;
	*newh = this->inpHeight;
	*neww = this->inpWidth;
	Mat dstimg;
	if (this->keep_ratio && srch != srcw) {
		float hw_scale = (float)srch / srcw;
		if (hw_scale > 1) {
			*newh = this->inpHeight;
			*neww = int(this->inpWidth / hw_scale);
			resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA);
			*left = int((this->inpWidth - *neww) * 0.5);
			copyMakeBorder(dstimg, dstimg, 0, 0, *left, this->inpWidth - *neww - *left, BORDER_CONSTANT, 114);
		}
		else {
			*newh = (int)this->inpHeight * hw_scale;
			*neww = this->inpWidth;
			resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA);  //等比例缩小,防止失真
			*top = (int)(this->inpHeight - *newh) * 0.5;  //上部缺失部分
			copyMakeBorder(dstimg, dstimg, *top, this->inpHeight - *newh - *top, 0, 0, BORDER_CONSTANT, 114); //上部填补top大小,下部填补剩余部分,左右不填补
		}
	}
	else {
		resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA);
	}
	return dstimg;
}

void YOLOv5::normalize_(Mat img)  //归一化
{
	//    img.convertTo(img, CV_32F);
    //cout<<"picture size"<<img.rows<<img.cols<<img.channels()<<endl;
	int row = img.rows;
	int col = img.cols;
	this->input_image_.resize(row * col * img.channels());  // vector大小

	for (int c = 0; c < 3; c++)  // bgr
	{
		for (int i = 0; i < row; i++)  // 行
		{
			for (int j = 0; j < col; j++)  // 列
			{
				float pix = img.ptr<uchar>(i)[j * 3 + 2 - c];  // Mat里的ptr函数访问任意一行像素的首地址,2-c:表示rgb
				this->input_image_[c * row * col + i * col + j] = pix / 255.0; //将每个像素块归一化后装进容器
			}
		}
	}
}

void YOLOv5::nms(vector<BoxInfo>& input_boxes)
{
	// sort(input_boxes.begin(), input_boxes.end(), [](BoxInfo a, BoxInfo b) { return a.score > b.score; }); // 降序排列
	// vector<float> vArea(input_boxes.size());
	// for (int i = 0; i < input_boxes.size(); ++i)
	// {
	// 	vArea[i] = (input_boxes[i].x2 - input_boxes[i].x1 + 1)
	// 		* (input_boxes[i].y2 - input_boxes[i].y1 + 1);
	// }
	// // 全初始化为false,用来作为记录是否保留相应索引下pre_box的标志vector
	// vector<bool> isSuppressed(input_boxes.size(), false);  
	// for (int i = 0; i < input_boxes.size(); ++i)
	// {
	// 	if (isSuppressed[i]) { continue; }
	// 	for (int j = i + 1; j < input_boxes.size(); ++j)
	// 	{
	// 		if (isSuppressed[j]) { continue; }
	// 		float xx1 = max(input_boxes[i].x1, input_boxes[j].x1);
	// 		float yy1 = max(input_boxes[i].y1, input_boxes[j].y1);
	// 		float xx2 = min(input_boxes[i].x2, input_boxes[j].x2);
	// 		float yy2 = min(input_boxes[i].y2, input_boxes[j].y2);

	// 		float w = max(0.0f, xx2 - xx1 + 1);
	// 		float h = max(0.0f, yy2 - yy1 + 1);
	// 		float inter = w * h;	// 交集
	// 		if(input_boxes[i].label == input_boxes[j].label)
	// 		{
	// 			float ovr = inter / (vArea[i] + vArea[j] - inter);  // 计算iou
	// 			if (ovr >= this->nmsThreshold)
	// 			{
	// 				isSuppressed[j] = true;
	// 			}
	// 		}	
	// 	}
	// }
	// // return post_nms;
	// int idx_t = 0;
    //    // remove_if()函数 remove_if(beg, end, op) //移除区间[beg,end)中每一个“令判断式:op(elem)获得true”的元素
	// input_boxes.erase(remove_if(input_boxes.begin(), input_boxes.end(), [&idx_t, &isSuppressed](const BoxInfo& f) { return isSuppressed[idx_t++]; }), input_boxes.end());
	// 另一种写法
	sort(input_boxes.begin(), input_boxes.end(), [](BoxInfo a, BoxInfo b) { return a.score > b.score; }); // 降序排列
	vector<bool> remove_flags(input_boxes.size(),false);
	auto iou = [](const BoxInfo& box1,const BoxInfo& box2)
	{
		float xx1 = max(box1.x1, box2.x1);
		float yy1 = max(box1.y1, box2.y1);
		float xx2 = min(box1.x2, box2.x2);
		float yy2 = min(box1.y2, box2.y2);
		// 交集
		float w = max(0.0f, xx2 - xx1 + 1);
		float h = max(0.0f, yy2 - yy1 + 1);
		float inter_area = w * h;
		// 并集
		float union_area = max(0.0f,box1.x2-box1.x1) * max(0.0f,box1.y2-box1.y1)
						   + max(0.0f,box2.x2-box2.x1) * max(0.0f,box2.y2-box2.y1) - inter_area;
		return inter_area / union_area;
	};
	for (int i = 0; i < input_boxes.size(); ++i)
	{
		if(remove_flags[i]) continue;
		for (int j = i + 1; j < input_boxes.size(); ++j)
		{
			if(remove_flags[j]) continue;
			if(input_boxes[i].label == input_boxes[j].label && iou(input_boxes[i],input_boxes[j])>=this->nmsThreshold)
			{
				remove_flags[j] = true;
			}
		}
	}
	int idx_t = 0;
    // remove_if()函数 remove_if(beg, end, op) //移除区间[beg,end)中每一个“令判断式:op(elem)获得true”的元素
	input_boxes.erase(remove_if(input_boxes.begin(), input_boxes.end(), [&idx_t, &remove_flags](const BoxInfo& f) { return remove_flags[idx_t++]; }), input_boxes.end());
}

void YOLOv5::detect(Mat& frame)
{
	int newh = 0, neww = 0, padh = 0, padw = 0;
	Mat dstimg = this->resize_image(frame, &newh, &neww, &padh, &padw);   //改大小后做padding防失真
	this->normalize_(dstimg);       //归一化
	// 定义一个输入矩阵,int64_t是下面作为输入参数时的类型
	array<int64_t, 4> input_shape_{ 1, 3, this->inpHeight, this->inpWidth };  //1,3,640,640

    //创建输入tensor
    /*
    这一行代码的作用是创建一个指向CPU内存的分配器信息对象(AllocatorInfo),用于在运行时分配和释放CPU内存。
    它调用了CreateCpu函数并传递两个参数:OrtDeviceAllocator和OrtMemTypeCPU。
    其中,OrtDeviceAllocator表示使用默认的设备分配器,OrtMemTypeCPU表示在CPU上分配内存。
    通过这个对象,我们可以在运行时为张量分配内存,并且可以保证这些内存在计算完成后被正确地释放,避免内存泄漏的问题。
    */
	auto allocator_info = MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
    //使用Ort库创建一个输入张量,其中包含了需要进行目标检测的图像数据。
	Value input_tensor_ = Value::CreateTensor<float>(allocator_info, input_image_.data(), input_image_.size(), input_shape_.data(), input_shape_.size());
	// 开始推理
	vector<Value> ort_outputs = ort_session->Run(RunOptions{ nullptr }, &input_names[0], &input_tensor_, 1, output_names.data(), output_names.size());   // 开始推理
	/generate proposals
    //cout<<"ort_outputs_size"<<ort_outputs.size()<<endl;
	vector<BoxInfo> generate_boxes;  // BoxInfo自定义的结构体
    float ratioh = (float)frame.rows / newh, ratiow = (float)frame.cols / neww;  //原图高和新高比,原图宽与新宽比

	float* pdata = ort_outputs[0].GetTensorMutableData<float>(); // GetTensorMutableData

	for(int i = 0; i < num_proposal; ++i) // 遍历所有的num_pre_boxes
	{   
		int index = i * nout;      // prob[b*num_pred_boxes*(classes+5)]  
		float obj_conf = pdata[index + 4];  // 置信度分数
        //cout<<"k"<<obj_conf<<endl;
		if (obj_conf > this->objThreshold)  // 大于阈值
		{
			int class_idx = 0;
			float max_class_socre = 0;
			for (int k = 0; k < this->num_classes; ++k)
			{
				if (pdata[k + index + 5] > max_class_socre)
				{
					max_class_socre = pdata[k + index + 5];
					class_idx = k;
				}
			}
			max_class_socre *= obj_conf;   // 最大的类别分数*置信度
			if (max_class_socre > this->confThreshold) // 再次筛选
			{ 
				//const int class_idx = classIdPoint.x;
				float cx = pdata[index];  //x
				float cy = pdata[index+1];  //y
				float w = pdata[index+2];  //w
				float h = pdata[index+3];  //h
				//cout<<cx<<cy<<w<<h<<endl;
				float xmin = (cx - padw - 0.5 * w)*ratiow;
				float ymin = (cy - padh - 0.5 * h)*ratioh;
				float xmax = (cx - padw + 0.5 * w)*ratiow;
				float ymax = (cy - padh + 0.5 * h)*ratioh;
				//cout<<xmin<<ymin<<xmax<<ymax<<endl;
				generate_boxes.push_back(BoxInfo{ xmin, ymin, xmax, ymax, max_class_socre, class_idx });
			}
		}
	}

	// Perform non maximum suppression to eliminate redundant overlapping boxes with
	// lower confidences
	nms(generate_boxes);
	for (size_t i = 0; i < generate_boxes.size(); ++i)
	{
		int xmin = int(generate_boxes[i].x1);
		int ymin = int(generate_boxes[i].y1);
		rectangle(frame, Point(xmin, ymin), Point(int(generate_boxes[i].x2), int(generate_boxes[i].y2)), Scalar(0, 0, 255), 2);
		string label = format("%.2f", generate_boxes[i].score);
		label = this->classes[generate_boxes[i].label] + ":" + label;
		putText(frame, label, Point(xmin, ymin - 5), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 255, 0), 1);
	}
	}


int main(int argc,char *argv[])
{

	/*

		找到并打开 Azure Kinect 设备
	*/
	//发现已连接的设备数

	const uint32_t device_count = k4a::device::get_installed_count();
	if (0 == device_count) {
		cout << "Error: no K4A devices found. " << endl;
		return -1;
	}
	else {
		std::cout << "Found " << device_count << " connected devices. " << std::endl;
		if (1 != device_count)// 超过1个设备,也输出错误信息。
		{
			std::cout << "Error: more than one K4A devices found. " << std::endl;
			return -1;
		}
		else// 该示例代码仅限对1个设备操作
		{
			std::cout << "Done: found 1 K4A device. " << std::endl;
		}
	}
	//打开(默认)设备
	k4a::device device = k4a::device::open(K4A_DEVICE_DEFAULT);
	std::cout << "Done: open device. " << std::endl;

	/*
		检索并保存 Azure Kinect 图像数据
	*/
	//配置并启动设备
	k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL;
	config.camera_fps = K4A_FRAMES_PER_SECOND_15;
	config.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32;
	config.color_resolution = K4A_COLOR_RESOLUTION_720P;
	config.depth_mode = K4A_DEPTH_MODE_NFOV_UNBINNED;
	//config.depth_mode = K4A_DEPTH_MODE_WFOV_2X2BINNED;
	config.synchronized_images_only = true; // ensures that depth and color images are both available in the capture
	device.start_cameras(&config);

	k4a::image rgbImage;
	Mat cv_rgbImage_no_alpha;
	Mat cv_rgbImage_with_alpha;
    // Camera capture and application specific code would go here
	k4a::capture capture;	



	Configuration yolo_nets = { 0.3, 0.5, 0.3,"/home/xue/picture/modle/best.onnx" }; //初始化属性
	YOLOv5 yolo_model(yolo_nets);
	clock_t startTime,endTime; //计算时间
	while(true)
	{
		if (device.get_capture(&capture, std::chrono::milliseconds(0)))
				if (device.get_capture(&capture)) {
			//cv::Mat src;
			
			rgbImage = capture.get_color_image();
			//src = cv::Mat(rgbImage.get_height_pixels(), rgbImage.get_width_pixels(), CV_8UC4, rgbImage.get_buffer());
			cv_rgbImage_with_alpha = cv::Mat(rgbImage.get_height_pixels(), rgbImage.get_width_pixels(), CV_8UC4,
						(void *)rgbImage.get_buffer());
			cv::cvtColor(cv_rgbImage_with_alpha, cv_rgbImage_no_alpha, cv::COLOR_BGRA2BGR);
			double timeStart = (double)getTickCount();
			startTime = clock();//计时开始	
			yolo_model.detect(cv_rgbImage_no_alpha);
			endTime = clock();//计时结束
			double nTime = ((double)getTickCount() - timeStart) / getTickFrequency();
			cout << "clock_running time is:" <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
			cout << "The run time is:" << (double)clock() /CLOCKS_PER_SEC<< "s" << endl;
			cout << "getTickCount_running time :" << nTime << "sec\n" << endl;
			// static const string kWinName = "Deep learning object detection in ONNXRuntime";

			cv::namedWindow("color", CV_WINDOW_NORMAL);
			cv::imshow("color",cv_rgbImage_no_alpha);

			cv_rgbImage_with_alpha.release();
			cv_rgbImage_no_alpha.release();

			capture.reset();
			if (cv::waitKey(1) == ' ')
					{//按键采集,用户按下' ',跳出循环,结束采集
						std::cout << "----------------------------------" << std::endl;
						std::cout << "------------- closed -------------" << std::endl;
						std::cout << "----------------------------------" << std::endl;
						break;
					}

				}
		}
	
    // Shut down the camera when finished with application logic
	cv::destroyAllWindows();
	device.close();
	rgbImage.reset();
	capture.reset();
    return 0;

}

博主使用了azure相机,用opencv做到了实时采集图片并推理。
所以代码里面包含了一些k4a库的函数,可以删掉换成自己的视频流或者图片。
关于代码有很多详细的注释,可以大概读一下,包括非极大值抑制等。
下面是cmakelist的代码

# cmake needs this line

cmake_minimum_required(VERSION 3.9.1)
 
# Define project name
project(yourproject)

# Kinect DK相机
find_package(k4a REQUIRED)
# Find OpenCV
find_package(OpenCV REQUIRED)
#find_package(Threads REQUIRED)
#find_package(OpenMP REQUIRED)
# Add includes
include_directories( ${CMAKE_CURRENT_LIST_DIR} ) # 包含当前目录下我的头文件
include_directories( ${OpenCV_INCLUDE_DIRS} )
include_directories(include)

# Enable C++11
set(CMAKE_CXX_FLAGS "-std=c++11")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CUDA_STANDARD 11)   
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
 
set(ONNXRUNTIME_INCLUDE_DIRS your_include)
set(ONNXRUNTIME_LIBS your_libonnxruntime.so)

include_directories(${ONNXRUNTIME_INCLUDE_DIRS})


# Link your application with other libraries
add_executable(${PROJECT_NAME} "main.cpp")

target_include_directories(${PROJECT_NAME} PUBLIC ${ONNXRUNTIME_INCLUDE_DIRS})
target_link_libraries(onnxrun1 ${CUDA_LIBRARIES})
target_link_libraries(${PROJECT_NAME} k4a::k4a ${ONNXRUNTIME_LIBS} ${OpenCV_LIBS})

这个代码包括了opencv k4a 和onnxruntime
注意:不要使用find_package来添加onnxruntime,因为找不到,把your_include替换成你自己刚下好的onnxruntime-gpu里的include目录地址,把your_libonnxruntime.so替换成lib里的libonnxruntime.so

3.别忘了NVIDIA驱动以及CUDA和CUDNN的安装!

1.NVIDIA驱动

英伟达驱动官网可以在这里下载驱动
具体安装过程可以参考,装完一定不要忘了sudo service lightdm start开启图形化界面,nvidia版本或者图形化界面的问题都会导致ubuntu黑屏,装驱动一定要小心细心!(博主的血泪经历)

如果你之前装了NVIDIA的驱动,检查一下它的版本,ubuntu一般自带的那个驱动是不能用来跑这个的。

博主的驱动版本:nvidia-smi 515.76 (可以终端输入nvidia-smi查看,记住表格中的CUDA Version后的数字,等会下载cuda时版本号必须小于等于这个版本)
在这里插入图片描述

2.cuda和cudnn的安装

博主这里选择的版本是cuda11.2版本
参考https://blog.csdn.net/weixin_42156097/article/details/127321349 这个博文详细记录了CUDA和cudnn的安装过程,记得版本不要搞错,cudnn的版本也要对应cuda和nvidia来整。
如果你安装完了cuda和cudnn,就可以开始快乐的cmake了!

3.cmake

我们就不详细介绍cmake相关的内容了,按照上面的代码放进去后就可以直接cmake . make ./yourproject了。
然后就是快乐的结果展示时间辣!
可以看到每一帧的处理速度在0.06s左右,可以达到实时监测辣!
后续需要添加什么功能就可以自己在c++中改辣!不喜欢用python处理数据的人的福音。

总结

为了生存开启了这段部署之旅,经历了onnxruntime安装后不会cmake链接,链接后发现缺东少西,甚至连nvidia的驱动都没装就开始调用cuda的gpu加速,装nvidia驱动甚至把ubuntu搞坏。
之后还遇到cuda和cudnn版本选择上的问题,onnxruntime版本换成了18。
nvidia驱动安装后最好执行sudo apt-mark hold nvidia-driver-525版本改成你自己的。这个命令可以阻止驱动自我更新,不然如果更新后会出现两个版本的驱动,出现系统版本和自己装的驱动版本不同从而导致错误的情况。

  • 31
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 首先,您需要将Yolov5模型转换为ONNX格式。您可以使用PyTorch将模型转换为ONNX格式,然后使用ONNX Runtime C++ API加载和运行模型。 以下是一些步骤: 1. 安装PyTorch和ONNX Runtime 2. 使用PyTorch将Yolov5模型转换为ONNX格式。您可以使用以下代码: ``` import torch import torchvision # Load the model model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) # Export the model to ONNX format input_shape = (1, 3, 640, 640) torch.onnx.export(model, torch.randn(*input_shape), "yolov5s.onnx", opset_version=11) ``` 3. 在C++中加载和运行模型。您可以使用以下代码: ``` #include <iostream> #include <vector> #include <chrono> #include <opencv2/opencv.hpp> #include "onnxruntime_cxx_api.h" using namespace std; using namespace cv; using namespace std::chrono; using namespace onnxruntime; int main() { // Load the model Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "test"); Ort::Session session(env, "yolov5s.onnx", session_options); // Get input and output names auto input_names = session.GetInputNames(); auto output_names = session.GetOutputNames(); // Create input tensor Ort::AllocatorWithDefaultOptions allocator; Ort::Value input_tensor(nullptr); Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); vector<int64_t> input_shape = {1, 3, 640, 640}; input_tensor = Ort::Value::CreateTensor<float>(memory_info, reinterpret_cast<float*>(new float[input_shape[0] * input_shape[1] * input_shape[2] * input_shape[3]]), input_shape.data(), input_shape.size()); // Load image Mat image = imread("test.jpg"); cvtColor(image, image, COLOR_BGR2RGB); resize(image, image, Size(640, 640)); float* input_data = input_tensor.GetTensorMutableData<float>(); for (int i = 0; i < 640 * 640 * 3; i++) { input_data[i] = image.data[i] / 255.0; } // Run inference auto start = high_resolution_clock::now(); vector<Ort::Value> output_tensors = session.Run(output_names, &input_names[0], &input_tensor, 1); auto end = high_resolution_clock::now(); auto duration = duration_cast<milliseconds>(end - start); cout << "Inference time: " << duration.count() << " ms" << endl; // Get output tensor Ort::Value& output_tensor = output_tensors[0]; float* output_data = output_tensor.GetTensorMutableData<float>(); // Process output for (int i = 0; i < 25200; i++) { if (output_data[i * 6 + 4] > 0.5) { int x1 = output_data[i * 6 + 0] * 640; int y1 = output_data[i * 6 + 1] * 640; int x2 = output_data[i * 6 + 2] * 640; int y2 = output_data[i * 6 + 3] * 640; cout << "Object detected: " << output_data[i * 6 + 5] << " (" << x1 << ", " << y1 << ") (" << x2 << ", " << y2 << ")" << endl; } } return 0; } ``` 这个例子假设您有一张名为“test.jpg”的图像,它将被用作模型的输入。它还假设您的模型输出是一个大小为[1, 25200, 6]的张量,其中25200是预测的边界框数,6是每个边界框的属性数(左上角和右下角坐标,置信度和类别)。 请注意,这只是一个简单的例子,您需要根据您的模型和数据进行适当的修改。 ### 回答2: 在使用ONNXRuntime C部署Yolov5之前,首先需要明确的是Yolov5是目标检测算法,而ONNXRuntime C则是一个高性能的推理框架,可以用来推理基于ONNX格式的深度学习模型,这包括Yolov5。 以下是ONNXRuntime C部署Yolov5的步骤参考: 1. 转换模型:由于Yolov5模型原先是以PyTorch格式存储,因此需要将其转化为ONNX格式。具体的转换方法可以参考ONNX官方文档,或者使用现成的转换脚本,如https://github.com/jkjung-avt/yolov5_onnx/blob/main/yolov5_onnx.py。 2. 编写C程序:根据ONNXRuntime C的API,编写C语言程序实现Yolov5模型的加载和推理。可以参考ONNXRuntime官方示例中的代码模板,进行修改和调整,完成模型的加载和推理功能。关键代码如下: ```c // 运行初始化,加载模型 OrtEnv* env; OrtCreateEnv(ORT_LOGGING_LEVEL_WARNING, "test", &env); OrtSessionOptions* session_options = OrtCreateSessionOptions(); OrtSession* session; OrtCreateSession(env, model_path, session_options, &session); // 获取模型输入输出信息 OrtStatus* status; OrtTensorTypeAndShapeInfo* input_info; OrtSessionGetInputTypeInfo(session, 0, &input_info); OrtAllocator* allocator; OrtCreateDefaultAllocator(&allocator); size_t num_inputs; OrtStatus* get_num_input = OrtSessionGetInputCount(session, &num_inputs); OrtValue** input_tensor = (OrtValue**)malloc(num_inputs * sizeof(OrtValue*)); OrtStatus* input_status = OrtCreateTensorAsOrtValue(allocator, input_info, &input_tensor[0]); OrtTypeInfo* type_info; OrtStatus* output_status = OrtSessionGetOutputTypeInfo(session, 0, &type_info); // 给输入tensor赋值 float* input = input_tensor[0]->GetTensorMutableData<float>(); for (int i = 0; i < input_size; i++) { input[i] = input_data[i]; } // 运行模型,获取结果 OrtValue* output_tensor = NULL; const char* output_names[] = { output_name }; OrtStatus* run_status = OrtRun(session, NULL, input_names, &input_tensor[0], num_inputs, output_names, 1, &output_tensor); float* output = output_tensor->GetTensorMutableData<float>(); ``` 3. 编译C程序:使用C编译器,如gcc,编译C程序,并将ONNXRuntime C的库文件链接到程序中,如: ```bash gcc main.c -lonnxruntime ``` 4. 运行C程序:运行编译后的程序,并输入Yolov5需要检测的图片或视频数据,程序将输出检测结果,包括检测框、置信度和类别等信息。 需要注意的几个点: 1. ONNXRuntime C需要先安装ONNXRuntime库,并将其包含到系统路径中。 2. 在程序中需要指定Yolov5的输入尺寸和类别数等信息。 3. 在使用Yolov5推理时,需要先对输入数据进行预处理,如尺寸缩放、通道变换和数据类型转换等。 4. 在编程时,需要对ONNXRuntime C的API进行深入学习,以保证程序的正确性和稳定性。同时,还需要对Yolov5的算法和原理有一定的了解,以便进行模型的参数调整和优化。 ### 回答3: 随着深度学习的广泛应用,越来越多的框架和工具被开发出来,但由于它们之间的差异,将模型从一个框架转换到另一个框架是一项具有挑战性和耗费时间的工作。ONNX(Runtime)是一种广泛接受的中间表示,它可以使不同的框架之间的模型转换变得容易。这篇文章将介绍如何使用ONNXRuntime C++ API来部署一个YOLOv5模型。 首先,我们需要下载YOLOv5模型的权重和cfg文件,可以从Github上的YOLOv5仓库中下载。在下载完这两个文件后,我们需要用Python中的train.py将它们转换成ONNX文件,具体地,可以使用如下命令: ``` python3 train.py --weights yolov5s.pt --cfg models/yolov5s.yaml --img 640 --batch 1 --no-autoanchor --fuse python3 models/yolo.py --cfg models/yolov5s.yaml --weights yolov5s.pt --names models/coco.names ``` 这将生成名为“yolov5s.onnx”的模型文件。现在,我们可以使用ONNXRuntime C++ API加载和运行它。 首先,我们需要在C++中安装ONNXRuntime的API,可以从官方网站(https://www.onnxruntime.ai/)下载ONNXRuntime C++ API安装文件。安装完成后,我们可以开始编写C++代码来加载和运行YOLOv5模型。 我们需要使用以下头文件: ``` #include "onnxruntime_cxx_api.h" #include <opencv2/opencv.hpp> ``` 接下来,我们需要定义一些变量来存储模型信息。我们可以使用onnxruntime::Env类初始化ONNXRuntime。 ``` std::string model_path = "yolov5s.onnx"; std::shared_ptr<onnxruntime::Environment> env = onnxruntime::Environment::Create(); ``` 然后,我们需要创建一个会话,该会话将包含模型。通过onnxruntime::Session类,我们可以加载模型并返回一个会话句柄。 ``` std::shared_ptr<onnxruntime::Session> session = std::make_shared<onnxruntime::Session>(*env, model_path, nullptr); ``` 我们还需要创建一个输入模型。我们需要使用onnxruntime::Tensor类,它将用于加载输入数据。 ``` std::vector<int64_t> input_node_dims = { 1, 3, 640, 640 }; auto memory_info = onnxruntime::MemoryInfo::CreateCpu(ONNXRUNTIME_CPU, onnxruntime::DeviceAllocatorRegistrationFlags::None); auto input_tensor = onnxruntime::make_unique<onnxruntime::Tensor>(onnxruntime::DataType::Float, onnxruntime::TensorShape(input_node_dims), memory_info); ``` 现在,我们有了将图像转换为ONNX模型所需的一切。我们需要加载图像,将其尺寸调整为模型的输入大小,并使用OpenCV库将图像像素值转换为浮点数。然后我们可以将数据复制到输入_tensor中。 ``` cv::Mat img = cv::imread("test.jpg"); cv::resize(img, img, cv::Size(640, 640), cv::INTER_AREA); img.convertTo(img, CV_32FC3, 1 / 255.0); memcpy(input_tensor->MutableData<float>(), img.data, img.total() * img.elemSize()); ``` 现在,我们可以将输入_tensor传递给模型并运行。 ``` std::vector<const char*> input_node_names = { "input" }; std::vector<const char*> output_node_names = { "output" }; std::vector<OrtValue> output_tensors; OrtTensorScales scales = { nullptr, 0 }; session->Run(Ort::RunOptions{ nullptr }, input_node_names.data(), &input_tensor, input_node_dims.size(), output_node_names.data(), output_node_names.size(), output_tensors, scales); ``` 最后,我们可以使用输出张量中的数据进行后处理,以获得目标框位置和类别预测结果等信息。 这些就是使用ONNXRuntime C++ API来部署YOLOv5模型的基本步骤。当然,如果我们要获得更高的精度和更快的速度,还可以使用各种技术来优化模型和代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值