
扫码添加官方竞赛小助手,加入竞赛交流群
添加请备注:姓名-公司/学校-参与赛题名
来源: 自来水大叔: 欢迎关注;如有侵权,请联系删除~
u版的意思是指Ultralytics开源的yolov5实现
https://github.com/ultralytics/yolov5github.com
1. 缘由
pytorch YOLOv5转换 openvino 的实现在github 上搜下有不少,但是直接拿来用或多或少都有些问题,原因在在于u版YOLOv5更新太快了,很多转换代码有些过时了,同时不同版本的onnx对某些算子无法支持,最重要的是没有一个完整全流程的实现,所以我决定自己撸一把,下面废话少说,直接开干
2. pytorch 转换onnx
下载u版的YOLOv5仓库后,使用自己的数据集,修改对应的yaml,然后训练模型,通过调用detect.py观察模型的检测结果,
python detect.py --source 000201.jpg --weights best.pt --conf 0.25
看起来结果正常,下面开始pytorch转onnx
先简单介绍下模型的配置文件yolov5s.yaml
# parameters
nc: 80 # number of classes 训练数据集的类别数,需要修改
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
# anchors
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
depth_multiple和width_multiple 为模型参数,无需修改
在训练模型开始之前会评估anchor与训练数据集的匹配程度,如果不满足要求则进行锚框的自动计算训练过程,所以在infer的时候anchor可能会发生变化
# YOLOv5 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Focus, [64, 3]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
[-1, 3, BottleneckCSP, [128]],
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 9, BottleneckCSP, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, BottleneckCSP, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 1, SPP, [1024, [5, 9, 13]]],
[-1, 3, BottleneckCSP, [1024, False]], # 9
上面这部分是YOLOv5的backbone部分,需要注意的是Focus模块,直接查看代码
class Focus(nn.Module):
# Focus wh information into c-space
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
super(Focus, self).__init__()
self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
可以看出Focus实现的是将输入切分成4份,然后在channel维度进行拼接,这样可以实现下采样为原来的1/2,之后再进行卷积操作,这样做的目的是为了减少下采样带来的信息损失
# YOLOv5 head
head:
[[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1, 3, BottleneckCSP, [512, False]], # 13
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 14], 1, Concat, [1]], # cat head P4
[-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large)
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
从head部分可以看出detect层有三个输出,分别对应的是输入缩小8,16,32倍之后的feature map,需要注意的是nn.Upsample和Detect层,在pytorch转onnx中原始的转化代码为
torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'],output_names=['classes', 'boxes'] if y is None else ['output'])
opset_version =12在转换nn.Upsample会报错,需要修改为opset_version=10
而对于detect层而言
def forward(self, x):
# x = x.copy() # for profiling
z = [] # inference output
self.training |= self.export
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.grid[i].shape[2:4] != x[i].shape[2:4]:
self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
y = x[i].sigmoid()
y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * 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))
return x if self.training else (torch.cat(z, 1), x)
在pytorch转换为onnx中,在export.py中
model.model[-1].export = True # set Detect() layer export=True
也就是说if not self.training:之后的代码不会执行,所以最后转为onnx的模型会有三个输出,分别对应于输入图像缩小8,16,32倍之后的feature map,如果输入图像的大小为512,输出应该为(1,3,64,64,class+5), (1,3,32,32,class+5), (1,3,32,32,class+5)
所以将原始代码修改为
torch.onnx.export(model, img, f, verbose=False, opset_version=10, input_names=['images'],output_names=['classes', 'boxes'] if y is None else ['output1', 'output2', 'output3'])
这样就可以导出 onnx,然后用 onnx-simplifer 简化模型
python models/export.py --weights best.pt --img 512 --batch 1
python -m onnxsim best.onnx best-sim.onnx
3.onnx转openvino
3.1激活环境:
在windows下安装的openvino,
C:\Program Files (x86)\Intel\openvino_2021.1.110\bin\setupvars.bat
3.2安装依赖
模型转换的脚本在目录
C:\Program Files (x86)\Intel\openvino_2021\deployment_tools\model_optimizer
里面包含了tf,caffe,mxnet,onnx等框架转换脚本
pip install -r requirements_onnx.txt
将onnx模型转换为openvino模型:
python mo.py --input_model F:\\projects\\detect\\best-sim.onnx --output_dir F:\\projects\\detect\\ --input_shape [1,3,512,512] --data_type FP16
在模型输出路径下生成了 best-sim.bin 和 best-sim.xml 文件,数据格式为float16
3.3实现openvino的python脚本以及c++脚本,需要注意:
3.3.1颜色通道转换
鉴于opencv和pytorch的颜色通道差异,opencv是BGR通道,pytorch是RGB,在输入网络之前,需要进行通道转换.
3.3.2图像resize的方式
图像缩放的时候需要按图像原始比例将图像的长或宽缩放到512.假设长被放大到512,宽按照长的变换比例无法达到512,则在图像的两边填充黑边确保输入图像总尺寸为512*512,此外还要注意的是使用这种方式在得到检测框后需要将检测框的坐标转换为在原始图像中的坐标,笔者使用这种方式实现的精度要优于将输入图像直接resize到512*512的方式,笔者认为这是由于这种粗暴的方式会造成部分物体失真变形,导致识别准确率会受到影响
3.3.3使用openmp实现图像处理的加速
omp_set_num_threads(4);
#pragma omp parallel for
需要加速的代码
4.耗时比较
测试分辨率大小为512*512的图像在cpu上的forward耗时,统计100次求平均
pytorch耗时0.69s
onnx耗时0.16s
openvino耗时0.1s
可以看出openvino的加速效果还是不错的
如果有任何细节问题欢迎可以私信笔者讨论
如有侵权,请联系删除~
相关推荐:
英特尔openvino中级认证:英特尔OpenVINO™(中级)认证地址:https://www.cvmart.net/list/OV2020