下面的内容是对 YOLOv5-yolov5s TensorRT部署前的准备之导出可用的ONNX。

之前已经写过部分内容,但是还不够详细。

1. YOLOv5-6.0版本

以下内容以 6.0 版本为准。

修改导出文件:

    def forward(self, x):z = []  # inference outputfor i in range(self.nl):x[i] = self.m[i](x[i])  # conv2d# 修改1:对于任何用到shape、size返回值的参数时,为了避免pytorch 导出 onnx 时候,对size 进行跟踪,跟踪时候会生成gather、shape的节点。使用 map 加上int转换。bs, _, ny, nx = map(int,x[i].shape)  # x(bs,255,20,20) to x(bs,3,20,20,85)# 修改2:对于reshape、view操作时,-1的指定请放到batch维度。其他维度可以计算出来即可。batch维度禁止指定为大于-1的明确数字。所以这里 bs 改为 -1.x[i] = x[i].view(-1, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()# export.py:model.train() if train else model.eval()  # 264行: train=False,  # model.train() mode 所以这里是 model.eval()模式, self.training 为 False,所以推理使用以下代码if not self.training:  # inferenceif self.grid[i].shape[2:4] != x[i].shape[2:4] or self.onnx_dynamic:self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)y = x[i].sigmoid()# inplace 操作是直接在 y 上进行操作(5.0 版本中使用该操作)if self.inplace:  y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xyy[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh# 非 inplace 操作,新建 xy, wh 变量,cat后赋值给 yelse:  # for YOLOv5 on AWS Inferentia https://github.com/ultralytics/yolov5/pull/2953# (1, 3(na), 80, 80, 2) + (1, 3(na), 80, 80, 2) * 8xy = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xy# (1, 3(na), 80, 80, 2) * (1, 3(na), 80, 80, 2)wh = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # why = torch.cat((xy, wh, y[..., 4:]), -1)  # -1 最后一个维度拼接 bs,3,80,80,85# 修改3:原因同 修改2。bs为-1,int(y.size(1)*y.size(2)*y.size(3)) 为 3*80*80或3*40*40或3*20*20z.append(y.view(-1, int(y.size(1)*y.size(2)*y.size(3)), self.no)) # -1,3*80*80,85  no = 85# 修改4:去掉下面的x, 节省内存,最终模型输出一个节点return x if self.training else torch.cat(z, 1)# grid 网格 如 3*3 网格def _make_grid(self, nx=20, ny=20, i=0):d = self.anchors[i].device  # 设备 cpu 或 'cuda:...'# yv = [0, 0, 0  #       1, 1, 1#       2, 2, 2]# xv = [0, 1, 2#       0, 1, 2#       0, 1, 2]yv, xv = torch.meshgrid([torch.arange(ny).to(d), torch.arange(nx).to(d)])# nx,ny,2 -> 1, na, nx,ny,2 # 参考:https://www.cnblogs.com/yanghailin/p/14329117.htmlgrid = torch.stack((xv, yv), 2).expand((1, self.na, ny, nx, 2)).float()# 把 anchors * stride 得到 anchor_grid 还原到原图大小 一个head:shape: na(3),2 -> 1, na, 1, 1, 2 -> 1, na, ny,nx,2anchor_grid = (self.anchors[i].clone() * self.stride[i]) \.view((1, self.na, 1, 1, 2)).expand((1, self.na, ny, nx, 2)).float()return grid, anchor_grid

导出 onnx 文件: 为了看模型各节点输出尺寸,我们加上  --simplify

python export.py --include onnx --simplify

YOLOv5有3个head,假设输入图像大小为640,那么 输出为:

Bs *128*80*80;Bs*256*40*40;Bs*512*20*20;

如下图中Detect所示:

注:这里onnx图中,Bs = 1。

Bs*128*80*80;   Bs*256*40*40;   Bs*512*20*20;    ------》》》

Bs*3*80*80*85;    Bs*3*40*40*85;    Bs*3*20*20*85;

注:3为每个cell 的Anchors数目,85 = x, y, w, h, obj, 80类 (排序就是这样)

推理模式:

export.py:264: train=False,  # model.train() mode

295:model.train() if train else model.eval()  # training mode = no Detect() layer grid construction  # 这里是 model.eval()

注:这里 Stride 为 模型下采样的倍数,YOLOv5 3个head 下采样为:8(80);16(40);32(20)。

上图中的 C_x与C_y 对应代码中的 self.grid;p_w*Stride和p_h*Stride对应代码中的 anchor_grid。

上图中的操作:

代码中是 for 循环三个head输出。下面以 80*80 head 为例说明:

问题:这里 w, h 为什么没有乘 Stride ? 原因:anchor_grid 已经是 640 尺度下的,故 anchor_grid大小隐含了 Stride(上面代码中: self.anchors[i].clone() * self.stride[i])。

所以,最后每个head输出:

上图中的操作放到 onnx 中做, 在C++ 推理decode该张量的时候就会很简单。后面C++代码中只需要 for 循环 25200次, 每次取出满足条件的;然后再 NMS,就结束了。


补充:避免使用inplace操作,例如y[…, 0:2] = y[…, 0:2] * 2 - 0.5

代码中:

                # 训练时候没影响,导出onnx问题很大,v6.0 默认使用的 非inplace 操作if self.inplace:y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xyy[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh

导出时候如果:export.py

m.inplace = inplace

parser.add_argument('--inplace', action='store_true', help='set YOLOv5 Detect() inplace=True')

python export.py --include onnx --inplace

上图可知,多了很多节点,并且还有 ScatterND节点,这种需要插件支持,这么复杂推理时候容易失败,所以不能 --inplace 导出。


目前未理解问题:yolo.py 中的  if self.grid[i].shape[2:4] != x[i].shape[2:4] : 是啥意思?

未解决:我尝试debug 找到 grid 在哪里进行的初始化,但是我并没有找到,只找到了:

models/experimental.py:  model.append(ckpt['ema' if ckpt.get('ema') else 'model'].float().fuse().eval())  # FP32 model   ->

models/yolo.py: m = self.model[-1]  # Detect()

然后被初始化为:

x : [1, 255, 4, 4],[1, 256, 2, 2],[1, 512, 1, 1]

self.grid: [1, 1, 84, 44, 2],[1, 1, 42, 22, 2],[1, 1, 21, 11, 2]

84, 44 != 4, 4,就画网格。

真正推理时候:x : [1, 3, 80, 80, 85], [1, 3, 40, 40, 85], [1, 3, 20, 20, 85]

84, 44 != 80, 80;42, 22 != 40,40;21, 11 != 20,20;

然后就画网格呗。

得到: self.grid[i]:1, na, ny, nx, 2;self.anchor_grid[i] : 1, na, ny, nx, 2


        # nx,ny,2 -> 1, na, ny,nx,2grid = torch.stack((xv, yv), 2).expand((1, self.na, ny, nx, 2)).float()# 把 anchors * stride 得到 anchor_grid 还原到原图大小 一个head:shape: na(3),2 -> 1, na, 1, 1, 2 -> 1, na, ny, nx, 2anchor_grid = (self.anchors[i].clone() * self.stride[i]) \.view((1, self.na, 1, 1, 2)).expand((1, self.na, ny, nx, 2)).float()

上面代码的维度变换的目的是为了能够和输出维度对应起来便于操作。


2. YOLOv5-5.0版本

YOLOv5-5.0版本 的默认onnx 导出没有进行上面的操作,也就是执行 python models/export.py 会得到3个输出。想得到 合并后的输出可以使用:

python models/export.py --grid

原因:

        self.training |= self.export
    parser.add_argument('--grid', action='store_true', help='export Detect() layer grid')model.model[-1].export = not opt.grid  # set Detect() layer grid export

然后按 6.0v 修改4处class Detect。

再使用上面命令导出,出现了 inplace 问题。修改代码如下,然后导出即可。

                # y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xy# y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh# z.append(y.view(bs, -1, self.no))xy = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xy  wh = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i].view(1, self.na, 1, 1, 2)  # why = torch.cat((xy, wh, y[..., 4:]), -1)z.append(y.view(-1, int(y.size(1)*y.size(2)*y.size(3)), self.no))return x if self.training else torch.cat(z, 1)

注: self.anchor_grid[i].view(1, self.na, 1, 1, 2) 做了维度变换才能和前面的相乘。


参考:

详解TensorRT的C++/Python高性能部署,实战应用到项目_哔哩哔哩_bilibili

【目标检测-YOLO】YOLOv5-yolov5s TensorRT部署准备之ONNX导出(第一篇)相关推荐

  1. 【目标检测-YOLO】YOLOv5-v6.0-网络架构详解(第二篇)

    参考:YOLOv5-5.0v-yaml 解析及模型构建(第二篇)_星魂非梦的博客-CSDN博客 前文:YOLOv5-v6.0-yolov5s网络架构详解(第一篇)_星魂非梦的博客-CSDN博客_yol ...

  2. 【目标检测-YOLO】博客阅读:Introduction to the YOLO Family

    阅读收获 本文是博客:https://pyimagesearch.com/2022/04/04/introduction-to-the-yolo-family/ 的翻译. 好吧!其实在这里面并没有太多 ...

  3. yolov5的TensorRT部署--warpaffine_cuda核函数

    从0到1实现基于tensorrt的yolo部署教程 http://t.csdn.cn/HUn4T,请点击该链接,即可看到全文 本文对于上面的案例,将预处理使用cuda核函数进行加速 一.cuda核函数 ...

  4. 深度学习目标检测:YOLOv5实现车辆检测(含车辆检测数据集+训练代码)

    深度学习目标检测:YOLOv5实现车辆检测(含车辆检测数据集+训练代码) 目录 深度学习目标检测:YOLOv5实现车辆检测(含车辆检测数据集+训练代码) 1. 前言 2. 车辆检测数据集说明 (1)车 ...

  5. 深度学习目标检测:YOLOv5实现红绿灯检测(含红绿灯数据集+训练代码)

    深度学习目标检测:YOLOv5实现红绿灯检测(含红绿灯数据集+训练代码) 目录 深度学习目标检测:YOLOv5实现红绿灯检测(含红绿灯数据集+训练代码) 1. 前言 2. 红绿灯检测数据集说明 (1) ...

  6. 【目标检测】YOLOv5能识别英雄和小兵?原理解析~

    目录 一.简介 二.模型结构 1.整体结构图 2.Backbone(CSPDarknet) 3.SPPF(Spatial Pyramid Pooling - Fast) 4.Neck(FPN+PAN) ...

  7. 深度学习目标检测---使用yolov5训练自己的数据集模型(Windows系统)

    目录 0    前言 1.从githab上克隆yolov5代码 1.1 yolov5网络project克隆 1.2 项目代码结构的整体介绍 1.3 深度学习环境的配置和安装yolov5所需要的库 2. ...

  8. yolov3网络结构图_目标检测——YOLO V3简介及代码注释(附github代码——已跑通)...

    GitHub: liuyuemaicha/PyTorch-YOLOv3​github.com 注:该代码fork自eriklindernoren/PyTorch-YOLOv3,该代码相比master分 ...

  9. 目标检测YOLO系列------YOLO简介

    目标检测YOLO系列------YOLO简介 1.为什么会出现YOLO算法 2.YOLO算法会逐渐成为目标检测的主流吗     YOLO以及各种变体已经广泛应用于目标检测算法所涉及到的方方面面,为了梳 ...

最新文章

  1. c#打开数据库连接池的工作机制_数据库连接池-tomcat-jdbc使用笔记
  2. MYSQL:多表联合查询的例子
  3. mp4v2再学习 -- H264视频编码成MP4文件
  4. Java 的插件框架 PF4J
  5. docker+kafka+zookeeper+zipkin的安装
  6. easy connect电脑版_北师大版小学英语六年级上册Unit4课文听力+翻译+单词录音跟读+高清课本(一起点)...
  7. python协程第一课(实现爬取自己博客)
  8. .net小插件:indent guides
  9. 米奇emoji_三星S9的AR Emoji迎新角色:迪士尼的米奇和米妮
  10. 安卓手機 adb shell常用命令
  11. Mac电脑如何添加打印机?
  12. python入门经典教程-Python经典入门教程ppt
  13. 2.微处理器:8088功能结构图【BIU 和 EU】 + 8088【内部各寄存器】的解释
  14. matlab7 fig exe 阴影,Matlab 生成完全独立运行的 EXE文件的问题请教
  15. android 消息轮训,Android消息机制Handler,有必要再讲一次
  16. java 年轻代算法_java内存模型 年轻代/年老代 持久区,jvm中的年轻代 老年代 持久代 gc...
  17. Pta——谷歌的招聘
  18. 十几加几不进位加法题C语言,十几加几(不进位))的加法教案
  19. 微信官方回应刷屏朋友圈的“个人影响度报告”:非官方功能,不会导致用户数据泄露...
  20. java百度地图离线LBS_Web版百度地图加载离线瓦片

热门文章

  1. 2023年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多
  2. C语言入门 | c语言基础知识
  3. 手动添加打印机的方法(hp laserjet p2055dn为例)
  4. 英特尔第11代处理器(Intel Tiger Lake) 疑难解答 - 安装Windows 10时找不到驱动器
  5. JVM深入学习(十六)-垃圾回收器的分类和性能指标
  6. 恋人/情人/性伴侣/红颜知己
  7. 计算机视觉课程设计:基于SSD、Dlib多进程目标检测的对比研究
  8. Android中dp、sp、px、pt之间的换算关系
  9. Android传感器、语音识别、定位系统、Google Map API、快捷方式、widget编程总结及示例
  10. 操作系统【用户接口】命令解释程序的主要功能、系统调用与一般过程调用的不同之处、系统调用的参数传递方式、系统调用的处理步骤