用opencv的dnn模块做yolov5目标检测_opencv yolov5-程序员宅基地

技术标签: 深度学习推理部署  dnn  目标检测  opencv  

深度学习推理部署,真好玩

最近在微信公众号里看到多篇讲解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_classes+5)。

阅读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, verbose=False, opset_version=12, input_names=['images'], output_names=['out0', 'out1', 'out2'])

最后生成的onnx文件,opencv的dnn模块就能成功读取了,接下来对照Detect类里的forward函数,用python或者C++编写计算预测框的中心坐标和高宽的功能。

周末这两天,我在win10+cpu机器里编写了用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 os

device = 'cuda' if torch.cuda.is_available() else 'cpu'

if __name__=='__main__':
    choices = ['yolov5s', 'yolov5l', 'yolov5m', 'yolov5x']
    modelfile = choices[0]+'.pt'
    utl_model = torch.load(modelfile, map_location=device)
    utl_param = utl_model['model'].model
    torch.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_location=device)这行代码,在这行代码后设断点查看utl_model里的内容,截图如下

可以看到utl_model里含有既存储有模型参数,也存储有网络结构,还储存了一些超参数等等的,这会严重影响转onnx文件。此外,我还发现,如果pytorch的版本低于1.7,那么在torch.load加载.pt文件时就会出错的。

此外,有读者反映,在Focus类的构造函数里添加了 self.contract = Contract(gain=2) ,之后在forward函数里报错,错误信息是没有contract这个成员。之所以会出现这个错误,原因正如上面所说的在ultralytics的.pt文件里既存储有模型参数,也存储有网络结构。因而在Focus类的构造函数里添加的成员是不会生效的,做一个小实验来验证,在forward函数设断点,运行,截图如下

 可以看到self里并没有contract这个成员。想要在导出onnx文件时不出错的做法是,代码如下:

class Focus(nn.Module):  
    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)
        #self.contract = Contract(gain=2)

    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)
        if torch.onnx.is_in_onnx_export():
            contract = Contract(gain=2)
            return self.conv(contract(x))
        else:
            return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))

因此在程序里,我把模型参数转换到cpu.numpy形式的,最后保存在.pkl文件里。这时候在win10系统cpu环境里,即使你的电脑没有安装pytorch,也能通过python程序访问到模型参数。

pytorch转onnx常见坑:

1. onnx只能输出静态图,因此不支持if-else分支。一次只能走一个分支。如果代码中有if-else语句,需要改写。
2. onnx不支持步长为2的切片。例如a[::2,::2]
3. onnx不支持对切片对象赋值。例如a[0,:,:,:]=b, 可以用torch.cat改写
4. 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检测人脸+关键点的程序里实验成功了。

        5月1日,我把这套程序发布在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类很有可能不是这么写的,那这是需要你手动修改程序,然后再运行。

        8月8日,看到最近旷视发布的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份,然后concat+conv。这时按照第(2)节里讲述的解决办法,修改Focus类,重新运行export_onnx.py生成onnx文件,Opencv读取onnx文件就不会再出错了。

        8月22日,我在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倍这两种尺度的特征图,比yolov3,v4,v5系列的都要少一个尺度特征图。其次在配置文件data/coco.data还可以看到anchor一共有6个,分别给两个尺度特征图里的网格点分配3个。观察输出的6个张量的形状信息,很明显前3个张量是22x22尺度特征图的检测框坐标回归量bbox_reg,检测框目标置信度obj_conf,检测框类别置信度cls_conf。由于给每个网格点分配3个anchor,检测框坐标包含(center_x, center_y, width, height),因此维数是4*3=12,这也就明白了bbox_reg的第1个维度是12,obj_conf的第1个维度是3,而COCO数据集有80类,那么cls_conf的第1个维度应该是3*80=240,但是在上面调试信息里显示的是80类。继续设断点调试代码,在utils/utils.py里,第326行有这么一行代码

类别置信度复制了3份,结合这个后处理代码,可以看出类别置信度对3个anchor是共享的。

        在观察出Yolo-FastestV2的这些特性之后,可以理解为何它的速度快和模型文件小的原因了。主要是因为它的输入图片尺寸比传统yolov3,v4,v5系列的要小,它的输出特征图尺寸个数,也比传统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
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/nihate/article/details/112731327

智能推荐

服务器发送信息给arduino,arduino通过esp8266模块发送数据到云服务器-程序员宅基地

文章浏览阅读1.4k次,点赞2次,收藏25次。arduino通过esp8266模块发送数据到云服务器我是代码小白,一个正在做毕设的秃头少年。鄙人拙作,有不当之处,还请指教。最近买了一套arduino设备,打算做一个物联网设备小玩意,可是怎么把数据上传到云服务器可愁坏我了。通过对比实验,我决定用esp8266wifi模块进行通信。云服务器的话,我现在还没写相应的代码,所以先用Onenet平台进行配置。Onenet平台进行配置1.进入Onenet..._esp8266 arduino wifi发送数据

latex h t b p是什么意思_latex htpb-程序员宅基地

文章浏览阅读1.4w次,点赞9次,收藏17次。常用选项[htbp]是浮动格式:『h』当前位置。将图形放置在正文文本中给出该图形环境的地方。如果本页所剩的页面不够,这一参数将不起作用。『t』顶部。将图形放置在页面的顶部。『b』底部。将图形放置在页面的底部。『p』浮动页。将图形放置在一只允许有浮动对象的页面上。在table或者figure 后加 [!htb] 是系统忽略“美学”标准,把表格和图片插入到你的代码中,是动的,但是不加感叹号,它就是按顺序选择h(此处),t(上方),b(下方),所以为了让图片随着你的代码移动,最好加一个[!htb]_latex htpb

【转载】linux下的usb抓包方法-程序员宅基地

文章浏览阅读67次。1 linux下的usb抓包方法1、配置内核使能usb monitor:make menuconfigDevice Drivers --> USB Support --> USB Monitor --> Sel..._linux安装tcpdump 查看usb

计算机组成pc em ir,计算机组成 课程设计报告.doc-程序员宅基地

文章浏览阅读164次。计算机组成 课程设计报告计算机组成原理课程设计报告姓 名:班 级:学 号:指导老师:2016年 6月31日目 录第一章 背景知识与课设任务概述11.1课设目的11.2课设任务11.2111.2211.2321.2421.252第二章 课设内容32.1指令的执行流程32.1.132.1.242.1.352.2存储器62.2.162.3运算器72.3.172.4硬件系统组成122.4..._计算机组成课程设计报告

python青果教务系统抢课_名额不够,技术来凑,利用Python实现教务系统强制性抢课...-程序员宅基地

文章浏览阅读1.3k次。最近一学期一次的抢课大戏又来了,几家欢乐几家愁。O(∩_∩)O哈哈~(l我每次一选就过了hah,我还是有欧的时候滴)。看着他们盯着教务系统就着急,何况我们那教务系统,不想说什么。emmm 想周围的朋友,正好下午利用扩容前一段时间写了个小脚本帮助朋友抢课。(当然抢到了啦,^_^)私信小编001即可获取大量Python学习资料,名额有限因为时间不够,来不及仔细琢磨,我第一想法就是直接提交选课的数据包(..._青果教务系统抢课

windows 加 switchyomega + burp 抓https包-程序员宅基地

文章浏览阅读4.6k次。很简单,下载证书后导入到受信任根目录证书下载,直接在代理状态浏览器访问burp点击CA就可以下载了 设置该证书全部信任,,switchyomega 设置如下即可 就可以抓https的包了 ...

随便推点

2021-07-18-程序员宅基地

文章浏览阅读43次。从零开始实现简易版本SpringIoC&DI&MVCSpring源码进修中,实现一个简易版本的Spring,包含以下主要内容:IoC,DI,MVC,已完成基本的功能。代码量还算一般,特此记录以共勉。首先是整个项目的基本思路。项目实现的功能就是从前端发一个请求,后端根据请求解析到后端的相应方法进行处理,完成后将结果进行返回。代码需要完成请求路径和类+方法的对应。配置阶段配置web.xml:DispatcherServlet设定init-param:contextConfigL

507页XX市应急管理局智慧矿山煤矿数字化矿山技术解决方案_18万字应急管理局智慧矿山煤矿数字化矿山技术解决方案word-程序员宅基地

文章浏览阅读542次。只有在单系统自动化的基础上,通过高速网络接入各单系统,充分数据融合,建立合理的联动机制才能完成从单系统自动化到综合自动化的转变,该部分的转变从投入的资金和实现的容易度相对来讲可实现性和可控性都比较容易,但是从综合自动化向数字化矿山发展,涉及的面比较广,必须由多方共同来推进,一般涉及到“综合自动化”、“空间数字化”及“管理信息化”三大方面,三者缺一不可,通过三者的有机融合,再通过合适的平台例如三维可视化平台进行展示,同时通过科学合理的管理制度和流程加以应用才是真正意义上有血有肉的数字化矿山。_18万字应急管理局智慧矿山煤矿数字化矿山技术解决方案word

Tomcat官网地址-程序员宅基地

文章浏览阅读1.4w次,点赞8次,收藏7次。Tomcat官网地址_tomcat官网

汇编指令长度计算_汇编指令占多少字节-程序员宅基地

文章浏览阅读5.1k次,点赞11次,收藏58次。指令长度与寻址方式有关系,规律或原则如下:一、没有操作数的指令,指令长度为1字节。如es:ds:cbwxlat等。二、操作数只涉及寄存器的指令,指令长度为2字节。如mov al,[si]mov ax,[bx+si]mov ds,ax等。三、操作数涉及内存地址的指令,指令长度为3字节。如mov al,[bx+1]mov ax,[bx+si+3]lea di,[1234]mov [2345],ax等。四、操作数涉及立即数的指令,指令长度为:寄存器类型+2。8位寄存器,寄存器_汇编指令占多少字节

二、RSA加密_ctf rsa 多个n和多个c-程序员宅基地

文章浏览阅读3.4k次。CTF中的RSA及攻击方法笔记1 数论基础1.1 模运算规则2 RSA相关题目2.1 已知 n,e,c 求 m2.2 已知 p,q,e 求 d2.3 已知dp,dq,c,p,q 求m2.4 仅已知c,c特别大 【c = m^e mod n】2.5 已知n1,n2,c1,c2,n 求 m2.6 已知n1,n2,e,c2 求m2.7 已知e,d,N 求p,q1 数论基础参考链接:https://www.freebuf.com/articles/web/257835.html1.1 模运算规则模运算与基_ctf rsa 多个n和多个c

mysql中把bigint类型转换为时间格式,与hive中unix_timestamp、FROM_UNIXTIME两个函数之间的区别_bigint转日期-程序员宅基地

文章浏览阅读2w次,点赞4次,收藏15次。数据库中时间类型是这样的,13位bigInt类型的数据select date_format(FROM_UNIXTIME(列名/1000),'%Y%m%d') from xx表原理就是把13位的时间格式/1000等于时间戳,使用FROM_UNIXTIME把时间戳转换成具体的日期ps:将时间转换为时间戳select unix_timestamp('2018-08-30..._bigint转日期

推荐文章

热门文章

相关标签