windows环境下 C++ onnxruntime框架yolov8推理

此处记录的是onnxruntime-cpu 推理,不涉及任何cuda的操作

introduction

         最近一直在考虑推理加速的问题,奈何本人在C++水平实在弱鸡, 网上的很多教程看得有点懵逼,不太适合新手,这里记录一下 从搭建windows环境环境 ,到使用onnxruntime框架yolov8推理过程。

VS环境搭建

        网上很多教程是在vs2019上实现,官网上没法直接下载,提供下载的版本是2022下载 Visual Studio Tools - 免费安装 Windows、Mac、Linux (microsoft.com),我这里也使用是2022-17.10.3, 安装时选择工作负荷如下:

下载opencvReleases - OpenCV安装(解压),本人使用的版本是4.8.0,添加环境变量,

下载onnxruntimeRelease ONNX Runtime v1.18.0 · microsoft/onnxruntime · GitHub安装(解压),本人使用的版本是1.17.1,添加环境变量

vs 新建空项目,在项目属性中添加依赖,一共要添加三处地方,包含目录, 库目录,外部依赖项,添加方法不懂可以百度,巨多教程。

相关代码

本人代码分为三个部分,infer.h , infer.cpp, main.cpp,其中源码部分做了详尽的注释,对新手友好

代码参考了如下来源

yolov8 ONNX Runtime C++ 部署_yolov8 c++ onnx-CSDN博客

ultralytics/examples/YOLOv8-ONNXRuntime-CPP/main.cpp at main · ultralytics/ultralytics · GitHub

YOLOv5-Lite/cpp_demo/ort/main.cpp at master · ppogg/YOLOv5-Lite · GitHub

infer.h

#pragma once
#include <iostream>
#include <windows.h>
#include <string>
#include <cmath>
#include <opencv2/opencv.hpp> // 来自外部依赖库
#include <onnxruntime_cxx_api.h> // 来自外部依赖库
using namespace std;


struct OutputDet {
	int id;
	float confidence;
	cv::Rect box;
};


struct Net_config
{
	string imagepath;// image path
	const ORTCHAR_T* modelpath;// model path
	float confThreshold; // Confidence threshold
	float iouThreshold; // IoU threshold
};

class V8
{
public:

	V8();  // 无参数构造函数
	void detect(Net_config config);

	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:

	int inpWidth, inpHeight, maxSide, Padwl, Padwr, Padht, Padhd;
	float ratio;



	vector<float> input_image_;
	cv::Mat letter_(cv::Mat& img);

	void normalize_(cv::Mat img);


	Ort::Session* ort_session;
	vector<const char*> input_names;
	vector<const char*> output_names;
	vector<vector<int64_t>> input_node_dims;  // >=1 outputs
	vector<vector<int64_t>> output_node_dims; // >=1 outputs
};




infer.cpp

#include <iostream>
#include <windows.h>
#include <string>
#include <cmath>
#include <opencv2/opencv.hpp>
#include <onnxruntime_cxx_api.h>

#include "infer.h" // 导入头文件,头文件定义了需要的结构体及类

using namespace std;
// 构造函数
V8::V8() {
	cout << "infer start" << endl;
}
// 图片读取
cv::Mat get_img(string imgpath) {
	cv::Mat img = cv::imread(imgpath);
	return img;
}
// 打印设置参数
void show_cfg(Net_config cfg) {
	const ORTCHAR_T* modelpath = cfg.modelpath;
	float confThreshold = cfg.confThreshold;
	float iouThreshold = cfg.iouThreshold;
	cout << "model path:" << " " << modelpath << " " << "conf Threshold:" << confThreshold << " " << "IoU Threshold:" << iouThreshold << endl;
}
// 图片数组归一化
void V8::normalize_(cv::Mat img)
{
	int row = img.rows;
	int col = img.cols;
	this->input_image_.resize(row * col * img.channels());
	for (int c = 0; c < 3; c++)
	{
		for (int i = 0; i < row; i++)
		{
			for (int j = 0; j < col; j++)
			{
				float pix = img.ptr<uchar>(i)[j * 3 + 2 - c];
				this->input_image_[c * row * col + i * col + j] = pix / 255.0;
			}
		}
	}
}
// 图片数组resize及pad
cv::Mat V8::letter_(cv::Mat& img)
{
	cv::Mat dstimg;
	this->maxSide = img.rows > img.cols ? img.rows : img.cols; //三元运算符表达式
	this->ratio = this->inpWidth / float(this->maxSide);
	int fx = int(img.cols * ratio);
	int fy = int(img.rows * ratio);
	this->Padwl = int(round((this->inpWidth - fx) * 0.5-0.1));
	this->Padwr = int(round((this->inpWidth - fx) * 0.5+0.1));
	this->Padht = int(round((this->inpHeight - fy) * 0.5-0.1));
	this->Padhd = int(round((this->inpHeight - fy) * 0.5 + 0.1));
	cv::resize(img, dstimg, cv::Size(fx, fy));
	cv::copyMakeBorder(dstimg, dstimg, Padht, Padhd, Padwl, Padwr, cv::BORDER_CONSTANT, cv::Scalar::all(127));
	return dstimg;
}

void V8::detect(Net_config config)
{
	show_cfg(config); //打印设置

	Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "your_app");

	// 配置会话选项
	Ort::SessionOptions session_options;
	session_options.SetLogId("MySession");
	session_options.SetIntraOpNumThreads(1);
	session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
	// 加载模型
	ort_session = new Ort::Session(env, config.modelpath, session_options);
	// 获取输入及输出个数
	size_t numInputNodes = ort_session->GetInputCount();
	size_t numOutputNodes = ort_session->GetOutputCount();
	Ort::AllocatorWithDefaultOptions allocator;
	for (int i = 0; i < numInputNodes; i++)
	{
		std::shared_ptr<char> inputName = std::move(ort_session->GetInputNameAllocated(i, allocator));
		this->input_names.push_back(inputName.get()); // 获取输入名字
		Ort::TypeInfo input_type_info = ort_session->GetInputTypeInfo(i);
		auto input_tensor_info = input_type_info.GetTensorTypeAndShapeInfo();
		auto input_dims = input_tensor_info.GetShape();
		this->input_node_dims.push_back(input_dims); // 获取输入维度

	}
	cout << "input_names:" << input_names[0] << endl;

	for (int i = 0; i < numOutputNodes; i++)
	{
		std::shared_ptr<char> outputName = std::move(ort_session->GetOutputNameAllocated(i, allocator));
		this->output_names.push_back(outputName.get()); //获取输出名字
		Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);
		auto output_tensor_info = output_type_info.GetTensorTypeAndShapeInfo();
		auto output_dims = output_tensor_info.GetShape();
		this->output_node_dims.push_back(output_dims); //获取输出维度
	}
	cout << "output_names:" << output_names[0] << endl;

	this->inpHeight = input_node_dims[0][2];
	this->inpWidth = input_node_dims[0][3];
	cout << "inpHeight:" << this->inpHeight << endl;
	cout << "inpWidth:" << this->inpWidth << endl;
	cout << "input_names:" << input_names[0] << endl;
	cv::Mat frame = get_img(config.imagepath);
	cv::Mat dstimg = this->letter_(frame);
	this->normalize_(dstimg);
	//cout << "input_image_.size:" << input_image_.size() << endl;
	//cout << "dstimg.size:" << dstimg.rows << " " << dstimg.cols <<" " << dstimg.channels() << endl;
	//cout << "input_shape:" << this->inpHeight << " " << this->inpWidth << " " << endl;

	array<int64_t, 4> input_shape_{ 1, 3, this->inpHeight, this->inpWidth };

	auto allocator_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
	Ort::Value input_tensor_ = Ort::Value::CreateTensor<float>(allocator_info, input_image_.data(), input_image_.size(), input_shape_.data(), input_shape_.size());

	// 获取张量的类型和形状信息
	Ort::TensorTypeAndShapeInfo tensor_info = input_tensor_.GetTensorTypeAndShapeInfo();

	// 获取张量的形状
	std::vector<int64_t> tensor_shape = tensor_info.GetShape();
	// 打印张量的形状
	std::cout << "Tensor Shape: [";
	for (size_t i = 0; i < tensor_shape.size(); ++i) {
		std::cout << tensor_shape[i];
		if (i != tensor_shape.size() - 1) {
			std::cout << ", ";
		}
	}
	std::cout << "]" << std::endl;

	std::vector<const char*> input_node_names = {"images"};
	std::vector<const char*> output_node_names = { "output0" };

	cout << "input_node_names:" << input_node_names[0] << endl; // 上文已经获得了input name,但是测试打印,打印不出来?所以重新定义

	/*
	try {
		vector<Ort::Value> ort_outputs = ort_session->Run(Ort::RunOptions{ nullptr },
			input_node_names.data(),
			&input_tensor_,
			1,
			output_node_names.data(),
			output_node_names.size());
		// Process ort_outputs as needed
	}
	catch (const Ort::Exception& ex) {
		std::cerr << "ONNX Runtime error: " << ex.what() << std::endl;
	}
	*/
	vector<Ort::Value> ort_outputs = this->ort_session->Run(Ort::RunOptions{ nullptr },// onnx推理
		input_node_names.data(),
		&input_tensor_,
		1,
		output_node_names.data(),
		output_node_names.size());

	Ort::Value& predictions = ort_outputs.at(0);
	auto pred_dims = predictions.GetTensorTypeAndShapeInfo().GetShape();

	std::cout << "输出节点shape:";
	for (int i = 0; i < pred_dims.size(); i++)
	{
		std::cout << pred_dims[i] << " ";
	}
	std::cout << "\n";

	//post-process
	int net_width = _className.size() + 4;
	float* all_data = ort_outputs[0].GetTensorMutableData<float>(); // 获取推理的输出

	auto _outputTensorShape = ort_outputs[0].GetTensorTypeAndShapeInfo().GetShape(); // 图片输出的维度信息 [1, 84, 8400]
	cv::Mat output0 = cv::Mat(cv::Size((int)_outputTensorShape[2], (int)_outputTensorShape[1]), CV_32F, all_data).t(); // [1, 84 ,8400] -> [1, 8400, 84]
	// 获取矩阵的行数和列数
	int rows = output0.rows;  // 行数  预测框的数量 8400
	int cols = output0.cols;  // 列数

	// 输出矩阵的形状信息
	std::cout << "Matrix shape: " << rows << " x " << cols << std::endl;	

	float* pdata = (float*)output0.data; // 获取结果指针


	vector<int> class_ids;  // 准备放入结构体的数据
	vector<float> confidences;
	vector<cv::Rect> boxes;
	for (int r = 0; r < rows; ++r) {
		cv::Mat scores(1, _className.size(), CV_32F, pdata + 4); // 创建80个类别的概率矩阵

		cv::Point classIdPoint;
		double max_class_soces;
		minMaxLoc(scores, 0, &max_class_soces, 0, &classIdPoint); // 得到最大类别概率、类别索引
		max_class_soces = (float)max_class_soces;


		if (max_class_soces > config.confThreshold) // 得到最大类别分数与阈值比较
		{
			confidences.push_back(max_class_soces);
			class_ids.push_back(classIdPoint.x);
			float x = pdata[0];// 前四个是x,y,w,h
			float y = pdata[1];
			float w = pdata[2];
			float h = pdata[3];

			int left = int((x -this->Padwl -0.5 * w) / this->ratio); // x,y,w,h还原至原始尺寸
			int top = int((y - this->Padht - 0.5 * h) / this->ratio);

			int width = int(w / this->ratio);
			int height = int(h / this->ratio);

			boxes.push_back(cv::Rect(left, top, width, height));
		}
		pdata += net_width; //下一个预测框, 一个预测框的长度是84
	}
	cout << "ratio:" << " " << ratio << endl;

	// 预测框执行NMS处理
	vector<int> nms_result;
	cv::dnn::NMSBoxes(boxes, confidences, config.confThreshold, config.iouThreshold, nms_result); // 还需要classThreshold?
	cout << "识别总数量:" << nms_result.size() << endl;

	if (nms_result.size() > 0) {
		vector<OutputDet> temp_output;// 依据NMS结果,获取预测框,所有预测框放入一个容器
		for (size_t i = 0; i < nms_result.size(); ++i) {
			int idx = nms_result[i];
			OutputDet result;
			result.id = class_ids[idx];
			result.confidence = confidences[idx];
			result.box = boxes[idx];
			temp_output.push_back(result);
		}

		

		// 画图
		for (size_t i = 0; i < temp_output.size(); ++i) {
			OutputDet out = temp_output[i];
			std::string label = this->_className[out.id] + " " +
				std::to_string(out.confidence).substr(0, std::to_string(out.confidence).size() - 4);

			//cout << "label:" << " " << label << endl;
			cv::RNG rng(cv::getTickCount());
			cv::Scalar color(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
			//cout << "x1:" << " " << out.box.x <<" " << "y1:" << " " << out.box.y <<" " << "x2:" << " " << out.box.x + out.box.width <<" " << "y2:" << " " << out.box.y + out.box.height << endl;
			cv::rectangle(frame, cv::Point(out.box.x, out.box.y), cv::Point(out.box.x + out.box.width, out.box.y + out.box.height), color, 3);
			cv::rectangle(
				frame,
				cv::Point(out.box.x, out.box.y - 25),
				cv::Point(out.box.x + out.box.width, out.box.y),
				color,
				cv::FILLED
			);

			cv::putText(
				frame,
				label,
				cv::Point(out.box.x, out.box.y - 5),
				cv::FONT_HERSHEY_SIMPLEX,
				0.75,
				cv::Scalar(0, 0, 0),
				2
			);
		};
	}
	
	std::cout << "Press any key to exit" << std::endl;
	cv::namedWindow("Result of Detection", cv::WINDOW_NORMAL);
	cv::imshow("Result of Detection", frame);
	cv::waitKey(0);
	cv::destroyAllWindows();
}

main.cpp

#include "infer.h"
#include <string>
#include <filesystem>
#include <windows.h>
#include <wchar.h>

using namespace std;
namespace fs = std::filesystem;

string modelpath;


std::string getparentpath() {
    std::filesystem::path filePath(__FILE__);
    std::filesystem::path dirPath = filePath.parent_path();
    return dirPath.string();
}

std::vector<std::string> listFiles(const std::string& path) {
    std::vector<std::string> imgpath;
    try {
        // 遍历指定路径下的所有文件和目录
        for (const auto& entry : fs::directory_iterator(path)) {
            string name = entry.path().filename().string();
            if (name.substr(name.rfind('.') + 1) == "jpg") {
                // 输出当前 entry 的路径
                fs::path path1 = path;
                fs::path path2 = name;
                string absolutepath = path +"\\"+ name;
                imgpath.push_back(absolutepath);
            };
            if (name.substr(name.rfind('.') + 1) == "onnx") { 
                fs::path path1 = path;
                fs::path path2 = name;
                string absolutepath = path + "\\" + name;
                modelpath = absolutepath;
            };
        }
    }
    catch (const std::filesystem::filesystem_error& e) {
        std::cerr << "Error accessing directory: " << e.what() << std::endl;
    }
    cout << modelpath << endl;

    return imgpath;
}


int main() {
    std::string dirpath = getparentpath();
    std::vector<std::string> imgpath = listFiles(dirpath);
    // string 转wchar_t*

    int ModelPathSize = MultiByteToWideChar(CP_UTF8, 0, modelpath.c_str(), static_cast<int>(modelpath.length()), nullptr, 0);
    wchar_t* wide_cstr = new wchar_t[ModelPathSize + 1];
    MultiByteToWideChar(CP_UTF8, 0, modelpath.c_str(), -1, wide_cstr, ModelPathSize);
    wide_cstr[ModelPathSize] = L'\0';
    const wchar_t* model_Path = wide_cstr;


    //const wchar_t* model_Path = L"D:\yolov8s.onnx";
    for (int i = 0; i < imgpath.size(); i++) {
        string path = imgpath[i];   
        Net_config cfg = { path, model_Path, 0.5, 0.5 };
	    V8 model;
	    model.detect(cfg);
       
    }	
	return 0;
}

推理结果

目前推理时间还未测试

补充

模型使用的是yolov8s对应的onnx,输入及输出信息如下:

在win11系统上,由于系统优先级的问题,直接调试运行会报错:Ort::GetApi(...) 返回 nullptr。解决方案看如下链接:报错信息如下:引发了异常: 读取访问权限冲突。Ort::GetApi(...) 返回 nullptr。_引发了异常: 读取访问权限冲突。 ort::getapi(...) 返回 nullptr。-CSDN博客

更新(2024.06.27)

修改了main.cpp代码,实现自适应识别相同路径下所有图片,onnx也放置于同样文件夹

onnx文件下载置顶

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值