DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析

文章目录

  • 一、前言——从YOLOv3到YOLOv5
  • 二、代码解析
    • 2.1 运行起来项目 detect.py
    • 2.2 网络结构 models/yolo.py
    • 2.3 配置文件 yolov5s.yaml
    • 2.4 网络子结构 models/common.py
      • 2.4.1 Conv与Focus
      • 2.4.2 Bottleneck与BottleneckCSP
    • 2.5 训练 train.py

先放官网大图
在这里插入图片描述
YOLOv5官方Pytorch实现:https://github.com/ultralytics/yolov5
再放干货:YOLOv3-YOLOv5的所有权重:(YOLOv5权重需要翻墙下载)
链接:https://pan.baidu.com/s/1rZAiccpdmUaaWDQ5tUwFcA
提取码:6qvy

一、前言——从YOLOv3到YOLOv5

YOLOv3
YOLO原项目darknet(官方)截止2020年5月31日,并没有更新添加这个"YOLOv5"的链接。最新的一次update还是上个月YOLOv4重磅出炉的那次,官方正式添加了YOLOv4项目链接。

"YOLOv5"的项目团队是Ultralytics LLC 公司,很多人应该没有听过这家公司。但提到他们公司的一个项目,很多人应该就知道了,因为不少同学用过。那就是基于PyTorch复现的YOLOv3,按目前github上star数来看,应该是基于PyTorch复现YOLOv3中的排名第一。Amusi 之前还分享过此项目。

附上Pytorch版的YOLOv3:https://github.com/ultralytics/yolov3

他们复现的YOLOv3版而且还有APP版本
YOLOv3 in PyTorch > ONNX > CoreML > iOS
在这里插入图片描述
其实这个公司团队在YOLOv3上花的功夫蛮多的,不仅有APP版,还对YOLOv3进行了改进,官方介绍的性能效果可以说相当炸裂!另外项目维护的也很牛逼,star数已达4.7 k,commits 都快逼近2500次!
在这里插入图片描述
可见Ultralytics LLC 公司在YOLO社区上的贡献还是有一定影响力的,这也是为什么他们刚推出"YOLOv5",就得到不少人的关注。

YOLOv5
据官方称:“YOLOv5” 实际上还处于开发的阶段,预计2020年第2季度/第3季度将全部开发完成。目前放出来的版本,是集成了YOLOv3-SPP和YOLOv4部分特性。

那么"YOLOv5"的性能有多强呢,Ultralytics LLC给出的数据如下:
这里说一下,YOLOv5-x的性能已经达到:47.2 AP / 63 FPS,但项目是在 image size = 736的情况下测得。但Ultralytics LLC并没有给出"YOLOv5"的算法介绍(论文、博客其实都没有看到),所以我们只能通过代码查看"YOLOv5"的特性。只能说现在版本的"YOLOv5"集成了YOLOv3-SPP和YOLOv4的部分特性等。
在这里插入图片描述
在这里插入图片描述

二、代码解析

代码目录
在这里插入图片描述

2.1 运行起来项目 detect.py

代码目录:
在这里插入图片描述
1)拿到yolov5的代码,我们首先要把项目运行起来。在这里我们需要下载得到权重,在这里我下载了yolov5s.pt文件,把其放到weights文件夹下;然后找到detect.py运行,inference文件下推理得到output文件夹:在这里插入图片描述
这样我们的第一步项目就跑通了,这是我们拿到源码首先要完成的任务。

2.2 网络结构 models/yolo.py

根据配置文件,models文件夹下选择yolov5s.yaml(根据选择的权重观察),在yolo.py文件里运行下面代码,使用netron可视化网络结构。(关于netron模型可视化,我们只需要在命令行安装pip install netron即可,然后进入netron,复制地址进入,打包我们需要的模型,即可使用可视化。这个工具有助于我们分析网络的整体架构)

1
2
model = Model(opt.cfg).to(device)
torch.save(model,"m.pt")

在这里插入图片描述
这个结构看起来比较简单,我们用torch.jit导出jit格式来看模型详细架构,运行以下代码:

1
2
3
4
5
    # Create model
    model = Model(opt.cfg).to(device)
    x = torch.randn(1,3,384,640)
    script_models = torch.jit.trace(model,x)
    script_models.save("m.jit")

导出m.jit格式后,将其重命名为m1.pt,再进行netron可视化分析,这里由于图片较长,我就不展示了。

2.3 配置文件 yolov5s.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple

# anchors
anchors:
  - [116,90, 156,198, 373,326]  # P5/32
  - [30,61, 62,45, 59,119]  # P4/16
  - [10,13, 16,30, 33,23]  # P3/8

# 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]]],
  ]

# YOLOv5 head
head:
  [[-1, 3, BottleneckCSP, [1024, False]],  # 9

   [-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]],
   [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 18 (P3/8-small)

   [-2, 1, Conv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, BottleneckCSP, [512, False]],
   [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 22 (P4/16-medium)

   [-2, 1, Conv, [512, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, BottleneckCSP, [1024, False]],
   [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 26 (P5/32-large)

   [[], 1, Detect, [nc, anchors]],  # Detect(P5, P4, P3)
  ]

解析:[-1, 1, Focus, [64, 3]] , [[-1, 6], 1, Concat, [1]]

① -1代表动态计算上一层的通道数(-2代表计算上两层的通道数),设计的原因是一层一层下来的,但存在残差路由结构;[-1,6]代表把上一层与第六层cat起来。

② [64,3]:网络第一层输出是32个通道(把模型打印出来可以看到),但这里是64,这就与采样率有关:64乘以width_multiple=32,与网络第一层输出一致。
3代表这一层复制3次,3乘以depth_multiple等于1,即1层。最少也要有1层。

width_multiple: 0.50这个参数与网络设计有关,现在设计网络一般都不设计一个网络,如yolov3-tiny,yolov3-darknet53,yolov3-spp,但都是单独设计,不太好;如果我们设计几种网络,一般设计常规网络(不大不小),进行训练,效果不错我们再进行缩放,包含深度缩放depth_multiple宽度缩放width_multiple(通道数),这样的网络被证明效果是不错的,所以可以得到n个网络,减轻了设计负担。

缩放规则r^2βw<2,r(分辨率)β(深度即层数)w(通道数),希望网络的这些参数变大1倍,但计算量小于2。

2.4 网络子结构 models/common.py

这部分我们需要根据 yolov5s.yaml 配置文件查看主干网backbone和侦测网head查看不同的子结构。

2.4.1 Conv与Focus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Conv(nn.Module):# 自定义卷积块:卷积_BN_激活。类比yolov4里的CBL结构
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, g=1, act=True):  # ch_in, ch_out, kernel, stride, groups
        super(Conv, self).__init__()
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # padding
        self.conv = nn.Conv2d(c1, c2, k, s, p, groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.LeakyReLU(0.1, inplace=True) if act else nn.Identity()
       
    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def fuseforward(self, x):
        return self.act(self.conv(x))
       
class Focus(nn.Module):# Focus模块:将W、H信息集中到通道空间
    # Focus wh information into c-space
    def __init__(self, c1, c2, k=1):
        super(Focus, self).__init__()
        self.conv = Conv(c1 * 4, c2, k, 1)

    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倍作用是可以使信息不丢失的情况下提高计算力。具体操作为把一张图片每隔一个像素拿到一个值,类似于邻近下采样,这样我们就拿到了4张图,4张图片互补,长的差不多,但信息没有丢失,拼接起来相当于RGB模式下变为12个通道,通道多少对计算量影响不大,但图像缩小,大大减少了计算量。可以当成下图理解:
在这里插入图片描述

2.4.2 Bottleneck与BottleneckCSP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class BottleneckCSP(nn.Module):
    # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super(BottleneckCSP, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
        self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
        self.cv4 = Conv(c2, c2, 1, 1)
        self.bn = nn.BatchNorm2d(2 * c_)  # applied to cat(cv2, cv3)
        self.act = nn.LeakyReLU(0.1, inplace=True)
        self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])

    def forward(self, x):
        y1 = self.cv3(self.m(self.cv1(x)))
        y2 = self.cv2(x)
        return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))

在这里我们可以画出其网络结构图如下图所示,其实我们还可以类比YOLOv4的子结构,我们发现其实他们大概类似,只不过YOLOv4第一个是CBM结构其他的CBM结构换成了卷积,这也解释了我在前一篇博文提到的CBM结构换卷积会有更好的效果,有兴趣可以跳到这篇博文DeepLearing—CV系列(十三)——YOLOv4完整核心理论详解。
在这里插入图片描述
下图为YOLOv4类似的子结构图:
在这里插入图片描述

2.5 训练 train.py