【Yolov8部署】 VS2019+opencv+onnxruntime 环境下部署目标检测模型

news2025/4/2 19:29:48

文章目录

  • 前言
  • 一、导出yolov8模型为onnx文件
  • 二、VS2019中环境配置
  • 三、源码与实际运行


前言

本文主要研究场景为工业场景下,在工控机与工业相机环境中运行的视觉缺陷检测系统,因此本文主要目的为实现c++环境下,将yolov8已训练好的检测模型使用onnxruntime 部署通过cpu或gpu cuda加速进行检测运算

一、导出yolov8模型为onnx文件

Yolo官方支持onnx的使用,直接使用yolo的官方库即可
配置好训练权重文件路径,输出参数按自己需求查手册配置

from ultralytics import YOLO

# Load a model
#model = YOLO("yolov8n.pt")  # Load an official model
model = YOLO(r"D:\deep_learning\YOLOv8.2\runs\train\exp9\weights\best.pt") # Load a custom trained model

# Export the model
success = model.export(format="onnx", opset=11, simplify=True)

输出成功的文件会在权重文件同目录下
在这里插入图片描述
这里生成的文件为11.6MB,过小的onnx文件可能是生成失败产生的
在这里插入图片描述
获取了onnx文件后接下来配置调用onnx文件的部分

二、VS2019中环境配置

上篇文章中详细介绍了opencv在vs2019中的配置方法,这里就不再重复写了
opencv的配置见【Yolov8部署】 VS2019+opencv-dnn CPU环境下部署目标检测模型

YOLO官方提供的onnxruntime示例源码如下
https://github.com/ultralytics/ultralytics/tree/main/examples/YOLOv8-ONNXRuntime-CPP
可按需下载使用,本文提供另一种使用方法,下文会附上源码
在这里插入图片描述
首先需要下载ONNX Runtime gpu版本库
下载链接如下:
https://github.com/microsoft/onnxruntime/releases/tag/v1.21.0
在这里插入图片描述
ONNX Runtime的版本需要注意与cuda版本对应
我的cuda版本装的是11.6,这里选用1.15版本的ONNX Runtime
在这里插入图片描述
使用onnxruntime推理不需要额外用opencv_gpu版本,cpu版本的opencv就可以满足要求
VS主要需要配置的是包含目录和三个动态库
在这里插入图片描述
库目录配置
在这里插入图片描述
附加依赖项配置,将三个lib动态库都配置上
在这里插入图片描述

三、源码与实际运行

以下是调用推理源码
main.cpp

#include <opencv2/opencv.hpp>
#include "onnxruntime_cxx_api.h"

int main()
{
    // 预测的目标标签数
    std::vector<std::string> labels;

    labels.push_back("bright_collision");
    labels.push_back("dark_collision");

    std::filesystem::path current_path = std::filesystem::current_path();
    std::filesystem::path imgs_path = current_path / "images";
    for (auto& i : std::filesystem::directory_iterator(imgs_path))
    {
	    if (i.path().extension() == ".jpg" || i.path().extension() == ".png" || i.path().extension() == ".jpeg")
	    {
		    std::string img_path = i.path().string();
		    cv::Mat img = cv::imread(img_path);

	    }
    }

    // 测试图片
    cv::Mat image = cv::imread("D:\\VsEnvironment\\yolov8onnxCUDA\\yolov8onnxCUDA\\images\\Image_20241126165712899.jpg");
    cv::imshow("输入图", image);

    // ******************* 1.初始化ONNXRuntime环境 *******************
    Ort::Env env = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "D:\\VsEnvironment\\dataSet\\best.onnx");
    // ***************************************************************

    // ******************* 2.设置会话选项 *******************
    // 创建会话
    Ort::SessionOptions session_options;
    // 优化器级别:基本的图优化级别
    session_options.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);
    // 线程数:4
    session_options.SetIntraOpNumThreads(4);
    // 设备使用优先使用GPU而是才是CPU
    OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0);//此条屏蔽则使用为cpu
    //OrtSessionOptionsAppendExecutionProvider_CPU(session_options, 1);
    std::cout << "onnxruntime inference try to use GPU Device" << std::endl;
    // ******************************************************

    // ******************* 3.加载模型并创建会话 *******************
    // onnx训练模型文件
    std::string onnxpath = "D:\\VsEnvironment\\dataSet\\best.onnx";
    std::wstring modelPath = std::wstring(onnxpath.begin(), onnxpath.end());
    // 加载模型并创建会话
    Ort::Session session_(env, modelPath.c_str(), session_options);
    // ************************************************************

    // ******************* 4.获取模型输入输出信息 *******************
    int input_nodes_num = session_.GetInputCount();			// 输入节点输
    int output_nodes_num = session_.GetOutputCount();		// 输出节点数
    std::vector<std::string> input_node_names;				// 输入节点名称
    std::vector<std::string> output_node_names;				// 输出节点名称
    Ort::AllocatorWithDefaultOptions allocator;				// 创建默认配置的分配器实例,用来分配和释放内存
    // 输入图像尺寸
    int input_h = 0;
    int input_w = 0;
    // 获取模型输入信息
    for (int i = 0; i < input_nodes_num; i++) {
	    // 获得输入节点的名称并存储
	    auto input_name = session_.GetInputNameAllocated(i, allocator);
	    input_node_names.push_back(input_name.get());
	    // 显示输入图像的形状
	    auto inputShapeInfo = session_.GetInputTypeInfo(i).GetTensorTypeAndShapeInfo().GetShape();
	    int ch = inputShapeInfo[1];
	    input_h = inputShapeInfo[2];
	    input_w = inputShapeInfo[3];
	    std::cout << "input format: " << ch << "x" << input_h << "x" << input_w << std::endl;
    }
    // 获取模型输出信息
    int num = 0;
    int nc = 0;
    for (int i = 0; i < output_nodes_num; i++) {
	    // 获得输出节点的名称并存储
	    auto output_name = session_.GetOutputNameAllocated(i, allocator);
	    output_node_names.push_back(output_name.get());
	    // 显示输出结果的形状
	    auto outShapeInfo = session_.GetOutputTypeInfo(i).GetTensorTypeAndShapeInfo().GetShape();
	    num = outShapeInfo[0];
	    nc = outShapeInfo[1];
	    std::cout << "output format: " << num << "x" << nc << std::endl;
    }
    // **************************************************************

    // ******************* 5.输入数据预处理 *******************
    // 默认是BGR需要转化成RGB
    cv::Mat rgb, blob;
    cv::cvtColor(image, rgb, cv::COLOR_BGR2RGB);
    // 对图像尺寸进行缩放
    cv::resize(rgb, blob, cv::Size(input_w, input_h));
    blob.convertTo(blob, CV_32F);
    // 对图像进行标准化处理
    blob = blob / 255.0;	// 归一化
    cv::subtract(blob, cv::Scalar(0.485, 0.456, 0.406), blob);	// 减去均值
    cv::divide(blob, cv::Scalar(0.229, 0.224, 0.225), blob);	//除以方差
    // CHW-->NCHW 维度扩展
    cv::Mat timg = cv::dnn::blobFromImage(blob);
    std::cout << timg.size[0] << "x" << timg.size[1] << "x" << timg.size[2] << "x" << timg.size[3] << std::endl;
    // ********************************************************

    // ******************* 6.推理准备 *******************
    // 占用内存大小,后续计算是总像素*数据类型大小
    size_t tpixels = 3 * input_h * input_w;
    std::array<int64_t, 4> input_shape_info{ 1, 3, input_h, input_w };
    // 准备数据输入
    auto allocator_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
    Ort::Value input_tensor_ = Ort::Value::CreateTensor<float>(allocator_info, timg.ptr<float>(), tpixels, input_shape_info.data(), input_shape_info.size());
    // 模型输入输出所需数据(名称及其数量),模型只认这种类型的数组
    const std::array<const char*, 1> inputNames = { input_node_names[0].c_str() };
    const std::array<const char*, 1> outNames = { output_node_names[0].c_str() };
    // **************************************************

    // ******************* 7.执行推理 *******************
    std::vector<Ort::Value> ort_outputs;
    try {
	    ort_outputs = session_.Run(Ort::RunOptions{ nullptr }, inputNames.data(), &input_tensor_, 1, outNames.data(), outNames.size());
    }
    catch (std::exception e) {
	    std::cout << e.what() << std::endl;
    }
    // **************************************************

    // ******************* 8.后处理推理结果 *******************
    // 1x5 获取输出数据并包装成一个cv::Mat对象,为了方便后处理
    const float* pdata = ort_outputs[0].GetTensorMutableData<float>();
    cv::Mat prob(num, nc, CV_32F, (float*)pdata);
    // 后处理推理结果
    cv::Point maxL, minL;		// 用于存储图像分类中的得分最小值索引和最大值索引(坐标)
    double maxv, minv;			// 用于存储图像分类中的得分最小值和最大值
    cv::minMaxLoc(prob, &minv, &maxv, &minL, &maxL);
    // 获得最大值的索引,只有一行所以列坐标既为索引
    int max_index = maxL.x;
    std::cout << "label id: " << max_index << std::endl;
    // ********************************************************

    // 在测试图像上加上预测的分类标签
    cv::putText(image, labels[max_index], cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 255), 2, 8);
    cv::imshow("输入图像", image);
    cv::waitKey(0);

    // ******************* 9.释放资源*******************
    session_options.release();
    session_.release();
    // *************************************************

    return 0;
}

由于预处理与推理都是调用onnxruntime_cxx_api.h中的部分,所以直接写在main.cpp中测试
通过OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0);可以控制是使用cpu推理还是gpu_cuda进行推理
实际推理耗时如下
在这里插入图片描述

在这里插入图片描述
cpu用时141毫秒而gpu用时4.134秒
这里硬件环境是r7-3750h 1650-4g 16g运行内存
两者运行时间差异太大
可能是gpu计算分配耗时比较久,有知道原因的小伙伴可以在博客下留言告知

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2325897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文阅读:Dual Anchor Graph Fuzzy Clustering for Multiview Data

论文地址:Dual Anchor Graph Fuzzy Clustering for Multiview Data | IEEE Journals & Magazine | IEEE Xplore 代码地址&#xff1a;https://github.com/BBKing49/DAG_FC 摘要 多视角锚图聚类近年来成为一个重要的研究领域&#xff0c;催生了多个高效的方法。然而&#…

乐橙R10 AI智能锁:以「技术减法」终结智能家居「参数内卷」

1 行业迷思&#xff1a;当「技术内卷」背离用户真实需求 “三摄猫眼”、“0.3秒人脸解锁”、“DeepSeek大模型”……智能锁行业的营销话术日益浮夸&#xff0c;但用户体验却陷入“功能冗余”与“操作复杂”的泥潭。 一位用户在社交平台直言&#xff1a;“我的智能锁有六个摄像…

如何使用 FastAPI 构建 MCP 服务器

哎呀&#xff0c;各位算法界的小伙伴们&#xff01;今天咱们要聊聊一个超酷的话题——MCP 协议&#xff01;你可能已经听说了&#xff0c;Anthropic 推出了这个新玩意儿&#xff0c;目的是让 AI 代理和你的应用程序之间的对话变得更顺畅、更清晰。不过别担心&#xff0c;为你的…

当 EcuBus-Pro + UTA0401 遇上 NSUC1500

文章目录 1.前言2.EcuBus-Pro简介2.1 官方地址2.2 概览 3.纳芯微NSUC1500简介3.1 NSUC1500概述3.2 产品特性 4.测试环境5.基础功能5.1 数据发送5.2 数据监控 6.自动化功能6.1 脚本创建6.2 脚本编辑6.3 脚本编辑与测试 7.音乐律动7.1 导入例程7.2 效果展示 ECB工程 1.前言 最近…

【FreeRTOS】裸机开发与操作系统区别

&#x1f50e;【博主简介】&#x1f50e; &#x1f3c5;CSDN博客专家 &#x1f3c5;2021年博客之星物联网与嵌入式开发TOP5 &#x1f3c5;2022年博客之星物联网与嵌入式开发TOP4 &#x1f3c5;2021年2022年C站百大博主 &#x1f3c5;华为云开发…

Deepseek API+Python 测试用例一键生成与导出 V1.0.4 (接口文档生成接口测试用例保姆级教程)

接口文档生成接口测试用例保姆级教程 随着测试需求的复杂性增加,测试用例的设计和生成变得愈发重要。Deepseek API+Python 测试用例生成工具在 V1.0.4 中进行了全方位的优化和功能扩展,特别是对接口测试用例设计的支持和接口文档的智能解析处理。本文将详细介绍 V1.0.4 版本…

CET-4增量表

CET-4词表-增量表 注&#xff1a; 【1】所谓增量&#xff0c;是相对于高中高考之增量 即&#xff0c;如果你是在读大学生&#xff0c;高中英语单词过关了&#xff0c;准备考CET-4&#xff0c;那么侧重下面的增量词表的学习&#xff0c;也算是一条捷径吧 ^_^ 【2】本结果数据 官…

DeepSeek详解:探索下一代语言模型

文章目录 前言一、什么是DeepSeek二、DeepSeek核心技术2.1 Transformer架构2.1.1 自注意力机制 (Self-Attention Mechanism)(a) 核心思想(b) 计算过程(c) 代码实现 2.1.2 多头注意力 (Multi-Head Attention)(a) 核心思想(b) 工作原理(c) 数学描述(d) 代码实现 2.1.3 位置编码 (…

FOC 控制笔记【三】磁链观测器

一、磁链观测器基础 1.1 什么是磁链 磁链&#xff08;magnetic linkage&#xff09;是电磁学中的一个重要概念&#xff0c;指导电线圈或电流回路所链环的磁通量。单位为韦伯&#xff08;Wb&#xff09;&#xff0c;又称磁通匝。 公式为&#xff1a; 线圈匝数 穿过单匝数的…

SpringBoot项目读取自定义的配置文件

先说使用场景: 开发时在resource目录下新建一个 config 文件夹, 在里面存放 myconf.properties 文件, 打包后这个文件会放到与jar包同级的目录下, 如下图 关键点&#xff1a;自定义的文件名(当然后缀是.properties)&#xff0c;自定义的存放路径。 主要的要求是在打包后运行过…

在PyCharm 中免费集成Amazon CodeWhisperer

CodeWhisperer 是Amazon发布的一款免费的AI 编程辅助小工具&#xff0c;可在你的集成开发环境&#xff08;IDE&#xff09;中生成实时单行或全函数代码建议&#xff0c;帮助你快速构建软件。简单来说&#xff0c;Amazon CodeWhisperer就是你写一段注释&#xff08;支持中文&…

[7-02-02].第15节:生产经验 - 消费者相关操作

Kafka笔记大纲 五、生产经验——分区的分配以及再平衡: 4.1.生产经验——分区的分配以及再平衡 4.2.参数&#xff1a; 5.4.1 Range 以及再平衡

Matlab_Simulink中导入CSV数据与仿真实现方法

前言 在Simulink仿真中&#xff0c;常需将外部数据&#xff08;如CSV文件或MATLAB工作空间变量&#xff09;作为输入信号驱动模型。本文介绍如何高效导入CSV数据至MATLAB工作空间&#xff0c;并通过From Workspace模块实现数据到Simulink的精确传输&#xff0c;适用于运动控制…

文件操作与IO—File类

目录 1 属性 2 构造方法 3 常用方法 4 示例代码 1 属性 修饰符与类型 属性 含义 static String pathSeparator 依赖于系统的路径分隔符&#xff0c;String类型的表示 static char pathSeparator 依赖于系统的路径分隔符&#xff0c;char类型的表示 2 构造方法 构造…

音频进阶学习二十四——IIR滤波器设计方法

文章目录 前言一、滤波器设计要求1.选频滤波器种类2.通带、阻带、过度带3.滤波器设计指标 二、IIR滤波器的设计过程1.设计方法2.常见的模拟滤波器设计1&#xff09;巴特沃斯滤波器&#xff08;Butterworth Filter&#xff09;2&#xff09;切比雪夫滤波器&#xff08;Chebyshev…

MVC编程

MVC基本概述 例子——显示本地文件系统结构 先分别拖入ListView,TableView,TreeView 然后在进行布局 在widget.cpp 结果 mock测试 1&#xff0c;先加入json测试对象 2.创建后端目录 3&#xff0c;在src添加新文件 在models文件夹里 在mybucket.h,添加测试用例的三个字段 4.在…

Qt进阶开发:对象树与拥有权

文章目录 一、对象树的概念二、对象拥有权&#xff08;Ownership&#xff09;三、Qt Widgets 中的特殊情况四、对象树与拥有权的实例 一、对象树的概念 在 Qt 中&#xff0c;对象树&#xff08;Object Tree&#xff09;与对象的拥有权&#xff08;Ownership&#xff09;密切相…

Django:构建高性能Web应用

引言&#xff1a;为何选择Django&#xff1f; 在当今快速发展的互联网时代&#xff0c;Web应用的开发效率与可维护性成为开发者关注的核心。Django作为一款基于Python的高级Web框架&#xff0c;以其"开箱即用"的特性、强大的ORM系统、优雅的URL路由设计&#xff0c;…

C语言基础系列【32】指针进阶5:指针与常量

博主介绍&#xff1a;程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章&#xff0c;首发gzh&#xff0c;见文末&#x1f447;&#x1f…

文法 2025/3/3

文法的定义 一个文法G是一个四元组&#xff1a;G(,,S,P) &#xff1a;一个非空有限的终极符号集合。它的每个元素称为终极符号或终极符&#xff0c;一般用小写字母表示。终极符号是一个语言不可再分的基本符号。 &#xff1a;一个非空有限的非终极符号集合。它的每个元素称为…