-
YOLOv8算法介绍
YOLOv8 项目地址: ONNX > CoreML > TFLite">GitHub - ultralytics/ultralytics: NEW - YOLOv8 🚀 in PyTorch > ONNX > CoreML > TFLite
ultralytics 并没有直接将开源库命名为 YOLOv8,而是直接使用 ultralytics 这个词,原因是 ultralytics 将这个库定位为算法框架,而非某一个特定算法,一个主要特点是可扩展性。其希望这个库不仅仅能够用于 YOLO 系列模型,而是能够支持非 YOLO 模型以及分类分割姿态估计等各类任务。
YOLOv8主要有如下的优点:
-
用户友好的API
-
可以同时实现分类、检测、分割和姿态估计任务
-
速度更快、准确率更高
-
全新的结构
-
新的损失函数
-
Anchor free
-
模型
YOLOv5
(300epoch)
Params(M)
FLOPs@640(B)
YOLOv8 (500epoch)
Params(M)
FLOPs@640(B)
YOLOX (300epoch)
Params(M)
FLOPs@640(B)
n
28.0
1.9
4.5
37.3
3.2
8.7
s
37.4
7.2
16.5
44.9
11.2
28.6
40.5
9.0
26.8
m
45.4
21.2
49
50.2
25.9
78.9
46.9
25.3
73.8
l
49.0
46.5
109.1
52.9
43.7
165.2
49.7
54.2
155.6
x
50.7
86.7
205.7
53.9
68.2
257.8
51.1
99.1
281.9
YOLOV5;YOLOV8;YOLOX模型对比
2.1 YOLOv8改进概述
-
Backbone: 第一层卷积由原本的 6×6 卷积改为 3×3 卷积;参考 YOLOv7 ELAN 设计思想将 C3 模块换成了 C2f 模块,并配合调整了模块的深度。
-
Neck:移除了 1×1 卷积的降采样层;同时也将原本的 C3 模块换成了 C2f 模块。
-
Head:这部分改动较大,换成了解耦头结构,将分类任务和回归任务解耦;同时也将 Anchor-Based 换成了 Anchor-Free 。
-
Loss:使用 BCE Loss 作为分类损失;使用 DFL Loss + CIOU Loss 作为回归损失。
-
样本匹配策略: 采用了 Task-Aligned Assigner 样本分配策略。
-
训练策略:新增加了最后 10 轮关闭 Mosaic 数据增强操作,该操作可以有效的提升精度。
2.2 Backbone
官方代码解析:
torch.split()的作用是把一个tensor拆分为多个tensor,相当于是concat的逆过程,定义如下:
torch.split(tensor, split_size_or_sections, dim=0) 第二个参数就是自定义在那个唯独进行切分,YOLOv8中利用split函数实现而不是像其他一些模块利用1*1卷积对同一个tensor降维两次。
由于每个有几个DarknetBottleneck就会分出几个分支作为残差最后concat到一起,所以代码中将每个分支都extend到一个list中,最后将这个list中所有的tensor在通道维度进行堆叠,堆叠后的tensor再经过一个1*1卷积进行降维。
class C2f(nn.Module): # CSP Bottleneck with 2 convolutions def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion super().__init__() self.c = int(c2 * e) # hidden channels self.cv1 = Conv(c1, 2 * self.c, 1, 1) self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2) self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n)) def forward(self, x): y = list(self.cv1(x).chunk(2, 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, 1)) def forward_split(self, x): y = list(self.cv1(x).split((self.c, self.c), 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, 1))
2.2 Neck
仍然使用主流的PAFPN,除了将所有的C3模块更换成C2f外,还将YOLOX中上采样前的CBS(Conv+BN+SiLU)模块全都去掉了,C2f模块后直接就进行上采样。如下图所示:
2.3 Head
YOLOv8也不再使用的传统的YOLOhead,开始使用YOLOX提出的Decoupled-Head。将分类和回归任务分离,之前的YOLOhead是合在一起的。双分支目前已经成为主流,例如YOLOv6;PP-YOLOE这些主流的检测器。
头部的改动是最大的,直接将原本的耦合头改成了解耦头,值得注意力的是,这个解耦头不再有之前的 objectness 分支,而是直接解耦成了两路,并且其回归分支使用了 Distribution Focal Loss
2.4 正负样本分配策略
在目标检测中,正负样本分配策略通常用于在训练期间为每个样本分配一个权重,以便模型更加关注困难的样本和重要的样本。动态分配策略和静态分配策略是两种常见的正负样本分配策略。
静态分配策略通常是在训练开始之前确定的,固定为一组预先定义的权重,这些权重不会在训练过程中改变。这种分配策略通常基于经验得出,可以根据数据集的特点进行调整,但是不够灵活,可能无法充分利用样本的信息,导致训练效果不佳。
相比之下,动态分配策略可以根据训练的进展和样本的特点动态地调整权重。在训练初期,模型可能会很难区分正负样本,因此应该更加关注那些容易被错分的样本。随着训练的进行,模型逐渐变得更加强大,可以更好地区分样本,因此应该逐渐减小困难样本的权重,同时增加易分样本的权重。动态分配策略可以根据训练损失或者其他指标来进行调整,可以更好地适应不同的数据集和模型。
总的来说,动态分配策略通常比静态分配策略更加灵活和高效,可以帮助模型更好地利用样本信息,提高训练效果。
动态样本分配策略:
SimOTA:
SimOTA可以看成是一个最优传输的问题。举一个通俗易懂的例子就是,有2个分配基地与6个周围城市,现在需要考虑一个最优的配送方式来确保分配东西到这几个城市的运输成本是最低的。而对于目标检测来说,这个最优传输问题也就是一个最优分配问题,如何实现把这些anchor point分配给gt的代价(cost)是最低的。这个代价就是iou损失,分类损失等内容,cost公式:
SimOTA步骤如下
-
确定正样本选择区间,选择区间为真实框(绿色)范围加上以真实框中心点为中心构建一个自定义范围的矩形(黄色),正样本将在这个区间进行选择
2. 计算其中每一个样本对每个GT的reg+cls loss
3. 计算cost
根据loss计算出cost
cost = ( pair_wise_cls_loss + 3.0 * pair_wise_ious_loss + 100000.0 * (~is_in_boxes_and_center) )
pair_wise_cls_loss就是每个样本与每个GT之间的分类损失
pair_wise_ious_loss是每个样本与每个GT之间的回归损失
is_in_boxes_and_center代表那些落入GT Bbox与黄色矩形交集内的样本,即上图中红色区域,然后这里进行了取反 ~ 表示不在GT Bbox与黄色矩形区域交集内的样本(黄色区域)接着又乘以100000.0,也就是说对于GT Bbox与黄色矩形交集外的样本cost加上了一个非常大的数,这样在最小化cost过程中会优先选择红色区域样本。
SimOTA所需要考虑的问题就是,如何筛选出优质的正样本来匹配gt box,从而减少这个匹配过程所产生的代价(cost)
cost的值应该越小越好,将计算出来的区域内所有cost从小到大进行排序
4. 计算IOU
计算区域内所有预测框和真实框的iou,并从大到小进行排序,选取前十个求平均并向下取整得到k ,k的取值范围 (0
-
-