Ai算法爱好者学习

  如果你去一家做视觉检测的公司仓库里逛一圈,问他们产线上跑的是什么模型,十有八九会听到一个名字:YOLOv5。

  这玩意2020年出来,到现在五六年了。后面v8、v10、11迭代了好几轮,各种新结构、新损失函数层出不穷,但不少工控机里,大概率还是Ultralytics那套train.py在稳稳地跑。不是大家不想追新,而是这套东西实在太皮实——改个yaml配置,对好数据路径,一行命令下去,第二天就能拿到一个能用的权重。对于要交付的项目来说,这种确定性比论文里高几个点的mAP更值钱。

  YOLOv5的真正价值,可能不在于它提出了什么惊天动地的理论突破,而在于它第一次把目标检测从"实验室调参"变成了一件"工程上可落地"的事。它用纯PyTorch重写,把训练、验证、导出、推理串成了一条完整的流水线,让写Python的工程师不用再对着C++的Darknet Makefile发愁。这篇文章,我想把YOLOv5的骨架拆开来看。从输入端的Mosaic增强,到Backbone的CSPDarknet,再到Neck的FPN+PAN,最后到Head的三个检测层,把这些模块为什么这样设计怎么配合,尽量讲清楚。

官方文档:https://docs.ultralytics.com/yolov5/

官方实现仓库:https://github.com/ultralytics/yolov5

一、基础知识点
1.1 网络架构

YOLOv5采用了一种模块化的网络架构,主要由三个部分组成:

  • Backbone(主干网络): 作为特征提取的核心,通常是在大规模数据集(如ImageNet或COCO)上预训练的卷积神经网络,如ResNet-50或Darknet53。

  • Neck(颈部网络) 介于Backbone和Head之间,用于整合不同层级的特征图,以提升检测性能。

  • Head(头部网络) 位于模型的末端,负责预测目标的类别和边界框位置

  目标检测模型的工作流程可概括为:输入数据通过主干网络提取特征,然后颈部网络进一步提炼特征,最终由头部网络完成目标的类别和位置预测,输出检测结果。 YOLOv5与Yolov3及Yolov4进行比较。主要的不同点:

  • (1)输入端 : Mosaic数据增强(v4中使用到了)、自适应锚框计算、自适应图片缩放

  • (2)骨干网络(Backbone) : Focus结构,CSP结构

  • (3)颈部网络(Neck) : FPN+PAN结构

  • (4)头部网络(Head) : GIOU_Loss

1.2 自定义锚框

  在 yolov3、v4 中是采用 kmean 和遗传算法对自定义数据集进行分析,获得合适自定义数据集中对象边界框预测的预设锚点框。在 yolov5 中锚点框是基于训练数据自动学习的。(Auto Learning Bounding Box Anchors)具体代码实现可以参考 general.py 文件中的 check_anchors 函数。

1.3 激活函数

  在 yolov5 中,中间 / 隐藏层使用了 Leaky ReLU 激活函数,最后的检测层使用了 Sigmoid 激活函数;

1.4 优化器

  在 yolov5 中提供了两个优化函数 Adam 和 SGD,并预设了与之匹配的训练超参数,默认是 SGD。

1.5 损失函数

yolo 系列的损失计算是基于 objectness score, class probability,bounding box regression score。

yolov5 中使用 GIoU Loss 作为 bounding box 的损失;

yolov5 中使用 二进制交叉熵(BCE) 和 Logits 损失函数 计算类概率和目标得分的损失,同时我们也可以使用 fl_gamma 参数来激活 focal loss 计算损失函数。

二、创新点
2.0 Mosaic数据增强

  Yolov5的输入端采用了和Yolov4一样的Mosaic数据增强的方式。随机缩放、随机裁剪、随机排布的方式进行拼接,对于小目标的检测效果很不错。Yolov4中使用的Mosaic是参考2019年底提出的CutMix数据增强的方式,但CutMix只使用了两张图片进行拼接,而Mosaic数据增强则采用了4张图片,随机缩放、随机裁剪、随机排布的方式进行拼接。

Mosaic数据增强的主要步骤:

  • 随机选择四张不同的图像作为输入

  • 分别对四张图片进行翻转(对原始图片进行左右的翻转)、缩放(对原始图片进行大小的缩放)、色域变化(对原始图片的明亮度、饱和度、色调进行改变)等操作。

  • 操作完成之后然后再将原始图片按照 第一张图片摆放在左上,第二张图片摆放在左下,第三张图片摆放在右下,第四张图片摆放在右上四个方向位置摆好。

  • 根据每张图片的尺寸变换方式,将映射关系对应到图片标签上。

  • 依据指定的横纵坐标,对大图进行拼接。处理超过边界的检测框坐标。

主要有几个优点:

  • 增加数据多样性,随机选取四张图像进行组合,组合得到图像个数比原图个数要多。

  • 增强模型鲁棒性,混合四张具有不同语义信息的图片,可以让模型检测超出常规语境的目标。

  • 加强批归一化层(Batch Normalization)的效果。当模型设置 BN 操作后,训练时会尽可能增大批样本总量(BatchSize),因为 BN 原理为计算每一个特征层的均值和方差,如果批样本总量越大,那么 BN 计算的均值和方差就越接近于整个数据集的均值和方差,效果越好。

  • Mosaic 数据增强算法有利于提升小目标检测性能。Mosaic 数据增强图像由四张原始图像拼接而成,这样每张图像会有更大概率包含小目标。

2.1 自适应锚框计算

  在YOLOv5中,每次训练开始之前,它都会根据你的数据集来自适应计算anchor锚框;在 yolo v3 和 yolo v4 中是采用 kmean 和遗传学习算法对自定义数据集进行分析,获得适合自定义数据集中对象边界框预测的预设锚定框。在 yolo v5 中锚定框是基于训练数据自动学习的。若觉得计算的锚框效果不佳,可以将--noautoanchor参数设置True default值即可关闭。

parser.add_argument('--noautoanchor', default=True, action='store_true', help=' 不自动调整anchor,默认为False')

  在 YOLOv5 的配置文件model/*.yaml 中已经预设了一些针对 COCO数据集在 640 × 640 640×640 640×640图像大小下锚定框的尺寸:

anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32
anchors参数共有三行,每行6个数值;且每一行代表应用不同的特征图;
第一行是在最大的特征图上的锚框, 80 x 80 80x80 80x80代表浅层的特征图(P3),包含较多的低层级信息,适合用于检测小目标,所以这一特征图所用的anchor尺度较小;
第二行是在中间的特征图上的锚框, 40 x 40 40x40 40x40特征图上就用介于这两个尺度之间的anchor用来检测中等大小的目标;
第三行是在最小的特征图上的锚框, 20 x 20 20x20 20x20代表深层的特征图,包含更多高层级的信息,如轮廓、结构等信息,适合用于大目标的检测,所以这一特征图所用的anchor尺度较大。

自定义锚框

  锚框核查函数 /utils/autoanchor.py 文件中; YOLOv5 在开始训练前会计算数据集标注信息针对默认锚定框的最佳召回率,如果最佳召回率大于或等于0.98,则不需要重新计算锚定框,使用默认锚框;如果最佳召回率小于0.98,则需要重新计算符合此数据集的锚框。

def metric(k):  # compute metric
    r = wh[:, None] / k[None]
    x = torch.min(r, 1 / r).min(2)[0]  # ratio metric
    best = x.max(1)[0]  # best_x
    aat = (x > 1 / thr).float().sum(1).mean()  # anchors above threshold
    bpr = (best > 1 / thr).float().mean()  # best possible recall
    return bpr, aat

stride = m.stride.to(m.anchors.device).view(-1, 1, 1)  # model strides
anchors = m.anchors.clone() * stride  # current anchors
bpr, aat = metric(anchors.cpu().view(-1, 2))

其中,bpr(best possible recall)参数就是判断是否需要重新计算锚定框的依据(是否小于 0.98) 重新计算符合此数据集标注框的锚定框,是利用 k均值聚类算法(k-means clustering)和遗传算法(genetic algorithm)实现的。

自定义锚框计算过程

  • 读取训练集中所有图片的w、h以及检测框的w、h

  • 将读取的坐标修正为绝对坐标

  • 使用Kmeans算法对训练集中所有的检测框进行聚类,得到k个anchors

  • 通过遗传算法对得到的anchors进行变异,如果变异后效果好将其保留,否则跳过

  • 将最终得到的最优anchors按照面积返回

import utils.autoanchor as autoAC
# 对数据集重新计算 anchors
new_anchors = autoAC.kmean_anchors('./data/mydata.yaml', 9, 640, 5.0, 1000, True)
print(new_anchors)
2.2 自适应图片缩放

utils/datasets.py中

def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True):
    # Resize image to a 32-pixel-multiple rectangle https://github.com/ultralytics/yolov3/issues/232
    shape = img.shape[:2]  # current shape [height, width]
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)

    # Scale ratio (new / old)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    if not scaleup:  # only scale down, do not scale up (for better test mAP)
        r = min(r, 1.0)

    # Compute padding
    ratio = r, r  # width, height ratios
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding
    if auto:  # minimum rectangle
        dw, dh = np.mod(dw, 64), np.mod(dh, 64)  # wh padding
    elif scaleFill:  # stretch
        dw, dh = 0.0, 0.0
        new_unpad = (new_shape[1], new_shape[0])
        ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]  # width, height ratios

    dw /= 2  # divide padding into 2 sides
    dh /= 2

    if shape[::-1] != new_unpad:  # resize
        img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
    return img, ratio, (dw, dh)

在常用的目标检测 算法中,不同的图片长宽都不相同,因此常用的方式是将原始图片统一缩放到一个标准尺寸,再送入检测网络中。 第一步:计算缩放比例

原始缩放尺寸是416*416,都除以原始图像的尺寸后,可以得到0.52,和0.69两个缩放系数,选择小的缩放系数。

第二步:计算缩放后的尺寸

原始图片的长宽都乘以最小的缩放系数0.52,宽变成了416,而高变成了312。

第三步:计算黑边填充数值

将416-312=104,得到原本需要填充的高度。再采用numpy中np.mod取余数的方式,得到8个像素,再除以2,即得到图片高度两端需要填充的数值。

此外,需要注意的是:

  • a.这里大白填充的是黑色,即(0,0,0),而Yolov5中填充的是灰色,即(114,114,114),都是一样的效果。

  • b.训练时没有采用缩减黑边的方式,还是采用传统填充的方式,即缩放到416*416大小。只是在测试,使用模型推理时,才采用缩减黑边的方式,提高目标检测,推理的速度。

  • c.为什么np.mod函数的后面用32?因为Yolov5的网络经过5次下采样,而2的5次方,等于32。所以至少要去掉32的倍数,再进行取余。

三、Backbone

  YOLOv5的骨干网络(Backbone)是整个目标检测模型的基础,负责从输入图像中提取有用的特征并不断缩小特征图。YOLOv5的Backbone采用了New CSP-Darknet53架构,这是一种专门为目标检测任务优化的深度学习模型。 New CSP-Darknet53

3.1 CSP结构

  YOLOv5的Backbone使用了Cross Stage Partial Network(CSPNet)结构,这是一种减少计算量的技术。CSP通过在卷积层之间共享权重,减少了模型的参数数量和计算量,同时保持了特征提取的效率。

CSPNet(Cross Stage Partial Network):跨阶段局部网络,以缓解以前需要大量推理计算的问题。

  • 增强了CNN的学习能力,能够在轻量化的同时保持准确性。

  • 降低计算瓶颈。

  • 降低内存成本。

CSPNet通过将梯度的变化从头到尾地集成到特征图中,在减少了计算量的同时可以保证准确率。 CSPNet和PRN都是一个思想,将feature map拆成两个部分,一部分进行卷积操作,另一部分和上一部分卷积操作的结果进行concate。

Darknet架构: Darknet是一个为YOLO系列优化的深度学习框架,它以速度快和资源消耗低而著称。Darknet53是Darknet系列中的一个变种,拥有53层深度,它通过堆叠多个卷积层和池化层来逐步提取图像的深层特征。

CBS模块

C3模块

3由三个CBS模块和一个BottleNeck模块组成,得名C3。在Backbone中,C3是更为重要的提取特征的模块。其结构图如下:

SPPF模块

SPP是空间金字塔池化,采用 1 × 1 1×1 1×1, 5 × 5 5×5 5×5, 9 × 9 9×9 9×9, 13 × 13 13×13 13×13的最大池化的方式,进行多尺度融合。YOLOv5 6.0版本开始使用了在SPP基础上改进的SPPF

3.2 Focus结构

  YOLOv5在Backbone的开始部分使用了Focus结构,这是一种有效的特征融合技术。Focus结构通过将输入图像分割成小块,然后分别提取特征,最后再将这些特征合并,从而提高了模型对小目标的检测能力。

YOLOv5 6.0开始将Focus模块替换成了一个 6 ∗ 6 66 6∗6的卷积层。两者的计算量是等价的,但使用 6 ∗ 6 66 6∗6的卷积会更加高效。

3.3 高效的激活函数

  YOLOv5采用了Mish激活函数,这是一种比传统的ReLU激活函数更加平滑的激活函数,有助于在训练过程中提供更稳定的梯度流动。

Mish激活函数是YOLOv5中的一个关键创新,它在正区间的表现比传统的ReLU激活函数更为平滑,这有助于梯度更有效地流动,从而加快网络训练的收敛速度。Mish的设计避免了ReLU在负区间的梯度消失问题,同时在正区间保持了类似ReLU的线性增长,这对于提取深层次特征和提高模型的非线性表达能力至关重要。

Mish是光滑的非单调激活函数,可定义为:

・ς其中,ς,是一个激活函数和。

3.4 多尺度特征融合

  YOLOv5的Backbone通过SPPF(Spatial Pyramid Pooling with Feature map Fusion)模块,实现了多尺度的特征融合,这使得模型能够同时检测不同大小的目标。

SPP是空间金字塔池化,采用 1 × 1 1×1 1×1, 5 × 5 5×5 5×5, 9 × 9 9×9 9×9, 13 × 13 13×13 13×13的最大池化的方式,进行多尺度融合。YOLOv5 6.0版本开始使用了在SPP基础上改进的SPPF。

SPP是将三个并行的MaxPool2d和输入Concat到一起,第一个MaxPool2d的kernel为 5 ∗ 5 55 5∗5,第二个为 9 ∗ 9 99 9∗9,第三个为 13 ∗ 13 1313 13∗13。用三个不同大小的kernel,代表三个尺度。 5 ∗ 5 55 5∗5的kernel可以理解为比较大的尺度,而 13 ∗ 13 13*13 13∗13就是比较小的尺度。这样就在图片的不同尺度下取到了最大的代表特征值,并Concat融合。

SPPF是将三个kernel为 5 ∗ 5 5*5 5∗5的MaxPool2d做串行计算。第一个MaxPool2d表示较大的尺度,第二个MaxPool在第一个MaxPool2d的基础上进一步做池化,那么产生的尺度将会进一步缩小,第三个同理。

  通过这些设计,YOLOv5的Backbone能够高效地从输入图像中提取丰富的特征,为后续的检测任务提供了坚实的基础。这些特征随后会被传递到Neck和Head部分,进行更精细的目标检测和定位。

四、Neck

Neck的作用就是从Backbone中获取相对于较浅的特征,再与深层的语义特征Concat到一起。Yolov5现在的Neck和Yolov4中一样,都采用FPN+PAN的结构,但在Yolov5刚出来时,只使用了FPN结构,后面才增加了PAN结构,此外网络中其他部分也进行了调整。

Yolov4的Neck结构中,采用的都是普通的卷积操作。而Yolov5的Neck结构中,采用借鉴CSPnet设计的CSP2结构,加强网络特征融合的能力。 FPN 结构通过自顶向下进行上采样,使得底层特征图包含更强的图像强语义信息

FPN结构中,通过Upsample上采样的方式,向特征图中插值,使特征图的尺寸变大,以便于融合来自Backbone的特征图,做特征的向上融合,特征图不断变大;

PAN 结构自底向上进行下采样,使顶层特征包含图像位置信息,两个特征最后进行融合,使不同尺寸的特征图都包含图像语义信息和图像特征信息,保证了对不同尺寸的图片的准确预测。

五、Head

Head层为Detect模块,Detect模块的网络结构很简单,仅由三个 1 ∗ 1 11 1∗1卷积构成,对应三个检测特征层。

上述经过FPN特征金字塔,我们可以获得 20 ∗ 20 ∗ 512 2020512 20∗20∗512、 40 ∗ 40 ∗ 256 4040256 40∗40∗256、 80 ∗ 80 ∗ 128 8080*128 80∗80∗128三个加强特征,然后我们利用这三个shape的特征层传入Yolo Head获得预测结果。

对于每一个特征层,我们可以获得利用一个 1 ∗ 1 1*1 1∗1卷积调整通道数,最终的通道数和需要区分的种类个数相关,每一个特征层上每一个特征点存在3个先验框。

如果使用的是COCO训练集,类则为 80 80 80种,最后的维度应该为 255 = 3 ∗ 85 255 = 385 255=3∗85,三个特征层的shape为 20 ∗ 20 ∗ 255 2020255 20∗20∗255、 40 ∗ 40 ∗ 255 4040255 40∗40∗255、 80 ∗ 80 ∗ 255 8080*255 80∗80∗255;最后的255可以拆分成 3 3 3个 85 85 85,对应 3 3 3个先验框的 85 85 85个参数,85可以拆分成 4 + 1 + 80 4+1+80 4+1+80。这里的3是指每个位置先验框(锚框)的数量;前4个参数用于判断每一个特征点的回归参数,回归参数调整后可以获得预测框;第5个参数用于判断每一个特征点是否包含物体; 最后80个参数用于判断每一个特征点所包含的物体种类。

六、损失函数

YOLOV5默认应该是使用的Clou,但它源码提供了Giou,Diou等函数,可以在源码中调整

YOLOv5采用了CIoU(Complete Intersection over Union)Loss函数来优化边界框预测。CIoU Loss不仅考虑了预测框与真实框之间的重叠区域,还考虑了两者中心点的距离、宽高比和对角线长度,从而更全面地评估预测框与真实框之间的相似度。CIoU Loss的引入使得模型在训练过程中能够更精确地预测边界框,尤其是在目标尺寸和形状上。

其中Yolov5中采用其中的GIOU_Loss做Bounding box的损失函数

先计算两个框的最小闭包 区域面积 (通俗理解:同时包含了预测框和真实框的最小框的面积),再计算出IoU,再计算闭包区域中不属于两个框的区域占闭包区域的比重,最后用IoU减去这个比重得到GIoU。

两个框的最小闭包区域面积 = 红色矩形面积
IoU = 黄色框和蓝色框的交集 / 并集
闭包区域中不属于两个框的区域占闭包区域的比重 = 蓝色面积 / 红色矩阵面积
GIoU = IoU - 比重
def Giou(rec1,rec2):
    #分别是第一个矩形左右上下的坐标
    x1,x2,y1,y2 = rec1 
    x3,x4,y3,y4 = rec2
    iou = Iou(rec1,rec2)
    area_C = (max(x1,x2,x3,x4)-min(x1,x2,x3,x4))*(max(y1,y2,y3,y4)-min(y1,y2,y3,y4))
    area_1 = (x2-x1)*(y1-y2)
    area_2 = (x4-x3)*(y3-y4)
    sum_area = area_1 + area_2

    w1 = x2 - x1   #第一个矩形的宽
    w2 = x4 - x3   #第二个矩形的宽
    h1 = y1 - y2
    h2 = y3 - y4
    W = min(x1,x2,x3,x4)+w1+w2-max(x1,x2,x3,x4)    #交叉部分的宽
    H = min(y1,y2,y3,y4)+h1+h2-max(y1,y2,y3,y4)    #交叉部分的高
    Area = W*H    #交叉的面积
    add_area = sum_area - Area    #两矩形并集的面积

    end_area = (area_C - add_area)/area_C    #闭包区域中不属于两个框的区域占闭包区域的比重
    giou = iou - end_area
    return giou

其中可使用的DIoU

DIoU要比GIou更加符合目标框回归的机制,将目标与anchor之间的距离,重叠率以及尺度都考虑进去,使得目标框回归变得更加稳定,不会像IoU和GIoU一样出现训练过程中发散等问题。

与GIoU loss类似,DIoU loss(L D I o U = 1 − D I o U L_{DIoU} = 1 - DIoUL 
DIoU=1−DIoU)在与目标框不重叠时,仍然可以为边界框提供移动方向。
DIoU loss可以直接最小化两个目标框的距离,因此比GIoU loss收敛快得多。
对于包含两个框在水平方向和垂直方向上这种情况,DIoU损失可以使回归非常快,而GIoU损失几乎退化为IoU损失。
DIoU还可以替换普通的IoU评价策略,应用于NMS中,使得NMS得到的结果更加合理和有效。
def Diou(bboxes1, bboxes2):
    rows = bboxes1.shape[0]
    cols = bboxes2.shape[0]
    dious = torch.zeros((rows, cols))
    if rows * cols == 0:#
        return dious
    exchange = False
    if bboxes1.shape[0] > bboxes2.shape[0]:
        bboxes1, bboxes2 = bboxes2, bboxes1
        dious = torch.zeros((cols, rows))
        exchange = True
    # #xmin,ymin,xmax,ymax->[:,0],[:,1],[:,2],[:,3]
    w1 = bboxes1[:, 2] - bboxes1[:, 0]
    h1 = bboxes1[:, 3] - bboxes1[:, 1] 
    w2 = bboxes2[:, 2] - bboxes2[:, 0]
    h2 = bboxes2[:, 3] - bboxes2[:, 1]
    
    area1 = w1 * h1
    area2 = w2 * h2

    center_x1 = (bboxes1[:, 2] + bboxes1[:, 0]) / 2 
    center_y1 = (bboxes1[:, 3] + bboxes1[:, 1]) / 2 
    center_x2 = (bboxes2[:, 2] + bboxes2[:, 0]) / 2
    center_y2 = (bboxes2[:, 3] + bboxes2[:, 1]) / 2

    inter_max_xy = torch.min(bboxes1[:, 2:],bboxes2[:, 2:]) 
    inter_min_xy = torch.max(bboxes1[:, :2],bboxes2[:, :2]) 
    out_max_xy = torch.max(bboxes1[:, 2:],bboxes2[:, 2:]) 
    out_min_xy = torch.min(bboxes1[:, :2],bboxes2[:, :2])

    inter = torch.clamp((inter_max_xy - inter_min_xy), min=0)
    inter_area = inter[:, 0] * inter[:, 1]
    inter_diag = (center_x2 - center_x1)**2 + (center_y2 - center_y1)**2
    outer = torch.clamp((out_max_xy - out_min_xy), min=0)
    outer_diag = (outer[:, 0] ** 2) + (outer[:, 1] ** 2)
    union = area1+area2-inter_area
    dious = inter_area / union - (inter_diag) / outer_diag
    dious = torch.clamp(dious,min=-1.0,max = 1.0)
    if exchange:
        dious = dious.T
    return dious

其中CIOU

作者考虑到bbox回归三要素中的长宽比还没被考虑到计算中,因此,进一步在DIoU的基础上提出了CIoU。Yolov4中采用CIOU_Loss作为目标Bounding box的损失。 完整的 CIoU 损失函数定义:

其中 α \alphaα 是权重函数,而 v vv 用来度量长宽比的相似性,定义为

def bbox_overlaps_ciou(bboxes1, bboxes2):
    rows = bboxes1.shape[0]
    cols = bboxes2.shape[0]
    cious = torch.zeros((rows, cols))
    if rows * cols == 0:
        return cious
    exchange = False
    if bboxes1.shape[0] > bboxes2.shape[0]:
        bboxes1, bboxes2 = bboxes2, bboxes1
        cious = torch.zeros((cols, rows))
        exchange = True

    w1 = bboxes1[:, 2] - bboxes1[:, 0]
    h1 = bboxes1[:, 3] - bboxes1[:, 1]
    w2 = bboxes2[:, 2] - bboxes2[:, 0]
    h2 = bboxes2[:, 3] - bboxes2[:, 1]

    area1 = w1 * h1
    area2 = w2 * h2

    center_x1 = (bboxes1[:, 2] + bboxes1[:, 0]) / 2
    center_y1 = (bboxes1[:, 3] + bboxes1[:, 1]) / 2
    center_x2 = (bboxes2[:, 2] + bboxes2[:, 0]) / 2
    center_y2 = (bboxes2[:, 3] + bboxes2[:, 1]) / 2

    inter_max_xy = torch.min(bboxes1[:, 2:],bboxes2[:, 2:])
    inter_min_xy = torch.max(bboxes1[:, :2],bboxes2[:, :2])
    out_max_xy = torch.max(bboxes1[:, 2:],bboxes2[:, 2:])
    out_min_xy = torch.min(bboxes1[:, :2],bboxes2[:, :2])

    inter = torch.clamp((inter_max_xy - inter_min_xy), min=0)
    inter_area = inter[:, 0] * inter[:, 1]
    inter_diag = (center_x2 - center_x1)**2 + (center_y2 - center_y1)**2
    outer = torch.clamp((out_max_xy - out_min_xy), min=0)
    outer_diag = (outer[:, 0] ** 2) + (outer[:, 1] ** 2)
    union = area1+area2-inter_area
    u = (inter_diag) / outer_diag
    iou = inter_area / union
    with torch.no_grad():
        arctan = torch.atan(w2 / h2) - torch.atan(w1 / h1)
        v = (4 / (math.pi ** 2)) * torch.pow((torch.atan(w2 / h2) - torch.atan(w1 / h1)), 2)
        S = 1 - iou
        alpha = v / (S + v)
        w_temp = 2 * w1
    ar = (8 / (math.pi ** 2)) * arctan * ((w1 - w_temp) * h1)
    cious = iou - (u + alpha * ar)
    cious = torch.clamp(cious,min=-1.0,max = 1.0)
    if exchange:
        cious = cious.T
    return cious

nms非极大值抑制

在目标检测的后处理过程中,针对很多目标框的筛选,通常需要nms操作。 因为CIOU_Loss中包含影响因子v,涉及groudtruth的信息,而测试推理时,是没有groundtruth的。所以Yolov4在DIOU_Loss的基础上采用DIOU_nms的方式,而Yolov5中采用加权nms的方式。 不同的nms,会有不同的效果,采用了DIOU_nms的方式,在同样的参数情况下,将nms中IOU修改成DIOU_nms。对于一些遮挡重叠的目标,确实会有一些改进。 比如下面黄色箭头部分,原本两个人重叠的部分,在参数和普通的IOU_nms一致的情况下,修改成DIOU_nms,可以将两个目标检出。虽然大多数状态下效果差不多,但在不增加计算成本的情况下,有稍微的改进也是好的。

七、 超参数详解
7.1 hpy超参数

文件位于data/hyps文件夹下

# Hyperparameters for VOC finetuning  
# ython train.py --batch 64 --weights yolov5m.pt --data voc.yaml --img 512 --epochs 50  
lr0: 0.01  # 学习率, SGD=1E-2, Adam=1E-3
lrf: 0.01    # 余弦退火超参数 
momentum: 0.937  # 学习率动量
weight_decay: 0.0005  # 权重衰减系数
warmup_epochs: 3.0  # 预热学习epoch
warmup_momentum: 0.8 # 预热学习率动量
warmup_bias_lr: 0.1 # 预热学习率
box: 0.05  # Bounding Box Regeression 损失的系数
cls: 0.5  # 分类损失的系数
cls_pw: 1.0 # 分类BCELoss中正样本的权重 
obj: 1.0  # 有无物体损失的系数
obj_pw: 1.0  # 有无物体BCELoss中正样本的权重
iou_t: 0.20  # 标签与anchors的iou阈值 iou training threshold
anchor_t: 4  # 标签的长h宽w/anchor的长h_a宽w_a阈值, 即h/h_a, w/w_a都要在(1/4, 4)之间anchor-multiple threshold
# anchors: 3.63  
# 下面是一些数据增强的系数, 包括颜色空间和图片空间  
fl_gamma: 0.0  
hsv_h: 0.015  # 色调  
hsv_s: 0.7   # 饱和度  
hsv_v: 0.4   # 明度  
degrees: 0.0 #旋转角度  
translate: 0.1  # 水平和垂直平移  
scale: 0.5   # 缩放  
shear: 0.0  # 剪切  
perspective: 0.0  # 透视变换参数  
flipud: 0.0  # 上下翻转  
fliplr: 0.5   # 左右翻转
mosaic: 1.0   #进行mosaic的概率
mixup: 0.0  #进行mixup的概率, 在mosaic启用时才可启用
copy_paste: 0.0  # segment copy-paste (probability), 在mosaic启用时, 才可以启用 

7.2 Acnhor

YOLOv5在yaml文件中预设好了输入图像为 640 ∗ 640 640*640 640∗640分辨率对应的anchor尺寸,YOLOv5的anchor也是在大特征图上检测小目标,在小特征图上检测大目标。三个特征图,每个特征图上的格子有三种尺寸的anchor。

# anchors  
anchors:  
  - [10,13, 16,30, 33,23]  # P3/8  检测小目标  10,13是一组尺寸,一共三组  
  - [30,61, 62,45, 59,119]  # P4/16     
  - [116,90, 156,198, 373,326]  # P5/32  检测大目标  
7.3 Backbone
# YOLOv5s v6.0 backbone  
backbone:  
# YOLOv5 v6.0 backbone
backbone:
  # from   第一列 输入来自哪一层  -1代表上一层, 4代表第4层     
  # number 第二列 卷积核的数量    最终数量需要乘上width  
  # module 第三列 模块名称 包括:Conv Focus BottleneckCSP  SPP  
  # args   第四列 模块的参数   
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4 卷积核的数量 = 128 * wedith = 128*0.5=64    
   [-1, 3, C3, [128]],  # 模块数量 = 3 * depth =3*0.33=1  
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],  # 模块数量 = 6 * depth =6*0.33=2
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],  # # 模块数量 = 9 * depth =9*0.33=3
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ] 
7.4 网络结构参数
**backbone的前3个C3数量对应yolov5s.yaml的配置3,6,9分别除了3,变为1/3后的1,2,3,和模型深度参数有关depth_multiple: 0.33**                  
层数,第几层      from  n    params  module                                  arguments
               ch[-1]  数量  参数量 模块名称(m)                             网络结构参数:输入维度,输出维度,卷积核大小,卷积步长
  0                -1  1      3520  models.common.Conv                      [3, 32, 6, 2, 2]
  1                -1  1     18560  models.common.Conv                      [32, 64, 3, 2]
  2                -1  1     18816  models.common.C3                        [64, 64, 1]
  3                -1  1     73984  models.common.Conv                      [64, 128, 3, 2]
  4                -1  2    115712  models.common.C3                        [128, 128, 2]
  5                -1  1    295424  models.common.Conv                      [128, 256, 3, 2]
  6                -1  3    625152  models.common.C3                        [256, 256, 3]
  7                -1  1   1180672  models.common.Conv                      [256, 512, 3, 2]
  8                -1  1   1182720  models.common.C3                        [512, 512, 1]
  9                -1  1    656896  models.common.SPPF                      [512, 512, 5]
 10                -1  1    131584  models.common.Conv                      [512, 256, 1, 1]
 12           [-1, 6]  1         0  models.common.Concat                    [1]
 13                -1  1    361984  models.common.C3                        [512, 256, 1, False]
 14                -1  1     33024  models.common.Conv                      [256, 128, 1, 1]
 15                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']
 16           [-1, 4]  1         0  models.common.Concat                    [1]
 17                -1  1     90880  models.common.C3                        [256, 128, 1, False]
 18                -1  1    147712  models.common.Conv                      [128, 128, 3, 2]
 19          [-1, 14]  1         0  models.common.Concat                    [1]
 20                -1  1    296448  models.common.C3                        [256, 256, 1, False]
 21                -1  1    590336  models.common.Conv                      [256, 256, 3, 2]
 22          [-1, 10]  1         0  models.common.Concat                    [1]
 23                -1  1   1182720  models.common.C3                        [512, 512, 1, False]
 24      [17, 20, 23]  1    229245  Detect                                  [80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [128, 256, 512]]
7.5 Yolov5四种网络的深度

7.6 Yolov5四种网络的宽度

此篇文章学习记录到此结束,后续更多精彩内容!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐