Yolov5的输入端采用了和Yolov4一样的Mosaic数据增强的方式。Mosaic是参考2019年底提出的CutMix数据增强的方式,但CutMix只使用了两张图片进行拼接,而Mosaic数据增强则采用了4张图片,随机缩放、裁剪、排布再进行拼接。
优点:丰富数据集,随机使用4张图片,裁剪缩放后再随机分布进行拼接,大大丰富了检测数据集,特别是随机缩放增加了很多小目标,让网络的鲁棒性更好。
(2)自适应图片缩放在常用的目标检测算法中,不同的图片长宽都不相同,因此常用的方式是将原始图片统一缩放到一个标准尺寸,再送入检测网络中。比如Yolo算法中常用416*416,608*608等尺寸。
在项目实际使用时,很多图片的长宽比不同,因此缩放填充后,两端的黑边大小都不同,而如果填充的比较多,则存在信息冗余,影响推理速度。因此在Yolov5的代码中datasets.py的letterbox函数中进行了修改,对原始图像自适应的添加最少的黑边。
计算原理见:深入浅出Yolo系列之Yolov5核心基础知识完整讲解 - 知乎 (zhihu.com)
(3)自适应锚框计算
在Yolo算法中,针对不同的数据集,都会有初始设定长宽的锚框。在网络训练中,网络在初始锚框的基础上输出预测框,进而和真实框groundtruth进行比对,计算两者差距,再反向更新,迭代网络参数。
在Yolov3、Yolov4中,训练不同的数据集时,计算初始锚框的值是通过单独的程序k-means聚类运行的。但Yolov5中将此功能嵌入到代码中,每次训练时,自适应的计算不同训练集中的最佳锚框值。
2:Backbone
(1)切片操作(Focus)
以Yolov5s的结构为例,原始640*640*3的图像输入Focus结构,采用切片操作,变成320*320*12的特征图,再经过一次卷积操作,变成320*320*32的特征图。
优点:在减少特征信息损失的情况下,实现下采样(对mAP影响很小)。此外,用一个Focus层替换原来v3中的3个卷积层,参数量、浮点数都减少了,实现了提速。
代码:位于common.py
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().__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)
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))
x[..., ::2, ::2]中...是指前面所有的冒号:都用一个省略号来代替,第一个::2是指w从0开始,步长为2取值,第二个::2是指h从0开始,步长为2取值,合起来就是将上图三通道中的红色部分取出,其他同理。
torch.cat((B,C,H,W),1)是指在第一个维度,即通道方向上进行拼接。
YOLOv5在6.0版本后把网络的第一层(原来是Focus模块)换成了一个6*6大小的卷积层。两者在理论上其实等价的,但是对于现有的一些GPU设备(以及相应的优化算法)使用6*6大小的卷积层比使用Focus模块更加高效。
(2)CSPNet(Cross Stage Partial Network)
CSPNet的作者认为推理计算过高的问题是由于网络优化中的梯度信息重复导致的。因此采用CSP模块先将基础层的特征映射划分为两部分,然后通过跨阶段层次结构将它们合并,在减少了计算量的同时可以保证准确率。
该图来自于YOLOv5网络详解,感谢太阳花的小绿豆~
代码:位于common.py
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_, c2, 3, 1, g=g)
self.add = shortcut and c1 == c2 # 若为True,则为Bottleneck1,在Backbone使用,否则为Bottleneck2,在Neck中使用
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
class C3(nn.Module):
# CSP Bottleneck with 3 convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1) # 对应上图中1
self.cv2 = Conv(c1, c_, 1, 1) # 对应上图中2
self.cv3 = Conv(2 * c_, c2, 1) # optional act=FReLU(c2) 对应上图中3
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
def forward(self, x):
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))
关于BottleneckCSPSPPF部分可参考YOLOv5中的CSP结构
3:Neck (1)将SPP换成了SPPFSPP是将输入并行通过多个不同大小的MaxPool,然后做进一步融合,能在一定程度上解决目标多尺度问题。
代码:位于common.py
class SPP(nn.Module):
# Spatial Pyramid Pooling (SPP) layer https://arxiv.org/abs/1406.4729
def __init__(self, c1, c2, k=(5, 9, 13)):
super().__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
def forward(self, x):
x = self.cv1(x)
with warnings.catch_warnings():
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning
return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
SPPF结构是将输入串行通过多个5*5大小的MaxPool层,最后将各个输出连接,与SPP相比,计算速度加快。
代码:位于common.py
class SPPF(nn.Module):
# Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher
def __init__(self, c1, c2, k=5): # equivalent to SPP(k=(5, 9, 13))
super().__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1) # 对应上图1
self.cv2 = Conv(c_ * 4, c2, 1, 1) # 对应上图2
self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
def forward(self, x):
x = self.cv1(x)
with warnings.catch_warnings():
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning
y1 = self.m(x)
y2 = self.m(y1)
return self.cv2(torch.cat((x, y1, y2, self.m(y2)), 1))
(2) 采用CSP+PAN(Path Augmentation Network)结构
如下图,不仅每个C3模块里含有CSP结构,在PAN结构中也加入了CSP。
FPN传递强语义信息(大目标更明确),PAN传递强定位信息(小目标更明确)
4:输出 (1)损失函数此处参考深入浅出Yolo系列之Yolov3&Yolov4&Yolov5&Yolox核心基础知识完整讲解
目标检测损失函数一般包括分类损失Classificition Loss+回归损失Regression Loss
回归损失的发展:Smooth L1 Loss-> IoU Loss(2016)-> GIoU Loss(2019)-> DIoU Loss(2020)->CIoU Loss(2020)
IOU_Loss:
两个框的交并比越大,挨得越近,损失越小。存在的问题:
问题1:即状态1的情况,当预测框和目标框不相交时,IOU=0,无法反应两个框距离的远近,此时损失函数不可导,IOU_Loss无法优化两个框不相交的情况。
问题2:即状态2和状态3的情况,当两个预测框大小相同,两个IOU也相同,IOU_Loss无法区分两者相交情况的不同。
GIOU_Loss:
可以看出IOU越大,差集越小,损失越小。存在的问题:
问题:状态1、2、3都是预测框在目标框内部且预测框大小一致的情况,这时预测框和目标框的差集都是相同的,因此这三种状态的GIOU值也都是相同的,这时GIOU退化成了IOU,无法区分相对位置关系。
基于这个问题,2020年的AAAI又提出了DIOU_Loss。
DIOU_Loss:
DIOU_Loss考虑了重叠面积和中心点距离,当目标框包裹预测框的时候,直接度量2个框的距离,因此DIOU_Loss收敛的更快,但是没有考虑到长宽比。
上面三种情况,目标框包裹预测框,本来DIOU_Loss可以起作用。但预测框的中心点的位置都是一样的,因此按照DIOU_Loss的计算公式,三者的值都是相同的。针对这个问题,又提出了CIOU_Loss。
CIOU_Loss:
CIOU_Loss和DIOU_Loss前面的公式都是一样的,不过在此基础上还增加了一个影响因子,将预测框和目标框的长宽比都考虑了进去。
总结:
IOU_Loss:主要考虑检测框和目标框重叠面积。
GIOU_Loss:在IOU的基础上,解决边界框不重合时的问题。
DIOU_Loss:在IOU和GIOU的基础上,考虑边界框中心点距离的信息。
CIOU_Loss:在DIOU的基础上,考虑边界框宽高比的尺度信息。



