电子商务书城网站建设方案,做网站宣传费用记什么科目,网站设计框架,ui下载高清素材的网站有哪些最近在微信公众号里看到多篇讲解yolov5在openvino部署做目标检测文章#xff0c;但是没看到过用opencv的dnn模块做yolov5目标检测的。于是#xff0c;我就想着编写一套用opencv的dnn模块做yolov5目标检测的程序。在编写这套程序时#xff0c;遇到的bug和解决办法#xff0c…
最近在微信公众号里看到多篇讲解yolov5在openvino部署做目标检测文章但是没看到过用opencv的dnn模块做yolov5目标检测的。于是我就想着编写一套用opencv的dnn模块做yolov5目标检测的程序。在编写这套程序时遇到的bug和解决办法在这篇文章里讲述一下。 在yolov5之前的yolov3和yolov4的官方代码都是基于darknet框架的实现的因此opencv的dnn模块做目标检测时读取的是.cfg和.weight文件那时候编写程序很顺畅没有遇到bug。但是yolov5的官方代码(https://github.com/ultralytics/yolov5)是基于pytorch框架实现的但是opencv的dnn模块不支持读取pytorch的训练模型文件的。如果想要把pytorch的训练模型.pth文件加载到opencv的dnn模块里需要先把pytorch的训练模型.pth文件转换到.onnx文件然后才能载入到opencv的dnn模块里。
因此用opencv的dnn模块做yolov5目标检测的程序包含两个步骤(1).把pytorch的训练模型.pth文件转换到.onnx文件。(2).opencv的dnn模块读取.onnx文件做前向计算。
(1).把pytorch的训练模型.pth文件转换到.onnx文件
在做这一步时我得吐槽一下官方代码https://github.com/ultralytics/yolov5这套程序里的代码混乱在pytorch里通常是在.py文件里定义网络结构的但是官方代码是在.yaml文件定义网络结构利用pytorch动态图特性解析.yaml文件自动生成网络结构。在.yaml文件里有depth_multiple和width_multiple它是控制网络的深度和宽度的参数。这么做的好处是能够灵活的配置网络结构但是不利于理解网络结构假如你想设断点查看某一层的参数和输出数值那就没办法了。因此在我编写的转换到.onnx文件的程序里网络结构是在.py文件里定义的。其次在官方代码里还有一个奇葩的地方那就是.pth文件。起初我下载官方代码到本地运行时torch.load读取.pth文件总是出错后来把pytorch升级到1.7就读取成功了。可以看到版本兼容性不好这是它的一个不足之处。设断点查看读取的.pth文件里的内容可以看到ultralytics的.pt文件里既存储有模型参数也存储有网络结构还储存了一些超参数包括anchors,stride等等的。第一次见到有这种操作的通常情况下.pth文件里只存储了训练模型参数的。
查看models\yolo.py里的Detect类在构造函数里有这么两行代码 我尝试过把这两行代码改成self.anchors a 和 self.anchor_grid a.clone().view(self.nl, 1, -1, 1, 1, 2)程序依然能正常运行但是torch.save保存模型文件后可以看到.pth文件里没有存储anchors和anchor_grid了在百度搜索register_buffer解释是pytorch中register_buffer模型保存和加载的时候可以写入和读出。
在这两行代码的下一行 它的作用是做特征图的输出通道对齐通过1x1卷积把三种尺度特征图的输出通道都调整到 num_anchors*(num_classes5)。
阅读Detect类的forward函数代码可以看出它的作用是根据偏移公式计算出预测框的中心坐标和高宽这里需要注意的是计算高和宽的代码
pwh (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]没有采用exp操作而是直接乘上anchors[i]这是yolov5与yolov3v4的一个最大区别(还有一个区别就是在训练阶段的loss函数里yolov5采用邻域的正样本anchor匹配策略增加了正样本。其它的是一些小区别比如yolov5的第一个模块采用FOCUS把输入数据2倍下采样切分成4份在channel维度进行拼接然后进行卷积操作,yolov5的激活函数没有使用Mish)。
现在可以明白Detect类的作用是计算预测框的中心坐标和高宽简单来说就是生成proposal作为后续NMS的输入进而输出最终的检测框。我觉得在Detect类里定义的1x1卷积是不恰当的应该把它定义在Detect类的外面紧邻着Detect类之前定义1x1卷积。
在官方代码里有转换到onnx文件的程序
python models/export.py --weights yolov5s.pt --img 640 --batch 1在pytorch1.7版本里程序是能正常运行生成onnx文件的。观察export.py里的代码在执行torch.onnx.export之前有这么一段代码 注意其中的for循环我试验过注释掉它重新运行就会出错打印出的错误如下 由此可见这段for循环代码是必需的。SiLU其实就是swish激活函数而在onnx模型里是不直接支持swish算子的因此在转换生成onnx文件时SiLU激活函数不能直接使用nn.Module里提供的接口而需要自定义实现它。
(2).opencv的dnn模块读取.onnx文件做前向计算
在生成.onnx文件后就可以用opencv的dnn模块里的cv2.dnn.readNet读取它。然而在读取时出现了如下错误 我在百度搜索这个问题的解决办法看到一篇知乎文章(Pytorch转ONNX-实战篇2实战踩坑总结 - 知乎)文章里讲述的第一条 于是查看yolov5的代码在common.py文件的Focus类,torch.cat的输入里有4次切片操作代码如下 那么现在需要更换索引式的切片操作观察到注释的Contract类它就是用view和permute函数完成切片操作的于是修改代码如下 其次在models\yolo.py里的Detect类里也有切片操作代码如下 前面说过Detect类的作用是计算预测框的中心坐标和高宽生成proposal这个是属于后处理的因此不需要把它写入到onnx文件里。
总结一下按照上面的截图代码修改Focus类把Detect类里面的1x1卷积定义在紧邻着Detect类之前的外面然后去掉Detect类组成新的model作为torch.onnx.export的输入
torch.onnx.export(model, inputs, output_onnx, verboseFalse, opset_version12, input_names[‘images’], output_names[‘out0’, ‘out1’, ‘out2’])
最后生成的onnx文件opencv的dnn模块就能成功读取了接下来对照Detect类里的forward函数用python或者C编写计算预测框的中心坐标和高宽的功能。
周末这两天我在win10cpu机器里编写了用opencv的dnn模块做yolov5目标检测的程序包含Python和C两个版本的。程序都调试通过了运行结果也是正确的。我把这套代码发布在github上地址是
https://github.com/hpc203/yolov5-dnn-cpp-python
后处理模块python版本用numpy array实现的C版本的用vector和数组实现的整套程序只依赖opencv库(opencv4版本以上的)就能正常运行彻底摆脱对深度学习框架pytorch,tensorflow,caffe,mxnet等等的依赖。用openvino作目标检测需要把onnx文件转换到.bin和.xml文件相比于用dnn模块加载onnx文件做目标检测是多了一个步骤的。因此我就想编写一套用opencv的dnn模块做yolov5目标检测的程序用opencv的dnn模块做深度学习目标检测在win10和ubuntu在cpu和gpu上都能运行可见dnn模块的通用性更好很接地气。
生成yolov5s_param.pth 的步骤首先下载https://github.com/ultralytics/yolov5 的源码到本地在yolov5-master主目录(注意不是我发布的github代码目录)里新建一个.py文件把下面的代码复制到.py文件里
import torch
from collections import OrderedDict
import pickle
import osdevice cuda if torch.cuda.is_available() else cpuif __name____main__:choices [yolov5s, yolov5l, yolov5m, yolov5x]modelfile choices[0].ptutl_model torch.load(modelfile, map_locationdevice)utl_param utl_model[model].modeltorch.save(utl_param.state_dict(), os.path.splitext(modelfile)[0]_param.pth)own_state utl_param.state_dict()print(len(own_state))numpy_param OrderedDict()for name in own_state:numpy_param[name] own_state[name].data.cpu().numpy()print(len(numpy_param))with open(os.path.splitext(modelfile)[0]_numpy_param.pkl, wb) as fw:pickle.dump(numpy_param, fw)运行这个.py文件这时候就可以生成yolov5s_param.pth文件。之所以要进行这一步我在上面讲到过ultralytics的.pt文件里既存储有模型参数也存储有网络结构还储存了一些超参数包括anchors,stride等等的。torch.load加载ultralytics的官方.pt文件也就是utl_model torch.load(modelfile, map_locationdevice)这行代码在这行代码后设断点查看utl_model里的内容截图如下 可以看到utl_model里含有既存储有模型参数也存储有网络结构还储存了一些超参数等等的这会严重影响转onnx文件。此外我还发现如果pytorch的版本低于1.7那么在torch.load加载.pt文件时就会出错的。
因此在程序里我把模型参数转换到cpu.numpy形式的最后保存在.pkl文件里。这时候在win10系统cpu环境里即使你的电脑没有安装pytorch也能通过python程序访问到模型参数。
pytorch转onnx常见坑
onnx只能输出静态图因此不支持if-else分支。一次只能走一个分支。如果代码中有if-else语句需要改写。onnx不支持步长为2的切片。例如a[::2,::2]onnx不支持对切片对象赋值。例如a[0,:,:,:]b 可以用torch.cat改写onnx里面的resize要求output shape必须为常量。可以用以下代码解决
if isinstance(size, torch.Size): size tuple(int(x) for x in size)
此外在torch.onnx.export(model, inputs, output_onnx)的输入参数model里应该只包含网络结构也就是说model里只含有nn.Conv2d, nn.MaxPool2d, nn.BatchNorm2d, F.relu等等的这些算子组件而不应该含有后处理模块的。图像预处理和后处理模块需要自己使用C或者Python编程实现。
在明白了这些之后在转换生成onnx文件你需要执行两个步骤第一步把原始训练模型.pt文件里的参数保存到新的.pth文件里第二步编写yolov5.py文件把yolov5的往来结构定义在.py文件里此时需要注意网络结构里不能包含切片对象赋值操作F.interpolate里的size参数需要加int强制转换。在执行完这两步之后才能生成一个opencv能成功读取并且做前向推理的onnx文件。
不过最近我发现在yolov5-pytorch程序里其实可以直接把原始训练模型.pt文件转换生成onnx文件的而且我在一个yolov5检测人脸关键点的程序里实验成功了。
这套程序发布在github上地址是
https://github.com/hpc203/yolov5-face-landmarks-opencv
https://github.com/hpc203/yolov5-face-landmarks-opencv-v2
这套程序只依赖opencv库就可以运行yolov5检测人脸关键点程序依然是包含C和Python两个版本的这套程序里还有一个转换生成onnx文件的python程序文件。只需运行这一个.py文件就可以生成onnx文件而不需要之前讲的那样执行两个步骤这样大大简化了生成onnx文件的流程使用方法可以阅读程序里的README文档。
在这个新的转换生成onnx文件的程序里需要重新定义yolov5网络结构主要是修改第一个模块Focus用Contract类替换索引式的切片操作在最后一个模块Detect类里只保留三个1x1卷积剩下的make_grid和decode属于后处理不能包含在网络结构里代码截图如下 如果要转换生成onnx文件需要设置export True这时候Detect模块的forward就只进行1x1卷积这时的网络结构就可以作为torch.onnx.export(model, inputs, output_onnx)的输入参数model。不过由于ultralytics的yolov5代码仓库几乎每天都在更新因此你现在看到的ultralytics的yolov5里的Detect类很有可能不是这么写的那这是需要你手动修改程序然后再运行。
看到最近旷视发布的anchor-free系列的YOLOX而在github开源的代码里并没有使用opencv部署的程序。因此我就编写了一套使用OpenCV部署YOLOX的程序支持YOLOX-S、YOLOX-M、YOLOX-L、YOLOX-X、YOLOX-Darknet53五种结构包含C和Python两种版本的程序实现。在今天我在github发布了这套程序地址是
https://github.com/hpc203/yolox-opencv-dnn
在旷视发布的YOLOX代码里提供了在COCO数据集上训练出来的.pth模型文件并且也提供了导出onnx模型的export_onnx.py文件起初我运行export_onnx.py生成onnx文件之后Opencv读取onnx文件失败了报错原因跟文章最开始的第(2)节里的一样这说明在YOLOX的网络结构里有切片操作经过搜索后在 yolox\models\network_blocks.py 里有个Focus类它跟YOLOv5里的Focus是一样的都是把输入张量切分成4份然后concatconv。这时按照第(2)节里讲述的解决办法修改Focus类重新运行export_onnx.py生成onnx文件Opencv读取onnx文件就不会再出错了。
在github发布了一套使用OpenCV部署Yolo-FastestV2的程序依然是包含C和Python两种版本的程序实现。地址是
https://github.com/hpc203/yolo-fastestv2-opencv
经过运行体验到这个Yolo-FastestV2的速度确实很快而且onnx文件只有957kb大小不超过1M。在官方代码https://github.com/dog-qiuqiu/Yolo-FastestV2里学习它的网络结构。设断点调试查看中间变量可以看到在model/detector.py网络输出了6个张量 它们的形状分别是
torch.Size([1, 12, 22, 22]) torch.Size([1, 3, 22, 22]) torch.Size([1, 80, 22, 22]) torch.Size([1, 12, 11, 11]) torch.Size([1, 3, 11, 11]) torch.Size([1, 80, 11, 11])
结合配置文件data/coco.data可以看到模型输入是352x352的图片而输出有22x22和11x11这两种尺度的特征图这说明Yolo-FastestV2的输出只有缩放16倍和缩放32倍这两种尺度的特征图比yolov3v4v5系列的都要少一个尺度特征图。其次在配置文件data/coco.data还可以看到anchor一共有6个分别给两个尺度特征图里的网格点分配3个。观察输出的6个张量的形状信息很明显前3个张量是22x22尺度特征图的检测框坐标回归量bbox_reg检测框目标置信度obj_conf检测框类别置信度cls_conf。由于给每个网格点分配3个anchor检测框坐标包含(center_x, center_y, width, height)因此维数是4312这也就明白了bbox_reg的第1个维度是12obj_conf的第1个维度是3而COCO数据集有80类那么cls_conf的第1个维度应该是380240但是在上面调试信息里显示的是80类。继续设断点调试代码在utils/utils.py里第326行有这么一行代码 类别置信度复制了3份结合这个后处理代码可以看出类别置信度对3个anchor是共享的。
在观察出Yolo-FastestV2的这些特性之后可以理解为何它的速度快和模型文件小的原因了。主要是因为它的输入图片尺寸比传统yolov3v4v5系列的要小它的输出特征图尺寸个数也比传统yolo的要少最后对网格点上的3个anchor是共享类别置信度的这也减少了特种通道数。
8月29日我在github发布了一套使用OpenCV部署全景驾驶感知网络YOLOP可同时处理交通目标检测、可驾驶区域分割、车道线检测三项视觉感知任务依然是包含C和Python两种版本的程序实现。地址是
https://github.com/hpc203/YOLOP-opencv-dnn
在这里我讲一下生成onnx文件需要注意的地方YOLOP的官方代码地址是 https://github.com/hustvl/YOLOP 它是华中科技大学视觉团队发布的它的代码是使用pytorch作为深度学习框架。仔细阅读和运行调试他的代码可以看出它的代码是在ultralytics的yolov5里修改的添加了可行驶区域分割和车道线分割这两个分割头在bdd100k数据集上的训练的不过YOLOP的检测类别只保留了bdd100k数据集里的车辆这一个类别。生成onnx文件第一步是把我发布的代码里的export_onnx.py拷贝到https://github.com/hustvl/YOLOP的主目录里。第二步在https://github.com/hustvl/YOLOP的主目录里打开lib/models/common.py首先修改Focus类原始的Focus类的forward函数里是由切片操作的那么这时按照第(2)节里讲述的解决办法修改Focus类示例代码如下
class Contract(nn.Module):# Contract width-height into channels, i.e. x(1,64,80,80) to x(1,256,40,40)def __init__(self, gain2):super().__init__()self.gain gaindef forward(self, x):N, C, H, W x.size() # assert (H / s 0) and (W / s 0), Indivisible gains self.gainx x.view(N, C, H // s, s, W // s, s) # x(1,64,40,2,40,2)x x.permute(0, 3, 5, 1, 2, 4).contiguous() # x(1,2,2,64,40,40)return x.view(N, C * s * s, H // s, W // s) # x(1,256,40,40)class Focus(nn.Module):# Focus wh information into c-space# slice concat convdef __init__(self, c1, c2, k1, s1, pNone, g1, actTrue): # ch_in, ch_out, kernel, stride, padding, groupssuper(Focus, self).__init__()self.conv Conv(c1 * 4, c2, k, s, p, g, act)self.contract Contract(gain2)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))return self.conv(self.contract(x))接下来修改Detect类里的forward函数示例代码如下
def forward(self, x):if not torch.onnx.is_in_onnx_export():z [] # inference outputfor i in range(self.nl):x[i] self.m[i](x[i]) # conv# print(str(i)str(x[i].shape))bs, _, ny, nx x[i].shape # x(bs,255,w,w) to x(bs,3,w,w,85)x[i] x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()# print(str(i)str(x[i].shape))if not self.training: # inferenceif 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()# print(**)# print(y.shape) #[1, 3, w, h, 85]# print(self.grid[i].shape) #[1, 3, w, h, 2]y[..., 0:2] (y[..., 0:2] * 2. - 0.5 self.grid[i].to(x[i].device)) * self.stride[i] # xyy[..., 2:4] (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # whprint(**)print(y.shape) #[1, 3, w, h, 85]print(y.view(bs, -1, self.no).shape) #[1, 3*w*h, 85]z.append(y.view(bs, -1, self.no))return x if self.training else (torch.cat(z, 1), x)else:for i in range(self.nl):x[i] self.m[i](x[i]) # conv# print(str(i)str(x[i].shape))bs, _, ny, nx x[i].shape # x(bs,255,w,w) to x(bs,3,w,w,85)x[i] x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()x[i] torch.sigmoid(x[i])x[i] x[i].view(-1, self.no)return torch.cat(x, dim0)RUBY 复制 全屏修改完之后运行export_onnx.py就能生成onnx文件并且opencv读取正常的。
9月18日我在github上发布了一套使用ONNXRuntime部署anchor-free系列的YOLOR依然是包含C和Python两种版本的程序。起初我是想使用OpenCV部署的但是opencv读取onnx文件总是出错于是我换用ONNXRuntime部署。地址是
https://github.com/hpc203/yolor-onnxruntime