一文看懂YOLO v5

YOLOv5在比赛与落地中运用广泛,它可以通过chaneel与layer的控制因子来根据落地要求灵活配置模型。与YOLO系列1-4不同的是YOLOv5对正样本的定义:其采用了跨领域网格,并在不同输出层匹配,极大的扩增了正样本anchor,加速模型收敛速度并提高模型召回率。为了大家更好的理解并使用yolov5,本文将结合代码从不同维度进行分析。

网络结构

在这里插入图片描述
YOLOv5的整体结构与YOLOv4相差不大,有兴趣的同学可以查看我之前总结的yolo系列文章YOLOv4

YOLOv5的结构如下:
Backbone:采用了Focus结构,CSP结构(CSPNet的跨阶段局部融合网络),减少bp时梯度信息的重复,降低计算量,提高CNN的学习能力。
Neck:模块采用PFN+PAN,融合不同维度的特征。
head:部分与YOLOv4相同,三个输出头,分别对应8,16,32stride,不同stride的输出预测不同尺寸的目标。

Focus
在这里插入图片描述
Focus是YOLOv5在网络结构中变化的地方,就是将特征切片成4份,每一份将相当于下采样(stride=2)的特征,然后在channel维度进行concat。

Neck
Yolov5现在的Neck和Yolov4中一样,都采用FPN+PAN的结构,但在Yolov5刚出来时,只使用了FPN结构,后面才增加了PAN结构,此外网络中其他部分也进行了调整。
在这里插入图片描述
Yolov4的Neck结构中,采用的都是普通的卷积操作。而Yolov5的Neck结构中,采用借鉴CSPnet设计的CSP2结构,加强网络特征融合的能力。

YOLOv5引入了depth_multiple和width_multiple系数来得到不同大小模型:
在这里插入图片描述
depth_multiple表示channel的缩放系数,就是将配置里面的backbone和head部分有关通道的设置,全部乘以该系数即可。而width_multiple表示BottleneckCSP模块的层缩放系数,将所有的BottleneckCSP模块的number系数乘上该参数就可以最终的层个数。

模型输入端

1.Mosaic数据增强
在这里插入图片描述

Mosaic数据增强则采用了4张图片,随机缩放、随机裁剪、随机排布的方式进行拼接。我们知道目标检测难点一直是对小目标的检测定位,主要原因是,第一,coco数据集中大中小目标占比不均衡,小目标的数量很多,但出现的频率却很低,这导致bp时对小目标的优化不足。第二,小目标本身检测难度就高于大目标,容易出现误检与漏检。

作者提出Mosaic,随机使用4张图片,随机缩放,再随机分布进行拼接,大大丰富了检测数据集,特别是随机缩放增加了很多小目标,让网络的鲁棒性更好。与此同时,Mosaic可以减少GPU的投入,使得小的bachsize也能训练出好的模型。

2.自适应anchor
check_anchors函数通过遗传算法与kmeans迭代算出最大可能召回率的anchor组合,这样生成的自适应anchor能够更好的配合网络,提高模型精度,减少对anchor的设计难度,非常实用。

3.自适应图片缩放(letterbox)
为了提高模型推理速度,yolov5提出自适应图片缩放,即根据长宽比对图像进行缩放,并添加最少的黑边,减少计算量。

做法比较简单,用缩放后的长边减去短边,用此值去对32取余,就得到padding。
在这里插入图片描述
在这里插入图片描述

LOSS

YOLOV5分类分支采用的loss是BCE,conf分支也是BCE,当然可以通过h[‘fl_gamma’]参数开启focal Loss,默认配置没有采用focal los,而bbox分支采用的是Giou loss。

YOLOv5中最有亮点的改变是对正样本的定义。在yolov3中,其正样本区域也就是anchor匹配策略非常粗暴:保证每个gt bbox一定有一个唯一的anchor进行对应,匹配规则就是IOU最大,并且某个gt一定不可能在三个预测层的某几层上同时进行匹配。然而,我们从FCOS等论文中了解到,增加高质量的正样本anchor能够加速模型收敛并提高召回。因此,YOLOv5对此做出了改进,提出匹配规则:

(1) 采用shape匹配规则,分别将ground truth的宽高与anchor的宽高求比值,如果宽高比例小于设定阈值,则说明该GT和anchor匹配,将该anchor认定为正样本。否则,该anchor被滤掉,不参与bbox与分类计算。

(2) 将GT的中心最邻近网格也作为正样本anchor的参考点。因此,bbox的xy回归分支的取值范围不再是0-1,而是-0.5-1.5(0.5是网格中心偏移),因为跨网格预测了。
在这里插入图片描述
但是,在置信度方面,模型将与GT的iou过小的低质量anchor引入计算BCE,因此,模型容易产生误检,即将非目标推测为目标物体。

代码分析

下面我们参考代码,具体看一下YOLOV5是如何改进的。

build_targets函数用于计算loss函数所需要的target(正样本anchor)。其大概流程为:

    def build_targets(self, p, targets):
        # Build targets for compute_loss(), input targets(image,class,x,y,w,h)
        na, nt = self.na, targets.shape[0]  # number of anchors, targets
        tcls, tbox, indices, anch = [], [], [], []
        gain = torch.ones(7, device=targets.device)  # normalized to gridspace gain
        ai = torch.arange(na, device=targets.device).float().view(na, 1).repeat(1, nt)  # same as .repeat_interleave(nt)
        targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2)  # append anchor indices

        g = 0.5  # bias
        off = torch.tensor([[0, 0],
                            [1, 0], [0, 1], [-1, 0], [0, -1],  # j,k,l,m
                            # [1, 1], [1, -1], [-1, 1], [-1, -1],  # jk,jm,lk,lm
                            ], device=targets.device).float() * g  # offsets

gain = torch.ones(7, device=targets.device),gain是输出的channel数,包含image,class,x,y,w,h,conf。 targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2) # append anchor indices将target复制3编,即每层anchor个数为3,target可以与每一层的3个不同size的anchor进行匹配。off是坐标的偏移,中心点以及上下左右4个分支。

        for i in range(self.nl):
            anchors = self.anchors[i]
            gain[2:6] = torch.tensor(p[i].shape)[[3, 2, 3, 2]]  # xyxy gain

            # Match targets to anchors
            t = targets * gain
            if nt:
                # Matches
                r = t[:, :, 4:6] / anchors[:, None]  # wh ratio
                j = torch.max(r, 1. / r).max(2)[0] < self.hyp['anchor_t']  # compare
                # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t']  # iou(3,n)=wh_iou(anchors(3,2), gwh(n,2))
                t = t[j]  # filter

                # Offsets
                gxy = t[:, 2:4]  # grid xy
                gxi = gain[[2, 3]] - gxy  # inverse
                j, k = ((gxy % 1. < g) & (gxy > 1.)).T
                l, m = ((gxi % 1. < g) & (gxi > 1.)).T
                j = torch.stack((torch.ones_like(j), j, k, l, m))
                t = t.repeat((5, 1, 1))[j]
                offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]

将targets归一化的xywh还原到特征图尺寸,方便与anchor(特征图尺寸)进行比较,从而,将宽高比低于self.hyp[‘anchor_t’]=4的anchor滤出。j,k与l,m选出最近的两个中心点。t = t.repeat((5, 1, 1))[j]在5个点中选择其中的3个作为中心点。

 # Define
            b, c = t[:, :2].long().T  # image, class
            gxy = t[:, 2:4]  # grid xy
            gwh = t[:, 4:6]  # grid wh
            gij = (gxy - offsets).long()
            gi, gj = gij.T  # grid xy indices

            # Append
            a = t[:, 6].long()  # anchor indices
            indices.append((b, a, gj.clamp_(0, gain[3] - 1), gi.clamp_(0, gain[2] - 1)))  # image, anchor, grid indices
            tbox.append(torch.cat((gxy - gij, gwh), 1))  # box
            anch.append(anchors[a])  # anchors
            tcls.append(c)  # class

由于其采用了跨网格预测, pxy = ps[:, :2].sigmoid() * 2. - 0.5,故xy预测输出不再是0-1,而是-1~1,加上offset偏移,则为-0.5-1.5;pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]由于shape过滤规则,wh预测输出也不再是任意范围,而是0-4。

				ps = pi[b, a, gj, gi]  # prediction subset corresponding to targets

                # Regression
                pxy = ps[:, :2].sigmoid() * 2. - 0.5
                pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]
                pbox = torch.cat((pxy, pwh), 1)  # predicted box
                iou = bbox_iou(pbox.T, tbox[i], x1y1x2y2=False, CIoU=True)  # iou(prediction, target)
                lbox += (1.0 - iou).mean()  # iou loss

为了缓解低质量正样本对模型性能的影响,作者对置信度做出了改进。原本正样本对用的conf label是1,但是低质量的正样本由于IOU小,不应该将其置信度理解为1,他们应对应比1小的conf label。因此,作者将IOU作为系数与gr相乘重新获取conf label。

tobj[b, a, gj, gi] = (1.0 - self.gr) + self.gr * score_iou

猜你喜欢

转载自blog.csdn.net/litt1e/article/details/119714797
今日推荐