Yolo v5 pytorch模型转onnx用c++进行推理

news2024/10/2 1:41:13

本文介绍如何使用u版的yolov5 模型转成 onnx模型,使用python代码推理onnx模型,然后再使用c++代码推理onnx模型,本文使用yolov5 s版本,在m,l,x都试过可行

环境配置 :

1 u版的yolov5 4.0 版本,其他版本没有试过 https://github.com/ultralytics/yolov5

2 opencv 4.3 3.4.8 都可以,pytorch版本1.7

3 onnx 版本采用1.8.0,onnxruntime 采用 1.6.0

4 系统版本: ubuntu 20.04

pytorch模型转成onnx模型:

1 训练好pytorch模型后,进行pytorch模型到onnx模型的转换,进入yolov5主目录的models目录,修改export.py文件,修改54行左右的内容,将model.model[-1].export = True 改为 model.model[-1].export = False 即可

修改完毕后即可导出onnx模型,返回上级yolov5主目录 执行 python models/export.py

onnxruntime c++推理模型

1 c++推理onnx模型所需要的库则是windows版本的onnxruntime库,推理的过程其实就是把python推理onnx模型的过程用c++实现一遍,,这里说明是nms用的是opencv自带的,没有进行加权,而且是用的cpu推理 的。

2.1 步骤主要分为 三步 ,1是初始化模型 2是填充数据进入onnx的输入tensor 3是推理后进行后处理获取输出

2.2 初始化模型,主要是设置好onnx运行时的属性配置,然后载入路径初始化模型,另外还可以进行对模型warm up

/**
  初始化模型
 * @brief Detector::initModel
 * @param model_path //模型路径
 * @param class_num  //类别数目
 * @param conf_thres //分数阈值
 * @param iou_thres  //iou阈值
 * @param input_size // 模型输入的尺寸
 */
void Detector::initModel(std::string &model_path, int class_num,float conf_thres,float iou_thres, std::tuple<int, int, int> &input_size) {
    m_classNum = class_num;
    m_confThres = conf_thres;
    m_iouThres = iou_thres;
    m_inputSize = input_size;
 
#ifdef _WIN32
 
    std::cout<<"is  _WIN32 "<<std::endl;
#else
    //const char* model_path = "best_640x480.onnx";
    const char* modelPath_s = model_path.c_str();
    std::cout<<"not  _WIN32 "<<std::endl;
#endif
    std::cout<<"initModel !!! "<<std::endl;
    // initialize  enviroment...one enviroment per process
    // enviroment maintains thread pools and other state info
    //Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "test");
    m_env = Ort::Env(ORT_LOGGING_LEVEL_WARNING, "test");//创建onnxruntime运行环境
 
    // initialize session options if needed
    Ort::SessionOptions session_options;//创建会话设置选项
    session_options.SetIntraOpNumThreads(1);//设置运行线程数
    session_options.SetInterOpNumThreads(1);
    session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);//设置会话属性
    std::cout<<"initModel 2!!! "<<std::endl;
 
    m_session = Ort::Session(m_env, String2WideString(model_path).c_str(), session_options);//创建会话
 
 
    //配置输入节点
    // print model input layer (node names, types, shape etc.)
    Ort::AllocatorWithDefaultOptions allocator;
    std::cout<<"initModel 1!!! "<<std::endl;
    // print number of model input nodes
    //size_t num_input_nodes = m_session->GetInputCount();//yolov5 just one input
    size_t num_input_nodes = m_session.GetInputCount();//yolov5 just one input
      std::cout<<"initModel 0!!! "<<std::endl;
    //printf("Number of inputs = %zu\n", num_input_nodes);
    m_input_node_names.resize(num_input_nodes); 
    //std::vector<int64_t> input_node_dims;  // simplify... this model has only 1 input node {1, 3, 224, 224}.
    // Otherwise need vector<vector<>>
 
    std::cout<<"num_input_nodes = "<<num_input_nodes<<std::endl;
    // iterate over all input nodes
    for (int i = 0; i < num_input_nodes; i++) {
 
        //char* input_name = m_session->GetInputName(i, allocator);
        char* input_name = m_session.GetInputName(i, allocator);
 
        m_input_node_names[i] = input_name;
 
        //Ort::TypeInfo type_info = m_session->GetInputTypeInfo(i);
        Ort::TypeInfo type_info = m_session.GetInputTypeInfo(i);
        auto tensor_info = type_info.GetTensorTypeAndShapeInfo();
 
        // print input shapes/dims
        m_input_node_dims = tensor_info.GetShape();
 
    }
    //配置输出节点
    m_output_node_names.push_back("output");
 
 
    //下面进行warm up,创建一个输入tensor跑一遍网络进行warm up
 
    //创建一个矩阵数据,尺寸是模型输入尺寸
    cv::Mat imgRGBFLoat(std::get<1>(m_inputSize),std::get<0>(m_inputSize),CV_32FC3);
 
//下面进行
    //图像预处理
    cv::Mat preprocessedImage;
    cv::dnn::blobFromImage(imgRGBFLoat, preprocessedImage);//HWC->CHW
    
    auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
 
    //创建输入tensor
    Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info,reinterpret_cast<float*>(preprocessedImage.data),
                                                              std::get<0>(m_inputSize)*std::get<1>(m_inputSize)*std::get<2>(m_inputSize),
                                                              m_input_node_dims.data(), 4);
 
    //auto output_tensors = m_session->Run(Ort::RunOptions{nullptr}, m_input_node_names.data(), &input_tensor, 1, m_output_node_names.data(), 1);
    auto output_tensors = m_session.Run(Ort::RunOptions{nullptr}, m_input_node_names.data(), &input_tensor, 1, m_output_node_names.data(), 1);
    //auto output_tensors2= m_session2.Run(Ort::RunOptions{nullptr}, m_input_node_names.data(), &input_tensor, 1, m_output_node_names.data(), 1);
 
    std::get<0>( m_input_w_h) = std::get<0>(m_inputSize);
    std::get<1>( m_input_w_h) = std::get<1>(m_inputSize);
    std::cout<<"init success !!!"<<std::endl;
 
}

2.3 对输入进的图片先进行尺寸的修改,然后进行归一化填充到输入tensor,最后进行推理获取输出。

/**
* 获取并组织相应格式的输出
* @param inputImg
* @return
*/
std::vector<std::vector<det_box>> Detector::getOutput(cv::Mat &inputImg) {
//auto input_w_h = std::tuple<int,int>(std::get<0>(m_inputSize),std::get<1>(m_inputSize));
//对输入的图片进行按最长边比例缩放,然后填充短边至模型所需要的输入
cv::Mat resized = letterbox_image_v2(inputImg,m_input_w_h);
cv::cvtColor(resized,resized,cv::COLOR_BGR2RGB);
//cv::imwrite("resized.jpg",resized);
//std::cout<<"getOutput 1"<<std::endl;
cv::Mat imgRGBFLoat;
//std::cout<<"getOutput 2"<<std::endl;
//图片类型进行转换,然后除以255进行归一化
resized.convertTo(imgRGBFLoat,CV_32F, 1.0 / 255);//preprocess

//图片填充到opencv的blob然后进行通道变换
cv::Mat preprocessedImage;
cv::dnn::blobFromImage(imgRGBFLoat, preprocessedImage);//HWC->CHW
//std::cout<<"getOutput 3"<<std::endl;
// create input tensor object from data values
auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
/* Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_tensor_values.data(), input_tensor_size, input_node_dims.data(), 4); */
//图片数据填充进输入的tensor
Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info,reinterpret_cast<float*>(preprocessedImage.data),
std::get<0>(m_inputSize)*std::get<1>(m_inputSize)*std::get<2>(m_inputSize),
m_input_node_dims.data(), 4);
//std::cout<<"getOutput 5"<<std::endl;


// score model & input tensor, get back output tensor
/* auto output_tensors = session.Run(Ort::RunOptions{nullptr}, input_node_names.data(), &input_tensor, 1, output_node_names.data(), 1); */
//auto output_tensors = m_session->Run(Ort::RunOptions{nullptr}, m_input_node_names.data(), &input_tensor, 1, m_output_node_names.data(), 1);

//执行推理获取输出的tensor
auto output_tensors = m_session.Run(Ort::RunOptions{nullptr}, m_input_node_names.data(), &input_tensor, 1, m_output_node_names.data(), 1);
std::cout<<"GetInputCount "<< m_session.GetInputCount()<<std::endl;
//std::cout<<"getOutput 6"<<std::endl;

// for(int i = 0; i<100;i++){
// auto output_tensors2 = m_session->Run(Ort::RunOptions{nullptr}, m_input_node_names.data(), &input_tensor, 1, m_output_node_names.data(), 1);
// }

//printf("Number 222 \n");

//get the output and format boxes
//对输出tensor进行后处理获取所需要的格式数据
std::vector<std::vector<det_box>> det_boxes = non_max_suppression_onnx(output_tensors[0], m_confThres,m_iouThres, m_classNum);
//std::cout<<"getOutput 7"<<std::endl;

return det_boxes;

//return std::vector<std::vector<det_box>>();
}

2.3 推理后得到输出的tensor,对输出的tensor进行后处理,主要是进行nms操作,这里的用到的是opencv自带的nms,另外里面用到的是iou,想改进可以考虑giou,ciou等。

 /**
 * 非极大值抑制算法
 * @param input_tensor 神经网络的输出
 * @param conf_thres 置信度
 * @param iou_thres  交叠阈值
 * @param class_num 类别
 * @return
 */
inline std::vector<std::vector<det_box>> non_max_suppression_onnx(Ort::Value & input_tensor,float conf_thres = 0.25,float iou_thres = 0.45,int class_num = 1){
 
    //下面进行nms,根据官方的做法来的,对于不同的类,对其坐标进行4096的偏移,比如第二类的,则对其坐标进行x+4096*1,y+4096*1的偏移,这样就不用对于每个类的目标进行nms,可以一次性到位
    int min_wh =2,max_wh = 4096;
    int max_det = 300,max_nms = 30000;
    //float time_limit = 10.0;
    //bool redundant = true;
    bool multi_label = false;
    if(class_num >= 1){
        multi_label = true;
    }
 
    //获取模型输出tensor的数据指针
    const float * prob = input_tensor.GetTensorData<float>();//tensor
    
    //获取目标数量,这里先获取输出tensor的元素数量,然后除以每个目标数据的长度就是目标数量,目标数据的长度是上面说的4+1+class_num
    int obj_count = input_tensor.GetTensorTypeAndShapeInfo().GetElementCount()/(class_num+5);
 
    //下面的这些数据容器用于opencv的nms操作
    std::vector<cv::Rect> boxes_vec; //目标框坐标
    std::vector<int> clsIdx_vec;//目标框类别索引
    std::vector<float> scores_vec;//目标框得分索引
    std::vector<int>boxIdx_vec; //目标框的序号索引
    //std::cout<<"obj_count is "<<obj_count<<std::endl;
    //根据目标框数量遍历,根据分数进行过滤
    for(int i = 0; i < obj_count ;i++){
        if(*(prob+i*(5+class_num)+4) < conf_thres )
            continue;
        if(multi_label){
            for(int cls_idx = 0;cls_idx<class_num;cls_idx++){
                float x1 = *(prob+i*(5+class_num)) - *(prob+i*(5+class_num)+2)/2;
                float y1 = *(prob+i*(5+class_num)+1) - *(prob+i*(5+class_num)+3)/2;
                float x2 = *(prob+i*(5+class_num)) + *(prob+i*(5+class_num)+2)/2;
                float y2 = *(prob+i*(5+class_num)+1) + *(prob+i*(5+class_num)+3)/2;
                // mix_conf = obj_conf * cls_conf > conf_thres
                //这里是官方的目标置信度*类别概率
                float mix_conf = (*(prob+i*(5+class_num)+4)) * (*(prob+i*(5+class_num)+5+cls_idx));
                if( mix_conf >conf_thres ){
                    boxes_vec.push_back(cv::Rect(x1+cls_idx*max_wh,
                                                 y1+cls_idx*max_wh,
                                                 x2 - x1,
                                                 y2 - y1)
                    );
                    //scores_vec.push_back((*(prob+i*(5+class_num)+4)));
                    scores_vec.push_back(mix_conf);
                    //std::cout<<"score "<<(*(prob+i*(5+class_num)+5+cls_idx))<<std::endl;
                    clsIdx_vec.push_back(cls_idx);
                    //cv::Rect
                }
            }
 
        }else{
 
        }
 
    }//obj_count
 
    //下面进行nms,结果是以索引的方式放在boxIdx_vec,根据索引就可以获取符合标准的目标框
    cv::dnn::NMSBoxes(boxes_vec, scores_vec, 0.0f, iou_thres, boxIdx_vec);
    std::vector<std::vector<det_box>> det_boxes;
    det_boxes.resize(class_num);
    for(int i = 0; i < boxIdx_vec.size();i++){
        det_box det_box_tmp;
        det_box_tmp.x1 = boxes_vec[boxIdx_vec[i]].x - clsIdx_vec[boxIdx_vec[i]]*max_wh;
        det_box_tmp.y1 = boxes_vec[boxIdx_vec[i]].y - clsIdx_vec[boxIdx_vec[i]]*max_wh;
        det_box_tmp.w = boxes_vec[boxIdx_vec[i]].width;
        det_box_tmp.h = boxes_vec[boxIdx_vec[i]].height;
        det_box_tmp.score = scores_vec[boxIdx_vec[i]];
        det_boxes[clsIdx_vec[boxIdx_vec[i]]].push_back(det_box_tmp);
    }
 
    return det_boxes;
 
}

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

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

相关文章

console调试,chrome调试工具之Console

背景 console作为web调试的一项重要技能大多数开发人员都是console.log()一把梭到底&#xff0c;其实console对象上还有很多可用于调试的方法 控制台打印类别&#xff0c;conso调试面板 第一个是全部消息&#xff0c;第二个是自己在控制台里使用console指令打印的&#xff0…

【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢

HashMap中为什么引入红黑树&#xff0c;而不是AVL树呢1. 概述 开始学习这个知识点之前我们需要知道&#xff0c;在JDK1.8 以及之前&#xff0c;针对HashMap有什么不同。 JDK 1.7的时候&#xff0c;HashMap的底层实现是数组 链表JDK1.8的时候&#xff0c;HashMap的底层实现是数…

Web3中文|Web3再起恐慌:全球第三大稳定币BUSD发行商面临诉讼

俗话说&#xff0c;老大的位置不好当。作为全球最大加密资产交易所BA的CEO&#xff0c;赵长鹏的生活可谓一刻都不能平静。 刚刚结束与SBF的口舌之争&#xff0c;从FTX倒台的熊市中挺过来&#xff0c;赵长鹏又遇到了新的麻烦。 Nansen数据显示&#xff0c;用户在过去24小时内从…

LabVIEW开发的上位机界面在其它电脑分辨率下-界面窗口偏移显示问题解决

目录 问题&#xff1a; 分析&#xff1a; 解决方式 1&#xff09;编辑前面板边界适配对应的分辨率 2&#xff09;编辑前面板窗口-窗口边界 3&#xff09;编辑前面板窗口-保持窗口比例 4&#xff09;设置VI属性--窗口运行时位置居中显示 参考 问题&#xff1a; 在基于La…

图的基本概念

1、图的概念 G(V&#xff0c;E) 图G由节点集合VV(G)和边集合EE(G)组成&#xff0c;其中V为非空有限集合。 集合V中的节点&#xff08;node&#xff09;用红色标出&#xff0c;通过集合E中黑色的边&#xff08;edge&#xff09;连接。 G的边&#xff1a;E中的每个顶点对&#x…

SAP 如何在调式中查找标准程序的权限对象

当我们尝试分析授权问题&#xff08;SU53、SU24……&#xff09;时&#xff0c;有许多不同的交易很有用。 但是&#xff0c;在某些情况下&#xff0c;在调试中检查授权对象很有用。 这很有用&#xff0c;例如&#xff0c;如果我们想确切地知道在事务执行的哪个点调用了给定的授…

关键路径法和最小生成树

1、关键路径法概述关键路径的服务对象是“AOE网”&#xff08;Activity on edge netword&#xff09;。不同的是AOV网只考虑顶点事件&#xff0c;而AOE网除了顶点事件&#xff08;如v[0]&#xff09;外&#xff0c;重点还有就是有向边还表示了活动(如e[0][1] a0)。其中顶点事件…

重生之我是赏金猎人-SRC漏洞挖掘(二)-逆向app破解数据包sign值实现任意数据重放添加

0x00前言 本期登场的目标虽不是SRC&#xff0c;但是整个漏洞的利用手法很有学习意义。目前在很多大厂的http数据包中都会添加sign值对数据包是否被篡改进行校验&#xff0c;而sign算法的破解往往是我们漏洞测试的关键所在~ 本人在一些漏洞挖掘实战中经常发现在破解sign值后&a…

【电商】订单拆单的流程中,系统需要做哪些工作?

什么是拆单&#xff1f; 在网上购买商品下单成功后&#xff0c;过一段时间再次浏览时&#xff0c;有时会发现你的订单会变成两个或多个&#xff0c;这就是系统做了拆单而导致的。 拆单&#xff0c;就是将一个大的订单依据某些规则的集合&#xff0c;将其分解成两个或多个子订…

内核模块(编译方法)

目录 一、向内核添加新功能 1.1 静态加载法&#xff1a; 1.2 动态加载法&#xff1a; a、新功能源码与Linux内核源码在同一目录结构下时 b、新功能源码与Linux内核源码不在同一目录结构下时 c、主机ubuntu下使用ko文件 d、开发板Linux下使用ko文件 二、内核模块基础代码…

链表题目总结 -- 回文链表

目录一. 从中心开始找最大的回文字符串1. 思路简述2. 代码3. 总结二. 判断是否为回文字符串1. 思路简述2. 代码3.总结三. 判断是否是回文链表1. 思路简述2. 代码3. 总结4. 优化解法一. 从中心开始找最大的回文字符串 题目链接&#xff1a;没有。给定一个字符串s&#xff0c;从…

平面电路和非平面电路

主要区别 参考&#xff1a;https://www.eda365.com/article-192836-1.html 平面电路&#xff1a;在平面上的每个元件的两端都可以不用交叉而连接到电路&#xff1b; 非平面电路&#xff1a;在平面上存在元件两端无法不交叉线路连接到电路。 例子&#xff08;上面参考连接中&a…

继企业级信息系统开发学习1.1 —— Spring配置文件管理Bean

骑士救美计划采用构造方法注入属性值1、创建救美任务类2、创建救美骑士类2、创建救美骑士类3、创建旧救美骑士测试类3、配置救美骑士Bean5、创建新救美骑士测试类采用构造方法注入属性值 1、创建救美任务类 在net.huawei.spring.day01包里创建RescueDamselQuest类 Rescue Da…

【重点】Selenium + Nightwatch 自动化测试环境搭建

开始搭建 1. 创建项目 我们来找个地方新建一个目录&#xff0c;起名为 "my-test-toolkit"&#xff0c;然后在目录内使用终端运行 npm init -y 生成项目配置文件package.json。 2. 安装工具 然后我们将安装 Selenium 与 Nightwatch。 安装 selenium-standalone&…

在哔站黑马程序员学习Spring—Spring Framework—(五)spring的第二特征AOP面向切面编程

一、AOP概念、作用AOP和OOP一样都是一种编程思想&#xff0c;用来指导我们做程序的。OOP面向对象编程指导我们做类、对象、继承、封装、多态等。AOP面向切面编程作用&#xff1a;在不惊动原始设计&#xff08;不改变源代码&#xff09;的基础上为其进行功能增强。核心&#xff…

2022年全国职业院校技能大赛网络空间安全A模块(1)

目录 模块A 基础设施设置与安全加固 一、项目和任务描述&#xff1a; 二、服务器环境说明 三、具体任务&#xff08;每个任务得分以电子答题卡为准&#xff09; A-1任务一 登录安全加固 1.密码策略&#xff08;Windows&#xff0c;Linux&#xff09; a.设置最短密码长度为…

AC/DC 基础

一、概念&#xff1a; AC转换成DC的基本方法有变压器方式和开关方式&#xff0c;如下图1、2所示&#xff1b;整流的基本方法有全波整流和半波整流&#xff0c;如下图3所示。 图1 变压器方式 图2 开关方式 图3 整流方式 二、转换方式 1、变压器方式 变压器方式首先需要通过变压…

< 算法基础 之 二分查找 >

算法基础 之 二分查找前言&#x1f449; “ 二分查找 ” 原理及实现&#x1f449; 实际案例&#xff1a;> 基础案例 - 搜索下标示例 1示例 2解决方案> 进阶案例 - 搜索二维矩阵示例 1示例 2解决方案往期内容 &#x1f4a8;前言 在开发中&#xff0c;我们常常会需要查找某…

java无重复字符的最长子串

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”&#xff0c;所以其长度为 3。 示例 2: 输入: s “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “…

GEE学习笔记九十二:Sentinel-2 最新去云方法总结

下面使用例子的原始影像截图如下&#xff1a; 第一种方法&#xff1a;使用QA波段去云 这是我们最常用的方法&#xff0c;具体原理就是利用QA60波段标记实现去云&#xff0c;具体代码如下&#xff1a; var s2 ee.ImageCollection("COPERNICUS/S2"), point /* …