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

先说声抱歉因为搬家和工作问题 所以这部分文章耽搁了很久,趁着放假有空把部分文章补上

目录

要点:

环境配置 :

pytorch模型转成onnx模型:

onnxruntime python推理模型

onnxruntime c++推理模型

onnxruntime java推理模型

小结



要点:

        本文介绍如何使用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 系统版本: windows 10  64位

pytorch模型转成onnx模型:

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

Yolo v5 pytorch模型转onnx用c++进行推理_第1张图片

        修改完毕后即可导出onnx模型,返回上级yolov5主目录 执行 python models/export.py  --weights    ./640x640.pt  --img 640   640 --batch 1

        参数解释 weights:训练好的pytorch模型  img :转换的高宽 (注意,这里必须是32的倍数,顺序是高宽)  batch:转换后一次推理的图片数目(注意这里使用多少,推理时就用多少,目前没研究动态batch推理)

onnxruntime python推理模型

        1导出onnx文件后,进到yolov5 主目录执行 python demo_onnx_carPerson.py  -i testImags/tesImg.jpg  -m ./mine_models/best.onnx 验证下

参数解释: -i :要推理的图片  -m:所用到的onnx模型

这里主要有两点,一个是理解模型输出张量的格式,一个是前预处理的方法

模型的输出张量格式是有一个个目标数据组成的,目标数据的格式是x_center,y_center,w,h,obj_conf,class_1_conf,class_2_conf,...,class_n_conf,就是中心点的x坐标,y坐标,宽,高,目标置信度,每个类别的置信度,那么一个目标数据的长度就是 4 + 1 + class_num,那么输出张量的格式就是obj_num*(4+1+class_num),obj_num是目标个数

前处理的方法,第一步就是先根据模型输入修改图片的尺寸,可以直接采用opencv resize 图片,这样也可以进行推理,不过为了跟官方保持一致,可以进行按图片最长边的比例进行缩放,然后进行padding的方法进行resize的,方法在demo_onnx_carPerson.py的letterbox_image_v2的函数里面。推理函数则是detect_onnx函数。

Yolo v5 pytorch模型转onnx用c++进行推理_第2张图片

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 &input_size) {
    m_classNum = class_num;
    m_confThres = conf_thres;
    m_iouThres = iou_thres;
    m_inputSize = input_size;

#ifdef _WIN32
 
    std::cout<<"is  _WIN32 "<GetInputCount();//yolov5 just one input
    size_t num_input_nodes = m_session.GetInputCount();//yolov5 just one input
      std::cout<<"initModel 0!!! "< input_node_dims;  // simplify... this model has only 1 input node {1, 3, 224, 224}.
    // Otherwise need vector>
 
    std::cout<<"num_input_nodes = "<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(memory_info,reinterpret_cast(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 !!!"<

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

/**
 * 获取并组织相应格式的输出
 * @param inputImg
 * @return
 */
std::vector> Detector::getOutput(cv::Mat &inputImg) {
    //auto input_w_h = std::tuple(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"<CHW
    //std::cout<<"getOutput 3"<(memory_info, input_tensor_values.data(), input_tensor_size, input_node_dims.data(), 4); */ 
    //图片数据填充进输入的tensor
    Ort::Value input_tensor = Ort::Value::CreateTensor(memory_info,reinterpret_cast(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"<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()<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> det_boxes =  non_max_suppression_onnx(output_tensors[0], m_confThres,m_iouThres, m_classNum);

    //std::cout<<"getOutput 7"<>();
}

        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> 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();//tensor
    
    //获取目标数量,这里先获取输出tensor的元素数量,然后除以每个目标数据的长度就是目标数量,目标数据的长度是上面说的4+1+class_num
    int obj_count = input_tensor.GetTensorTypeAndShapeInfo().GetElementCount()/(class_num+5);

    //下面的这些数据容器用于opencv的nms操作
    std::vector boxes_vec; //目标框坐标
    std::vector clsIdx_vec;//目标框类别索引
    std::vector scores_vec;//目标框得分索引
    std::vectorboxIdx_vec; //目标框的序号索引
    //std::cout<<"obj_count is "< 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))<> 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;

}

############################################### 

onnxruntime java推理模型

        1 java推理模型跟c++类似,下载好相应的onnxruntime,opencv jar包配置好IDEA环境即可

小结

        时间比较仓促,可能其中会有错误,欢迎指出。

        改进的建议: 1 目前是基于cpu的代码,如果想用gpu可以考虑修改下

                            2 使用的是普通的nms,iou,可以用加权nms,giou,ciou进行提升

                            3  nms的性能提升,这个是基于 建议1 ,如果是用gpu推理,可以考虑 cuda版本的nms,大佬们可以自行实现,以前在推理ssd libtorch时确实发现有很大速度提升,有需要可以参考之前写的ssd libtorch里的nms,不过因为时间关系没有写的很详细

                            4 有时间再把Yolov5 移植到海思Hi3516DV300 利用NPU进行推理的过程写下,  目前时间来不及写,主要是为了符合hi3516Dv300 npu推理而修改网络结构,  然后是转换到caffe的修改,最后是转成Hi3156Dv300 所要的wk模型,最后还  要进行推理后处理等,Yolov5移植到海思Hi3516DV300也是一个不小的工程    小项目。。。。

                            5 博客所需要文件已打包上传,有需要的哥们可以下载 

yolov5.zipc++推理yolov5onnx模型_onnxjava-深度学习文档类资源-CSDN下载

 车辆,车牌,反光衣,安全帽等数据集,链接,有兴趣的朋友可以看下 链接:

链接:https://pan.baidu.com/s/1mG7X71rngtWqP2tsfFm26A 提取码:5555

参考链接:Qt中cvMat与QImage,QPixmap的转换_草帽小子的博客-CSDN博客

你可能感兴趣的:(工程,机器学习,pytorch,onnx,c++,模型部署,yolov5,车牌车辆数据集)