转载自:https://zhuanlan.zhihu.com/p/606440867
大家可以去以上链接浏览,我只是怕原作把文章删除了。。哭。。
背景知识:
YOLOv5 升级迭代比较快,在知乎上搜索相关 NCNN 的教程,排在前两位的就是 NCNN 作者 nihui 大佬的文章了。
链接:
- 详细记录u版YOLOv5目标检测ncnn实现 - nihui的文章 - 知乎 https://zhuanlan.zhihu.com/p/275989233
2. 详细记录u版YOLOv5目标检测ncnn实现(第二版) - nihui的文章 - 知乎 https://zhuanlan.zhihu.com/p/471357671
本文的思路基于上两篇文章,但是稍稍有所不同。
本着用新不用旧的思想,本文将基于当前时间最新的 YOLOv5 进行模型转换和模型推理部署。

版本号 v7.0

commit id: 4d28fec3b8b663fa8225634ca8eeb4446505527e
0x1 缘由
YOLOv5 部署老生常谈的问题有:
- 前处理(letterbox resize)
- Focus 模块(在 v7.0 版本已经没有了)
- 后处理 (包括框解码和 NMS)
问题 1 代码层面 NCNN 已经有了很好的解决方案,并且支持动态形状。
问题 3 由于当前最新的 YOLOv5 中的 export.py 去掉了 --train 参数,导致这部分要自己动手修改源码了。
0x2 pytorch测试和导出torchscript
首先是下载当前时间最新的 YOLOv5 ,当前时间是 : 2023-02-15 10:35
git clone https://github.com/ultralytics/yolov5 # clone
cd yolov5
git checkout 4d28fec3b8b663fa8225634ca8eeb4446505527e # switch to commit id
pip install -r requirements.txt # install
老样子,用默认的配置和预训练模型推理一张图片:
python detect.py --source data/bus.jpg --weights yolov5s.pt --view-img
接下来动源码,models/yolo.py 中的 56-79 行注释掉,换成下面的内容:
# def forward(self, x):
# z = [] # inference output
# for i in range(self.nl):
# x[i] = self.m[i](x[i]) # conv
# bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
# x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
#
# if not self.training: # inference
# if self.dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]:
# self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)
#
# if isinstance(self, Segment): # (boxes + masks)
# xy, wh, conf, mask = x[i].split((2, 2, self.nc + 1, self.no - self.nc - 5), 4)
# xy = (xy.sigmoid() * 2 + self.grid[i]) * self.stride[i] # xy
# wh = (wh.sigmoid() * 2) ** 2 * self.anchor_grid[i] # wh
# y = torch.cat((xy, wh, conf.sigmoid(), mask), 4)
# else: # Detect (boxes only)
# xy, wh, conf = x[i].sigmoid().split((2, 2, self.nc + 1), 4)
# xy = (xy * 2 + self.grid[i]) * self.stride[i] # xy
# wh = (wh * 2) ** 2 * self.anchor_grid[i] # wh
# y = torch.cat((xy, wh, conf), 4)
# z.append(y.view(bs, self.na * nx * ny, self.no))
#
# return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x)
def forward(self, x):
z = [] # inference output
for i in range(self.nl):
feat = self.m[i](x[i]) # conv
# x(bs,255,20,20) -> x(bs,20,20,255)
feat = feat.permute(0, 2, 3, 1).contiguous()
z.append(feat.sigmoid())
return tuple(z)
导出 torchscript:
python export.py --weights yolov5s.pt --include torchscript
会在当前工作目录下生成 yolov5s.torchscript 。
用 Netron 看一眼清爽的后处理长什么样:

会有三个上述的结构,这是我们修改的 forward 中的内容,由于 PNNX 会把最后的 permute reshape 干掉,所以不得已只能也把 sigmoid 导出了。据说模型最后是 sigmoid softmax 会导致量化掉点,等我之后实验再说。
0x3 转换
下载编译好的 pnnx 工具包:PNNX
指定 inputshape 并且额外指定 inputshape2 转换成支持动态 shape 输入的模型:
./pnnx yolov5s.torchscript inputshape=[1,3,640,640] inputshape2=[1,3,320,320]
转换日志:

转换主要产物:

看一眼 yolov5s.ncnn.param:


in0 对应输入,尺寸是 3*640*640 或者动态输入的尺寸。
out0 对应输出,尺寸是 80*80*255 或者动态输出的尺寸。
out1 对应输出,尺寸是 40*40*255 或者动态输出的尺寸。
out2 对应输出,尺寸是 20*20*255 或者动态输出的尺寸。
注意,本文用pnnx转换后的模型,尾巴上有permute和sigmoid层。
0x4 u版YOLOv5后处理
anchor信息是在 models/yolov5s.yaml:
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
pytorch的后处理在 models/yolo.py Detect类 forward函数,也就是我们注释掉的部分,对着它改写成 cpp。
与其他 YOLOv5 教程不同的是,本文最后的输出形状:
C = 80 或 40 或 20
H = 80 或 40 或 20
W = 255 = 85 * 3,对应于 bbox 的 dx, dy, dw, dh, bbox 置信度,80 个分类的分数,3 对应于三种 anchor。
ncnn实现代码和转好的模型已上传到github:
GitHub - triple-Mu/ncnn-examples: Learning ncnn with some examples
使用方式:
git clone https://github.com/triple-Mu/ncnn-examples.git
cd ncnn-examples/yolov5
mkdir build
cmake .. && make -j$(nproc)
mv triplemu-yolov5 .. & cd ..
./triplemu-yolov5
0x5 动态尺寸推理
与其他的教程相同,本文也支持动态输入尺寸,更多的探索请查看源码 src/triplemu-yolov5.cpp 。
0x6 总结
允许在不修改内容前提下转载本文!!
欢迎大家通过 triple-Mu 联系我哈~
顺便宣传一下我的 YOLOv8-TensorRT 部署仓库:
GitHub - triple-Mu/YOLOv8-TensorRT: YOLOv8 using TensorRT accelerate !
1079

被折叠的 条评论
为什么被折叠?



