Windows C++ yolov5->ONNX->TensoRT (ResNet、FCN)
文章目录
- 前言
- 一、yolov5->ONNX
- 二、TensorRT简介
- 1、TensorRT工作原理
- 2、trt模型是什么?
- 3、Host、Buffer、CUDA数据如何通信?
- 三、代码
- 1、ONNX->模型二进制数据流(IHostmemory) ,序列化
- 2、如何保存和读取trt模型?
- 1、保存
- 2、读取
- 3、模型二进制数据流(IHostmemory) ->engine,反序列化
- 4、推理
- 1、构建context
- 2、初始化Buffer和Host空间
- 3、输入图像处理
- 4、数据拷贝和执行推理
- 5、后处理
- 四、结果
前言
本博客主要出于记录阶段性工作,内容如果有误,还请各位小伙伴不吝指正!
网络结构: 这个博客是以yolov5为例的 ,但是这一套流程我是用ResNet分类、FCN分割和yolo检测都是能走通的,更重要的方法掌握。
环境配置和依赖: 本人C++菜鸡,项目使用的是TensorRT文件夹下的sample/sample_mnist项目,然后在里面写cpp文件,这个方式对我这样的新手很友好~(github上的sln属实没有一个能配置成功的)。
基础知识: 有关于YOLO、TensorRT基本原理的这篇就不详细介绍了。
TensorRT: 这里使用最简单的模式,即固定宽高、固定batch数目。
一、yolov5->ONNX
我使用的是yolo官方github的v6.0版本,移除了Focus模块并且激活函数替换为了SiLU。官方项目中自带了转ONNX的export.py文件,下载权重后直接运行就可以导出。
导出后就可以使用netron查看模型结构了,https://netron.app/这个链接可以直接查看。
这里面有一些细节可以可以关注,比如最开始的下采样模块在原始的v1.0版本使用的Focus模块,在做之前我还担心TensorRT不支持,不过V6.0中已经变成了两个卷积。
另外激活函数已也做了调整,貌似mish或者swish激活函数TensorRT都不支持,现在的SiLU其实就是x*sigmoid(x),非常友好。
二、TensorRT简介
1、TensorRT工作原理
首先明确TensorRT中用于执行推理的是engine,可以理解为是torch中的model,有了engine我们就可以进行类似于out = engine(image)的操作。
然后还有一个序列化、反序列化、原始模型二进制数据(自己起的名,代表了IHostmemory类)、TensorRT格式的模型(代表了INetworkDefinition类) 四个概念。
序列化指的是TensorRT格式的模型转为原始模型二进制数据,反序列化指的是原始模型二进制数据转为engine。生成TensorRT格式的模型的方式有很多种,可以通过ONNX,Caffe、TF或者直接在TensorRT中写每一层定义。
2、trt模型是什么?
这个过程中最耗时的是序列化过程,我本机耗时大概在2分钟左右。所以在实际部署中不能每次都从ONNX开始执行,一般都是保存中间的原始模型二进制数据,再直接进行反序列化。 我们可以通过二进制存储、读取的方式将IHostmemory保存到本地,本地的文件就是trt模型。
3、Host、Buffer、CUDA数据如何通信?
输入->Buffer->CUDA->执行推理->Buffer->输出
Host就是本机内存,输入和输出最终都是要返回到Host的。
三、代码
C++基础薄弱,个别地方写的很简陋,请谅解!
1、ONNX->模型二进制数据流(IHostmemory) ,序列化
提前说明的是,TensorRT模型(INetworkDefinition类) 不代表类似torch中的model,它是一个空壳子,我们需要把模型结构、参数等塞进去。上面说过torch中model应该更接近于TensorRT中的engine,是一个执行器。
建立的过程其实都是模板化的东西,当然后续高阶用户使用动态宽高或者自定义组件时还需要修改。
1、创建builder,其中Logger也是模板化的固定代码,用于显示执行过程的信息。
2、创建config。
3、创建network,其中参数为显示的batch维度,固定的代码。
4、创捷一个parser并读取ONNX文件,parser有很多种有caffe的和TF,注意看IParser的命名空间 。
5、设置最大工作空间。
6、序列化network,得到二进制数据流(IHostmemory)。
2、如何保存和读取trt模型?
上面已经介绍过trt模型是什么,以及为什么要保存trt模型。
1、保存
保存的话的前提是我们已经获取到了模型二进制数据流(IHostmemory),代码中就是plan。
2、读取
上面说过,如说使用读取本地的trt模型来构建engine,那么就不需要序列化的部分了。所以使用该方式后那么 三.1章节 的代码段就不需要了,因为我们直接获取到了模型二进制数据流(IHostmemory)——serilaziedData。
3、模型二进制数据流(IHostmemory) ->engine,反序列化
代码就两行,mEngine就是我们想要的推理器。
4、推理
1、构建context
context是engine的一个线程,意思是说我们在得到了一个engine后可以开启多个context进行多线程推理。
一行代码,后面还会用到context。
2、初始化Buffer和Host空间
1、要确定Bindings,Bindings是模型中输入和输出的tensor。对于yolo来讲一共有5个Bindbings,分别是输入:(1, 3, 640, 640)、三个头的输出:(1,3,80,80,85),(1,3,40,40,85),(1,3,20,20,85) 、合并所有anchor的总和:(1,25200,85)。维度为什么是这样,这里不详细说了,这个就是yolo的基本原理。
2、定义buffer空间,这里使用一个for循环,把每一个tensor的大小都保存起来,并向CUDA申请显存。
3、同样在Host也申请一份内存。
3、输入图像处理
float* SampleOnnxMNIST::verifyInput(cv::Mat &resize)
{string path = "E:\\torch_deloy\\car.jpeg";const int c = 3; // 3const int h = 640; // 224const int w = 640; // 224cv::Mat img = cv::imread(path);auto scaleSize = cv::Size(w, h);cv::Mat rgb;cv::cvtColor(img, rgb, cv::COLOR_BGR2RGB);cv::Mat resized;cv::resize(rgb, resized, scaleSize);resize = resized;float* data;data = (float*)calloc(h * w * c, sizeof(float));for (int c = 0; c < 3; ++c){for (int i = 0; i < resized.rows; ++i){ //获取第i行首像素指针 cv::Vec3b* p1 = resized.ptr<cv::Vec3b>(i);//cv::Vec3b *p2 = image.ptr<cv::Vec3b>(i);for (int j = 0; j < resized.cols; ++j){data[c * resized.cols * resized.rows + i * resized.cols + j] = (p1[j][c] / 255.0f);}}}return data;}
输入数据是以RRRR…GGGGG…BBBB…的格式进行转递的。
4、数据拷贝和执行推理
这里需要关注是的
// 将输入传递到GPUCHECK(cudaMemcpy(buffers[0], (const void*)cur_input, buffer_size[0], cudaMemcpyHostToDevice));
// 输出传回给CPUCHECK(cudaMemcpy(out, buffers[4], buffer_size[4], cudaMemcpyDeviceToHost));
buffer[0]是我们之前申请的第一个Binding的空间,也就是输入。
buffer[4]是我们之前申请的第最后Binding的空间,也就是(1,25200,85)。
buffer[1-3]我们并没有使用,这部分可以在申请空间时删除,但是我懒得弄了。
再执行完成后,out就是Host中的结果了。
out的数据排列格式为为:
anchor1的x, anchor1的y, anchor1的w,anchor1的h,anchor1的conf, anchor1的80类置信度, anchor2的x, anchor2的y, anchor2的w,................. anchor25200的x, anchor25200的y, anchor25200的w...........
5、后处理
对于检测来说就是一个NMS了,对于分类、分割其实要简单很多。
NMS我是网上抄的,然后修改了一些部分。代码很烂,有些地方为了方便写的很随意,轻喷~
static float get_iou_value(Rect rect1, Rect rect2)
{int xx1, yy1, xx2, yy2;xx1 = max(rect1.x, rect2.x);yy1 = max(rect1.y, rect2.y);xx2 = min(rect1.x + rect1.width - 1, rect2.x + rect2.width - 1);yy2 = min(rect1.y + rect1.height - 1, rect2.y + rect2.height - 1);int insection_width, insection_height;insection_width = max(0, xx2 - xx1 + 1);insection_height = max(0, yy2 - yy1 + 1);float insection_area, union_area, iou;insection_area = float(insection_width) * insection_height;union_area = float(rect1.width * rect1.height + rect2.width * rect2.height - insection_area);iou = insection_area / union_area;return iou;
}//input: boxes: 原始检测框集合;
//input: confidences:原始检测框对应的置信度值集合
//input: confThreshold 和 nmsThreshold 分别是 检测框置信度阈值以及做nms时的阈值
//output: indices 经过上面两个阈值过滤后剩下的检测框的index
void nms_boxes(vector<Rect>& boxes, vector<float>& confidences, float confThreshold, float nmsThreshold, vector<int>& indices)
{BBOX bbox;vector<BBOX> bboxes;int i, j;for (i = 0; i < boxes.size(); i++){bbox.box = boxes[i];bbox.confidence = confidences[i];bbox.index = i;bboxes.push_back(bbox);}sort(bboxes.begin(), bboxes.end(), [](const BBOX& a, const BBOX& b) { return a.confidence > b.confidence; });int updated_size = bboxes.size();for (i = 0; i < updated_size; i++){if (bboxes[i].confidence < confThreshold)continue;indices.push_back(bboxes[i].index);for (j = i + 1; j < updated_size; j++){float iou = get_iou_value(bboxes[i].box, bboxes[j].box);if (iou > nmsThreshold){bboxes.erase(bboxes.begin() + j);j--;updated_size = bboxes.size();}}}
}
bool SampleOnnxMNIST::postporessing(float* buffer, cv::Mat &img) {vector<Rect> boxes;vector<float> confidences;float confThreshold = 0.5;float nmsThreshold = 0.5;vector<int> indexList;vector<int> Classes;for (int i = 0; i < 25200; i++) {Rect temp;temp.x = ((float*)buffer)[i * 85];temp.y = ((float*)buffer)[i * 85 + 1];temp.width = ((float*)buffer)[i * 85 + 2];temp.height = ((float*)buffer)[i * 85 + 3];float tempConf = *max_element(&buffer[i * 85 + 5], &buffer[(i + 1) * 85]);int tempClass = max_element(&buffer[i * 85 + 5], &buffer[(i + 1) * 85]) - &buffer[i * 85 +5];Classes.push_back(tempClass);confidences.push_back(((float*)buffer)[i * 85+4] * tempConf);boxes.push_back(temp);}nms_boxes(boxes, confidences, confThreshold, nmsThreshold, indexList);for (int i = 0; i < size(indexList); i++) {int x1 = boxes[indexList[i]].x - 0.5 * boxes[indexList[i]].width;int x2 = boxes[indexList[i]].x + 0.5 * boxes[indexList[i]].width;int y1 = boxes[indexList[i]].y - 0.5 * boxes[indexList[i]].height;int y2 = boxes[indexList[i]].y + 0.5 * boxes[indexList[i]].height;cv::rectangle(img, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(0, 0, 255));}cv::imshow("00", img);cv::waitKey(0);return 0;
}
四、结果
哦对了,忘了显示类别了。。。
还有就是速度贼慢,应该是后处理有问题,各位如果有好的后处理代码可以一起分享哈~~
Windows C++ yolov5->ONNX->TensoRT (ResNet、FCN)相关推荐
- windows 下mysql的安装于使用(启动、关闭)
1.下载Windows (x86, 64-bit), ZIP Archive解压: 2.双击在bin目录里的mysqld.exe dos窗体一闪就没了,这时netstat -an发现port3306已 ...
- windows通过脚本批量设置环境变量(env、path)实战:java环境、scala环境、maven环境、gradle环境、nodejs、git等
windows通过脚本批量设置环境变量(env.path)实战:java环境.scala环境.maven环境.gradle环境.nodejs.git等 目录
- 聊一聊ResNet系列(ResNet、ResNeXt)
摘要:终于到了介绍ResNet系列的时候了.ResNet真的很好用,特别时shortcut的想法真的厉害.最近,又推出了改进版本ResNeXt,新版本结合了Inception多支路的思想. 关键字:深 ...
- 利用Windows自带的计算器计算十六进制(八进制、二进制)数据
有时需要对十六进制数做加减乘除,某些手机app能实现此功能,但使用起来未免有些麻烦,其实Windows自带的计算器就可以实现十六进制数的运算. 1.找到并打开计算器 2.点击如图所示的位置 3.点击程 ...
- Windows 10 Enterprise 2015 LTSB 2019_Windows 10(LTSB、LTSC)
版本 下载链接 Windows 10 Enterprise 2016 LTSB (x86) - DVD (Chinese-Simplified) ed2k://|file|cn_windows_10_ ...
- linux 进程间通信 dbus-glib【实例】详解三 数据类型和dteeth(类型签名type域)(层级结构:服务Service --> Node(对象、object) 等 )(附代码)
linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...
- yolov5转hisi的nnie(c and c++)
yolov5转hisi的nnie(c and c++) 总述 一. 训练前修改网络 二. 导出模型 三. 后处理 1. c++版 2. 基于hisi SDK的纯c版(后续更新) 总述 刚躺了坑,记录一 ...
- windows服务开发(一、安装)
最近由于工作需要,写了一个windows服务程序,有许多经验,我会陆续写出来. 请原谅我从安装谈起,因为我一直有一个误区:只要从System.ServiceProcess.ServiceBase继承一 ...
- 【YOLOv5 数据集划分】训练和验证、训练验证和测试(train、val)(train、val、test)
[YOLOv5 数据集划分]训练和验证.训练验证和测试(train.val),(train.val.test) ①在已有测试集的情况下划分训练集和验证集 # 将图片和标注数据按比例切分为 训练集和测试 ...
最新文章
- CentOS的Gearman安装与使用无错版
- LSD-SLAM解读——帧间追踪(详细推导)
- splunk的bucket组织目录——时间序列,按照时间来组织目录
- FckEditor-未能映射路径/UserFiles/image/
- Halcon —— pick_and_place_scara_stationary_cam.hdev
- 【区块链与未来】区块链技术将重塑我们的世界
- spark的python开发安装方式_PyCharm搭建Spark开发环境的实现步骤
- angularjsl路由_AngularJS路由和模板
- 使用Preplot批量将ascii文件转为二进制文件
- python教程list类型_Python数据类型之list相关常用操作
- select * from dim.dim_area_no@to_dw
- php图片旋转显示不出来的,php – 我服务的图像不正确,它们都显示为旋转90度
- background 互联网图片_微信小程序 background-image设置背景图片不显示的解决办法...
- 面试官:如何设计群聊消息的已读未读功能?
- WEB网站压力测试教程详解
- 基于安卓的备忘录文件加密_苹果备忘录、锤子便签、印象笔记哪个更好用?
- 小米路由器局域网设备ping不通
- 【Proteus仿真】数字温度计,利用 Mega16 控制 DS18B20 ,若温度达到设定阈值,即可报警提醒(用串口控制停止报警、用键盘输入改变报警阈值)
- fast RCN论文笔记
- Access IIF 查询语句
热门文章
- 【Oboe——Android低延迟音频应用开发库使用介绍】
- CoolShell-第6题
- python趣味编程范例_厦门大学图书馆v5.5书目检索系统
- 振涛教育云计算学院2206A班荣中伟
- Linux内核分析期末总结
- 论文笔记 2014-Influence Maximization-Near-Optimal Time Complexity Meets Practical Efficiency
- python找数字做加法升级版答案_python学数学1-2:认识数字--自然数加法
- MFC之GetDlgItem函数返回NULL的问题解决
- java学习问题引导
- 缺失的白皮书:DPOS共识算法工作原理及鲁棒性根源分析