文章目录
- YOLOv8
- YOLOv8 概述
- 模型结构
- Loss 计算
- 训练数据增强
- 训练策略
- 模型推理过程
- 网络模型解析
- 卷积神经单元(model.py)
- Yolov8实操
- 快速入门
- 环境配置
- 数据集准备
- 模型的训练/验证/预测/导出
- 使用CLI
- 使用python
- 多任务支持
- 检测
- 实例分割
- 分类
- 配置
- 设置操作类型
- 训练
- 预测
- 验证
- 数据扩充
- 日志,检查点,绘图和文件管理
- 自定义模型
- YOLOv8原理解析
- 0.前言
- 1.YOLOv5
- 1.1 YOLOv5网络回顾
- 1.2 YOLOv5网络结构图
- 2.YOLOv8
- 2.1 YOLOv8概述
- 2.2 YOLOv8整体结构图
- 2.3 YOLOv8yaml 文件与 YOLOv5yaml 文件对比
- 2.3.1 参数部分
- 2.3.2 主干部分
- 2.3.3 Neck部分
- 2.3.4 Head部分
- 2.4 正负样本分配策略
- 2.4.1 静态分配策略和动态分配策略有什么区别
- 2.4.2 simOTA 策略
- 2.4.4 Task-Aligned Assigner 策略
- 2.4.5 损失计算
- 详解YOLOv8网络结构/环境搭建/数据集获取/训练/推理/验证/导出/部署
- YOLOv8的简介
- YOLOv8相对于Yolov5的核心改动
- YOLOv8的网络结构
- Backbone
- Neck
- Head
- 环境搭建
- 数据集获取
- 模型获取
- 模型训练
- 训练的三种方式
- 模型验证/测试
- 模型推理
- 模型输出
YOLOv8
是目前YOLO系列算法中最新推出的检测算法,YOLOv8可以完成检测、分类、分割任务,其检测和分割网络结构图如下。
YOLOv8 概述
YOLOv8 算法的核心特性和改动可以归结为如下:
- 提供了一个全新的 SOTA 模型,包括 P5 640 和 P6 1280 分辨率的目标检测网络和基于 YOLACT 的实例分割模型。和 YOLOv5 一样,基于缩放系数也提供了 N/S/M/L/X 尺度的不同大小模型,用于满足不同场景需求
- Backbone:
骨干网络和 Neck 部分可能参考了 YOLOv7 ELAN 设计思想,将 YOLOv5 的C3
结构换成了梯度流更丰富的C2f
结构,并对不同尺度模型调整了不同的通道数。
属于对模型结构精心微调,不再是无脑一套参数应用所有模型,大幅提升了模型性能。不过这个 C2f 模块中存在 Split 等操作对特定硬件部署没有之前那么友好了
- Head: Head部分较yolov5而言有两大改进:1)换成了目前主流的解耦头结构(
Decoupled-Head
),将分类和检测头分离 2)同时也从 Anchor-Based 换成了 Anchor-Free - Loss :1) YOLOv8抛弃了以往的IOU匹配或者单边比例的分配方式,而是使用了
Task-Aligned Assigner
正负样本匹配方式。2)并引入了Distribution Focal Loss(DFL)
- Train:训练的数据增强部分引入了 YOLOX 中的最后 10 epoch 关闭 Mosiac 增强的操作,可以有效地提升精度
从上面可以看出,YOLOv8 主要参考了最近提出的诸如 YOLOX、YOLOv6、YOLOv7 和 PPYOLOE 等算法的相关设计,本身的创新点不多,偏向工程实践,主推的还是 ultralytics 这个框架本身。
下面将按照模型结构设计、Loss 计算、训练数据增强、训练策略和模型推理过程共 5 个部分详细介绍 YOLOv8 目标检测的各种改进,实例分割部分暂时不进行描述。
模型结构
如下图, 左侧为 YOLOv5-s,右侧为 YOLOv8-s。
在暂时不考虑 Head 情况下,对比 YOLOv5 和 YOLOv8 的 yaml 配置文件可以发现改动较小。
Backbone和Neck的具体变化
a) 第一个卷积层的 kernel 从 6x6 变成了 3x3
b) 所有的 C3 模块换成 C2f,结构如下所示,可以发现多了更多的跳层连接和额外的 Split 操作
C3 | C2f |
c)去掉了 Neck 模块中的 2 个卷积连接层
d) Backbone 中 C2f 的 block 数从 3-6-9-3 改成了 3-6-6-3
e) 查看 N/S/M/L/X 等不同大小模型,可以发现 N/S 和 L/X 两组模型只是改了缩放系数,但是 S/M/L 等骨干网络的通道数设置不一样,没有遵循同一套缩放系数。如此设计的原因应该是同一套缩放系数下的通道设置不是最优设计,YOLOv7 网络设计时也没有遵循一套缩放系数作用于所有模型
Head的具体变化
从原先的耦合头变成了解耦头,并且从 YOLOv5 的 Anchor-Based 变成了 Anchor-Free。
C3 | C2f |
从上图可以看出,不再有之前的 objectness 分支,只有解耦的分类和回归分支,并且其回归分支使用了 Distribution Focal Loss 中提出的积分形式表示法。
Loss 计算
Loss 计算过程包括 2 个部分: 正负样本分配策略和 Loss 计算。
正负样本分配策略
现代目标检测器大部分都会在正负样本分配策略上面做文章,典型的如 YOLOX 的 simOTA、TOOD 的 TaskAlignedAssigner 和 RTMDet 的 DynamicSoftLabelAssigner,这类 Assigner 大都是动态分配策略,而 YOLOv5 采用的依然是静态分配策略。考虑到动态分配策略的优异性,YOLOv8 算法中则直接引用了 TOOD 的 TaskAlignedAssigner。
TaskAlignedAssigner 的匹配策略简单总结为: 根据分类与回归的分数加权的分数选择正样本。
Loss计算
Loss 计算包括 2 个分支: 分类和回归分支,没有了之前的 objectness 分支。
分类分支依然采用 BCE Loss
回归分支需要和 Distribution Focal Loss 中提出的积分形式表示法绑定,因此使用了 Distribution Focal Loss, 同时还使用了 CIoU Loss。3 个 Loss 采用一定权重比例加权即可。
训练数据增强
数据增强方面和 YOLOv5 差距不大,只不过引入了 YOLOX 中提出的最后 10 个 epoch 关闭 Mosaic 的操作。假设训练 epoch 是 500,其示意图如下所示:
考虑到不同模型应该采用的数据增强强度不一样,因此对于不同大小模型,有部分超参会进行修改,典型的如大模型会开启 MixUp 和 CopyPaste。数据增强后典型效果如下所示:
训练策略
YOLOv8 的训练策略和 YOLOv5 没有啥区别,最大区别就是模型的训练总 epoch 数从 300 提升到了 500,这也导致训练时间急剧增加。以 YOLOv8-S 为例,其训练策略汇总如下:
模型推理过程
YOLOv8 的推理过程和 YOLOv5 几乎一样,唯一差别在于前面需要对 Distribution Focal Loss 中的积分表示 bbox 形式进行解码,变成常规的 4 维度 bbox,后续计算过程就和 YOLOv5 一样了。
其推理和后处理过程为:
(1) bbox 积分形式转换为 4d bbox 格式
对 Head 输出的 bbox 分支进行转换,利用 Softmax 和 Conv 计算将积分形式转换为 4 维 bbox 格式
(2) 维度变换
YOLOv8 输出特征图尺度为 80x80、40x40 和 20x20 的三个特征图。Head 部分输出分类和回归共 6 个尺度的特征图。 将 3 个不同尺度的类别预测分支、bbox 预测分支进行拼接,并进行维度变换。为了后续方便处理,会将原先的通道维度置换到最后,类别预测分支 和 bbox 预测分支 shape 分别为 (b, 80x80+40x40+20x20, 80)=(b,8400,80),(b,8400,4)。
(3) 解码还原到原图尺度
分类预测分支进行 Sigmoid 计算,而 bbox 预测分支需要进行解码,还原为真实的原图解码后 xyxy 格式。
(4) 阈值过滤
遍历 batch 中的每张图,采用 score_thr 进行阈值过滤。在这过程中还需要考虑 multi_label 和 nms_pre,确保过滤后的检测框数目不会多于 nms_pre。
(5) 还原到原图尺度和 nms
基于前处理过程,将剩下的检测框还原到网络输出前的原图尺度,然后进行 nms 即可。最终输出的检测框不能多于 max_per_img。
有一个特别注意的点:YOLOv5 中采用的 Batch shape 推理策略,在 YOLOv8 推理中暂时没有开启,不清楚后面是否会开启,在 MMYOLO 中快速测试了下,如果开启 Batch shape 会涨大概 0.1~0.2。
网络模型解析
卷积神经单元(model.py)
在ultralytics/nn/modules.py
文件中定义了yolov8网络中的卷积神经单元。
autopad
- 功能: 返回pad的大小,使得padding后输出张量的大小不变。
- 参数:
k
: 卷积核(kernel)的大小。类型可能是一个int
也可能是一个序列
。p
: 填充(padding)的大小。默认为None
。d
: 扩张率(dilation rate)的大小, 默认为1
。普通卷积的扩张率为1,空洞卷积的扩张率大于1。
假设k
为原始卷积核大小,d
为卷积扩张率(dilation rate),加入空洞之后的实际卷积核尺寸与原始卷积核尺寸之间的关系:k =d(k-1)+1
。
通常,如果我们添加
p
h
p_h
ph行填充(大约一半在顶部,一半在底部)和
p
w
p_w
pw列填充(大约一半在左侧,一半在右侧),则输出的形状为
(
n
h
−
k
h
+
p
h
+
1
)
×
(
n
w
−
k
w
+
p
w
+
1
)
(n_h-k_h+p_h+1)\times (n_w-k_w+p_w+1)
(nh−kh+ph+1)×(nw−kw+pw+1)
当设置
p
h
=
k
h
−
1
p_h=k_h-1
ph=kh−1和
p
w
=
k
w
−
1
p_w=k_w-1
pw=kw−1时,输入和输出具有相同的高度和宽度。
假设p
为填充(padding)的大小(通常,
p
h
=
p
w
=
p
2
p_h=p_w=\frac{p}{2}
ph=pw=2p)。一般来说
w
=
k
k
h
=
k
w
=
k
w = k k_h=k_w=k
w=kkh=kw=k,且为奇数。
则当p=k//2
时,padding后输出张量的大小不变。
def autopad(k, p=None, d=1): # kernel(卷积核), padding(填充), dilation(扩张)
# 返回pad的大小,使得padding后输出张量的shape不变
if d > 1: # 如果采用扩张卷积,则计算扩张后实际的kernel大小
k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] #
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # 自动pad
return p
Conv
- 功能: 标准的卷积
- 参数:输入通道数(
c1
), 输出通道数(c2
), 卷积核大小(k
,默认是1), 步长(s
,默认是1), 填充(p
,默认为None), 组(g
, 默认为1), 扩张率(d
,默认为1), 是否采用激活函数(act
,默认为True, 且采用SiLU为激活函数)
激活函数采用的是SiLU
。
class Conv(nn.Module):
# 标准的卷积 参数(输入通道数, 输出通道数, 卷积核大小, 步长, 填充, 组, 扩张, 激活函数)
default_act = nn.SiLU() # 默认的激活函数
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
super().__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False) # 2维卷积,其中采用了自动填充函数。
self.bn = nn.BatchNorm2d(c2) # 使得每一个batch的特征图均满足均值为0,方差为1的分布规律
# 如果act=True 则采用默认的激活函数SiLU;如果act的类型是nn.Module,则采用传入的act; 否则不采取任何动作 (nn.Identity函数相当于f(x)=x,只用做占位,返回原始的输入)。
self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
def forward(self, x): # 前向传播
return self.act(self.bn(self.conv(x))) # 采用BatchNorm
def forward_fuse(self, x): # 用于Model类的fuse函数融合 Conv + BN 加速推理,一般用于测试/验证阶段
return self.act(self.conv(x)) # 不采用BatchNorm
DWConv
深度可分离卷积,继承自Conv
g=math.gcd(c1, c2)
分组数是输入通道(c1
)和输出通道(c2
)的最大公约数。(因为分组卷积时,分组数需要能够整除输入通道和输出通道)
class DWConv(Conv):
# 深度可分离卷积
def __init__(self, c1, c2, k=1, s=1, d=1, act=True): # ch_in, ch_out, kernel, stride, dilation, activation
super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), d=d, act=act)
DWConvTranspose2d
带有深度分离的转置卷积,继承自nn.ConvTranspose2d
groups=math.gcd(c1, c2)
分组数是输入通道(c1
)和输出通道(c2
)的最大公约数。(因为分组卷积时,分组数需要能够整除输入通道和输出通道)
class DWConvTranspose2d(nn.ConvTranspose2d):
# Depth-wise transpose convolution
def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0): # 输入通道, 输出通道, 卷积核大小, 步长, padding, padding_out
super().__init__(c1, c2, k, s, p1, p2, groups=math.gcd(c1, c2))
ConvTranspose
和Conv类似,只是把Conv2d
换成了ConvTranspose2d
。
class ConvTranspose(nn.Module):
# Convolution transpose 2d layer
default_act = nn.SiLU() # default activation
def __init__(self, c1, c2, k=2, s=2, p=0, bn=True, act=True):
super().__init__()
self.conv_transpose = nn.ConvTranspose2d(c1, c2, k, s, p, bias=not bn)
self.bn = nn.BatchNorm2d(c2) if bn else nn.Identity()
self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
def forward(self, x):
return self.act(self.bn(self.conv_transpose(x)))
DFL(Distribution Focal Loss)
本篇文章(https://ieeexplore.ieee.org/document/9792391)提出了GFL(了Generalized Focal Loss)。GFL具体又包括Quality Focal Loss(QFL)和Distribution Focal Loss(DFL),其中QFL用于优化分类和质量估计联合分支,DFL用于优化边框分支。
class DFL(nn.Module):
# Integral module of Distribution Focal Loss (DFL) proposed in Generalized Focal Loss
def __init__(self, c1=16):
super().__init__()
self.conv = nn.Conv2d(c1, 1, 1, bias=False).requires_grad_(False)
x = torch.arange(c1, dtype=torch.float)
self.conv.weight.data[:] = nn.Parameter(x.view(1, c1, 1, 1))
self.c1 = c1
def forward(self, x):
b, c, a = x.shape # batch, channels, anchors
return self.conv(x.view(b, 4, self.c1, a).transpose(2, 1).softmax(1)).view(b, 4, a)
# return self.conv(x.view(b, self.c1, 4, a).softmax(1)).view(b, 4, a)
TransformerLayer
关于
Transformer
的理解和torch.nn.MultiheadAttention
的用法,请参考博客《详解注意力机制和Transformer》
我们可以发现它和yolo中的TransformerLayer部分只是少了层规范化(LayerNorm),以及在Feed-Forward Networks 中只采用了两个不带偏置线性层,且没有采用激活函数。
TransformerLayer代码实现如下:
class TransformerLayer(nn.Module):
# Transformer layer (LayerNorm layers removed for better performance)
def __init__(self, c, num_heads): # c: 词特征向量的大小 num_heads 检测头的个数。
super().__init__()
self.q = nn.Linear(c, c, bias=False)# 计算query, in_features=out_features=c
self.k = nn.Linear(c, c, bias=False)# 计算key
self.v = nn.Linear(c, c, bias=False)# 计算value
self.ma = nn.MultiheadAttention(embed_dim=c, num_heads=num_heads) # 多头注意力机制
self.fc1 = nn.Linear(c, c, bias=False)
self.fc2 = nn.Linear(c, c, bias=False)
def forward(self, x):
x = self.ma(self.q(x), self.k(x), self.v(x))[0] + x # 多头注意力机制+残差连接
x = self.fc2(self.fc1(x)) + x # 两个全连接层+ 残差连接
return x
如果输入是x
,x的大小是(s,n,c)
。 其中n
是batch size, s
是源序列长度,c
是词特征向量的大小(embed_dim)。
然后x分别通过3个Linear层
(线性层的结构相同,但是可学习参数不同)计算得到键k
、查询q
、值v
。因为线性层的输入特征数和输出特征数均等于c, 所以k,q,v的大小也是(s,n,c)
。
接着,把k、q、v作为参数输入到多头注意力ma
中,返回两个结果attn_output
(注意力机制的输出)和attn_output_weights
(注意力机制的权重)。在这里,我们只需要注意力机制的输出就可以,因此,我们取索引0 self.ma(self.q(x), self.k(x), self.v(x))[0]
,它的大小是(s,n,c)
。+x
表示残差连接,不改变x的形状。
self.fc2(self.fc1(x))
表示经过两个全连接层,输出大小是(s,n,c)
。+x
表示残差连接,不改变x的形状。因此最终输出的形状大小和输入的形状一样。
Transformer Block
TransformerBlock是把若干个TransformerLayer串联起来。
对于图像数据而言,输入数据形状是 [batch, channel, height, width]
,变换成 [height × width, batch, channel]
。height × width
把图像中各个像素点看作一个单词,其对应通道的信息连在一起就是词向量。channel
就是词向量的长度。
TransformerBlock的实现代码如下:
class TransformerBlock(nn.Module):
def __init__(self, c1, c2, num_heads, num_layers):
super().__init__()
self.conv = None
if c1 != c2:
self.conv = Conv(c1, c2)
self.linear = nn.Linear(c2, c2) # learnable position embedding
self.tr = nn.Sequential(*(TransformerLayer(c2, num_heads) for _ in range(num_layers)))
self.c2 = c2
def forward(self, x): # x:(b,c1,w0,h0)
if self.conv is not None:
x = self.conv(x) # x:(b,c2,w,h)
b, _, w, h = x.shape
p = x.flatten(2).permute(2, 0, 1) # flatten后:(b,c2,w*h) p: (w*h,b,c2)
# linear后: (w*h,b,c2) tr后: (w*h,b,c2) permute后: (b,c2,w*h) reshape后:(b,c2,w,h)
return self.tr(p + self.linear(p)).permute(1, 2, 0).reshape(b, self.c2, w, h)
1)输入的x
大小为(b,c1,w,h)
。其中b
为batch size, c1
是输入通道数大小, w
和h
分别表示图像的宽和高。
2)经过Conv层:Conv层中的2d卷积,卷积核大小是1x1, 步长为1,无填充,扩张率为1。因此不改变w和h, 只改变输出通道数,形状变为(b,c2,w,h)
。Conv层中的BN和SiLU不改变形状大小。输出的x大小为(b,c2,w,h)
3)对x进行变换得到p
: x.flatten(2)后,大小变为 (b,c2,w*h)
permute(2, 0, 1)后,p的大小为(w*h,b,c2)
- 将p输入到线性层后,因为线性层的输入特征数和输出特征数相等,因此输出的大小为
(w*h,b,c2)
。
+p` 进行残差连接后,大小不变,仍为`(w*h,b,c2)
- 然后将上一步的结果输入到
num_layers
个TransformerLayer中。w*h
相当于序列长度,b
是批量的大小,c2
相当于词嵌入特征长度。每个TransformerLayer的输入和输出的大小不变。经过若干个TransformerLayer后,大小是(w*h,b,c2)
。
6)permute(1, 2, 0)后: 形状变为(b,c2,w*h) ``reshape(b, self.c2, w, h)
后:(b,c2,w,h)
Bottleneck
先使用 3x3 卷积降维,剔除冗余信息;再使用 3×3 卷积升维。
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5): # ch_in, ch_out, shortcut, groups, kernels, expand
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, k[0], 1) # 输入通道: c1, 输出通道:c_ , 卷积核:3x3, 步长1
self.cv2 = Conv(c_, c2, k[1], 1, g=g) # 输入通道:c_ , 输出通道c2, 卷积核:3x3, 步长1
self.add = shortcut and c1 == c2 # 当传入的shortcut参数为true,且c1和c2相等时,则使用残差连接。
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
第一层卷积,输入通道: c1
, 输出通道:c_
, 卷积核:3x3, 步长1
第一层卷积,输入通道: c_
, 输出通道:c2
, 卷积核:3x3, 步长1
其中
c
_
=
c
2
2
c\_=\frac{c_2}{2}
c_=2c2。当c1和c2相等时,采用残差连接。
BottleneckCSP
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().__init__()
c_ = int(c2 * e) # hidden channels
# 输出x的大小是(b,c1,w,h)
self.cv1 = Conv(c1, c_, 1, 1) # cv1的大小为(b,c_,w,h)
self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) # cv2的大小为(b,c_,w,h)
self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) # m通过Conv2d,变成cv3,大小是(b,c_,w,h)
self.cv4 = Conv(2 * c_, c2, 1, 1)
self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3)
self.act = nn.SiLU()
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
# cv1通过n个串联的bottleneck,变成m,大小为(b,c_,w,h)
def forward(self, x):
y1 = self.cv3(self.m(self.cv1(x))) # (b,c_,w,h)
y2 = self.cv2(x) # (b,c_,w,h)
return self.cv4(self.act(self.bn(torch.cat((y1, y2), 1))))
# cat后:(b,2*c_,w,h) 返回cv4: (b,c2,w,h)
1)输出x的大小是(b,c1,w,h)
, 然后有两条计算路径分别计算得到y1和y2。
y1
的计算路径:先x通过cv1,大小变成(b,c_,w,h)
。cv1通过n个串联的bottleneck,变成m,大小为(b,c_,w,h)
。m通过cv3, 得到y1, 大小是(b,c_,w,h)
y2
的计算路径:x通过cv2得到y2,大小是(b,c_,w,h)
2)y1和y2在dim=1
处连接, 大小是(b,2*c_,w,h)
, 然后再通过BN和SiLU,大小不变。
3)最终,通过cv4, 返回结果的大小是(b,c2,w,h)
C3
与 BottleneckCSP 类似,但少了 1 个 Conv、1 个 BN、1 个 Act,运算量更少。总共只有3次卷积(cv1,cv2,cv3)。
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)
self.cv2 = Conv(c1, c_, 1, 1)
self.cv3 = Conv(2 * c_, c2, 1) # optional act=FReLU(c2)
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))
C2
C2只有两个卷积(cv1,cv2)的CSP Bottleneck。
class C2(nn.Module):
# CSP Bottleneck with 2 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__()
# 假设输入的x大小是(b,c1,w,h)
self.c = int(c2 * e) # hidden channels e=0.5,对输出通道进行平分。
self.cv1 = Conv(c1, 2 * self.c, 1, 1) # cv1的大小是(b,c2,w,h)
self.cv2 = Conv(2 * self.c, c2, 1) # optional act=FReLU(c2)
# self.attention = ChannelAttention(2 * self.c) # or SpatialAttention() #此处可以使用空间注意力或者跨通道的注意力机制。
self.m = nn.Sequential(*(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))) # a通过n个串联的Bottleneck后的到m,m的大小是(b,c,w,h)
def forward(self, x):
a, b = self.cv1(x).split((self.c, self.c), 1)# 对cv进行在维度1进行平分,a和b的大小都是(b,c,w,h)
return self.cv2(torch.cat((self.m(a), b), 1)) # 把m和b在维度1进行cat后,大小是(b,c2,w,h)。最终通过cv2,大小是(b,c2,w,h)
1)输出x的大小是(b,c1,w,h)
, 通过Conv层,得到cv1, cv1的大小是(b,c2,w,h)
- 然后再dim=1的维度上对cv1进行分割,a和b的大小都是
(b,c2/2,w,h)
。 - a通过n个串联的Bottleneck后的到m,m的大小是
(b,c,w,h)
- 把m和b在维度1进行cat后,大小是
(b,c2,w,h)
。最终m通过cv2,输出的大小是(b,c2,w,h)
C2f
C2f与C2相比,每个Bottleneck的输出都会被Concat到一起。
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__()
# 假设输入的x大小是(b,c1,w,h)
self.c = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, 2 * self.c, 1, 1) # cv1的大小是(b,c2,w,h)
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)) # n个Bottleneck组成的ModuleList,可以把m看做是一个可迭代对象
def forward(self, x):
y = list(self.cv1(x).split((self.c, self.c), 1))
# cv1的大小是(b,c2,w,h),对cv1在维度1等分成两份(假设分别是a和b),a和b的大小均是(b,c2/2,w,h)。此时y=[a,b]。
y.extend(m(y[-1]) for m in self.m)
# 然后对列表y中的最后一个张量b输入到ModuleList中的第1个bottleneck里,得到c,c的大小是(b,c2/2,w,h)。然后把c也加入y中。此时y=[a,b,c]
# 重复上述操作n次(因为是n个bottleneck),最终得到的y列表中一共有n+2个元素。
return self.cv2(torch.cat(y, 1))
# 对列表y中的张量在维度1进行连接,得到的张量大小是(b,(n+2)*c2/2,w,h)。
# 最终通过cv2,输出张量的大小是(b,c2,w,h)
1)cv1的大小是(b,c2,w,h)
,对cv1在维度1等分成两份(假设分别是a和b),a和b的大小均是(b,c2/2,w,h)
。此时y=[a,b]。
2)然后对列表y中的最后一个张量b输入到ModuleList中的第1个bottleneck里,得到c,c的大小是(b,c2/2,w,h)
。然后把c也加入y中。此时y=[a,b,c]。
3)上述步骤重复上述操作n次(因为是n个bottleneck),最终得到的y列表中一共有n+2个元素。
4)对列表y中的张量在维度1进行连接,得到的张量大小是(b,(n+2)*c2/2,w,h)
。
5)最终通过cv2,输出张量的大小是(b,c2,w,h)
ChannelAttention
通道注意力模型: 通道维度不变,压缩空间维度。该模块关注输入图片中有意义的信息。
class ChannelAttention(nn.Module):
# Channel-attention module https://github.com/open-mmlab/mmdetection/tree/v3.0.0rc1/configs/rtmdet
def __init__(self, channels: int) -> None:
super().__init__()
self.pool = nn.AdaptiveAvgPool2d(1) # 自适应平均池化后,大小为(b,c,1,1)
self.fc = nn.Conv2d(channels, channels, 1, 1, 0, bias=True)
self.act = nn.Sigmoid()
def forward(self, x: torch.Tensor) -> torch.Tensor:
return x * self.act(self.fc(self.pool(x)))
1)假设输入的数据大小是(b,c,w,h)
2)通过自适应平均池化使得输出的大小变为(b,c,1,1)
3)通过2d卷积和sigmod激活函数后,大小是(b,c,1,1)
4)将上一步输出的结果和输入的数据相乘,输出数据大小是(b,c,w,h)
。
SpatialAttention
空间注意力模块:空间维度不变,压缩通道维度。该模块关注的是目标的位置信息。
class SpatialAttention(nn.Module):
# Spatial-attention module
def __init__(self, kernel_size=7):
super().__init__()
assert kernel_size in (3, 7), 'kernel size must be 3 or 7' # kernel size 的大小必须是3或者7
padding = 3 if kernel_size == 7 else 1 # 当kernel_size是7时,padding=3; 当kernel_size是3时,padding=1
self.cv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.act = nn.Sigmoid()
def forward(self, x):
return x * self.act(self.cv1(torch.cat([torch.mean(x, 1, keepdim=True), torch.max(x, 1, keepdim=True)[0]], 1)))
1) 假设输入的数据x是(b,c,w,h)
,并进行两路处理。
2)其中一路在通道维度上进行求平均值,得到的大小是(b,1,w,h)
;另外一路也在通道维度上进行求最大值,得到的大小是(b,1,w,h)
。
3) 然后对上述步骤的两路输出进行连接,输出的大小是(b,2,w,h)
4)经过一个二维卷积网络,把输出通道变为1,输出大小是(b,1,w,h)
4)将上一步输出的结果和输入的数据x相乘,最终输出数据大小是(b,c,w,h)
。
CBAM
CBAM就是把ChannelAttention和SpatialAttention串联在一起。
class CBAM(nn.Module):
# Convolutional Block Attention Module
def __init__(self, c1, kernel_size=7): # ch_in, kernels
super().__init__()
self.channel_attention = ChannelAttention(c1)
self.spatial_attention = SpatialAttention(kernel_size)
def forward(self, x):
return self.spatial_attention(self.channel_attention(x))
C1
总共只有3次卷积(cv1,cv2,cv3)的Bottleneck。
class C1(nn.Module):
# CSP Bottleneck with 1 convolution
def __init__(self, c1, c2, n=1): # ch_in, ch_out, number
super().__init__()
self.cv1 = Conv(c1, c2, 1, 1)
self.m = nn.Sequential(*(Conv(c2, c2, 3) for _ in range(n)))
def forward(self, x):
y = self.cv1(x)
return self.m(y) + y
1)假设输入的数据是(b,c1,w,h)
- 首先通过一个Conv块,得到
y
, 大小为(b,c2,w,h)
- 然后让y通过n个3x3的Conv块,得到
m
- 最后让m和y相加。
C3x
C3x 继承自C3, 变换是Bottleneck中的卷积核大小变为(1,3)和(3,3)
class C3x(C3):
# C3 module with cross-convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
super().__init__(c1, c2, n, shortcut, g, e)
self.c_ = int(c2 * e)
self.m = nn.Sequential(*(Bottleneck(self.c_, self.c_, shortcut, g, k=((1, 3), (3, 1)), e=1) for _ in range(n)))
C3TR
C3TR继承自C3, n 个 Bottleneck 更换为 1 个 TransformerBlock。
class C3TR(C3):
# C3 module with TransformerBlock()
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
super().__init__(c1, c2, n, shortcut, g, e)
c_ = int(c2 * e)
self.m = TransformerBlock(c_, c_, 4, n)# num_heads=4, num_layers=n
SPP
空间金字塔模型:三个MaxPool 并行连接,kernel size分别为5 * 5
,9 * 9
和13 * 13
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))
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)
self.cv2 = Conv(c_ * 4, c2, 1, 1)
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))
Focus
Focus模块在v5中是图片进入backbone前,对图片进行切片操作,具体操作是在一张图片中每隔一个像素拿到一个值,类似于邻近下采样,这样就拿到了四张图片,四张图片互补,长的差不多,但是没有信息丢失,这样一来,将W、H信息就集中到了通道空间,输入通道扩充了4倍,即拼接起来的图片相对于原先的RGB三通道模式变成了12个通道,最后将得到的新图片再经过卷积操作,最终得到了没有信息丢失情况下的二倍下采样特征图。
例如: 原始的640 × 640 × 3的图像输入Focus结构,采用切片操作,先变成320 × 320 × 12的特征图,再经过一次卷积操作,最终变成320 × 320 × 32的特征图。切片操作如下:
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=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的定义如下:
第一个切片: x[..., ::2, ::2]
第二个切片: x[..., 1::2, ::2]
第三个切片: x[..., ::2, 1::2]
第四个切片: x[..., 1::2, 1::2]
把上述四个切片连接在一起, 可以看到w,h是原始数据的一半,通道数变为原来的四倍。
GhostConv
Ghost卷积来自华为诺亚方舟实验室,《GhostNet: More Features from Cheap Operations》发表于2020年的CVPR上。提供了一个全新的Ghost模块,旨在通过廉价操作生成更多的特征图。
原理如下图所示:
Ghost Module分为两步操作来获得与普通卷积一样数量的特征图:
Step1:少量卷积(比如正常用128个卷积核,这里就用64个,从而减少一半的计算量);
Step2:cheap operations,用图中的Φ表示,Φ是诸如33、55的卷积,并且是逐个特征图的进行卷积(Depth-wise convolutional,深度卷积)。
class GhostConv(nn.Module):
# Ghost Convolution https://github.com/huawei-noah/ghostnet
def __init__(self, c1, c2, k=1, s=1, g=1, act=True): # ch_in, ch_out, kernel, stride, groups
super().__init__()
c_ = c2 // 2 # hidden channels
self.cv1 = Conv(c1, c_, k, s, None, g, act=act)
self.cv2 = Conv(c_, c_, 5, 1, None, c_, act=act) # 分组数=c_=通道数,进行point-wise的深度分离卷积
def forward(self, x):
y = self.cv1(x)
return torch.cat((y, self.cv2(y)), 1)
GhostBottleneck
class GhostBottleneck(nn.Module):
# Ghost Bottleneck https://github.com/huawei-noah/ghostnet
def __init__(self, c1, c2, k=3, s=1): # ch_in, ch_out, kernel, stride
super().__init__()
c_ = c2 // 2
self.conv = nn.Sequential(
GhostConv(c1, c_, 1, 1), # 卷积核的大小是1*1,属于point-wise的深度可分离卷积
DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # 输入通道数和输出通道数相等,属于depth-wise的深度可分离卷积
GhostConv(c_, c2, 1, 1, act=False)) #point-wise的深度可分离卷积,且不采用偏置项。
self.shortcut = nn.Sequential(DWConv(c1, c1, k, s, act=False), Conv(c1, c2, 1, 1,
act=False)) if s == 2 else nn.Identity()
def forward(self, x):
return self.conv(x) + self.shortcut(x)
C3Ghost
C3Ghost继承自C3, Bottleneck更换为GhostBottleneck
class C3Ghost(C3):
# C3 module with GhostBottleneck()
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
super().__init__(c1, c2, n, shortcut, g, e)
c_ = int(c2 * e) # hidden channels
self.m = nn.Sequential(*(GhostBottleneck(c_, c_) for _ in range(n)))
Concat
当dimension=1时,将多张相同尺寸的图像在通道维度维度上进行拼接。
class Concat(nn.Module):
# Concatenate a list of tensors along dimension
def __init__(self, dimension=1):
super().__init__()
self.d = dimension
def forward(self, x):
return torch.cat(x, self.d)
Yolov8实操
快速入门
环境配置
新建虚拟环境
# 新建名为pytorch的虚拟环境,python=3.8
conda create -n pytorch python=3.8 -y
# 查看当前存在的虚拟环境
conda env list
# 进入pytorch
conda activate pytorch
安装pytorch和torchvision
pip install torch-1.9.0+cu102-cp38-cp38-linux_x86_64.whl
pip install torchvision-0.10.0+cu102-cp38-cp38-linux_x86_64.whl
安装ultralytics
git clone https://github.com/ultralytics/ultralytics
cd ultralytics
pip install -e .
数据集准备
数据集制作参考: YOLO格式数据集制作
# 训练/验证/测试 数据
train: /data/zyw/project/dataset/finalTrafficLightDataset/train/images
val: /data/zyw/project/dataset/finalTrafficLightDataset/val/images
test: /data/zyw/project/dataset/finalTrafficLightDataset/WPIDataset/images
# 类别个数
nc: 12
# 类别名称
names: ["greenCircle", "yellowCircle", "redCircle", "greenLeft", "yellowLeft", "redLeft", "greenRight", "yellowRight", "redRight", "greenForward", "yellowForward", "redForward"]
模型的训练/验证/预测/导出
使用CLI
如果你想对模型进行训练、验证或运行推断,并且不需要对代码进行任何修改,那么使用YOLO命令行接口是最简单的入门方法。
YOLO命令行界面(command line interface, CLI) 方便在各种任务和版本上训练、验证或推断模型。CLI不需要定制或代码,可以使用yolo命令从终端运行所有任务。
-
语法
yolo task=detect mode=train model=yolov8n.yaml args... classify predict yolov8n-cls.yaml args... segment val yolov8n-seg.yaml args... export yolov8n.pt format=onnx args...
注意:参数不需要’
- -
'前缀。这些是为后面介绍的特殊命令保留的 -
训练示例
yolo task=detect mode=train model=yolov8n.pt data=coco128.yaml device=0
-
多GPU训练示例
yolo task=detect mode=train model=yolov8n.pt data=coco128.yaml device=\'0,1,2,3\'
-
重写默认的配置参数
# 语法 yolo task= ... mode= ... arg=val # 例子: 进行10个epoch的检测训练,learning_rate为0.01 yolo task=detect mode=train epochs=10 lr0=0.01
-
重写默认配置文件
# 可以在当前工作目录下创建一个默认配置文件的副本 yolo task=init # 然后可以使用cfg=name.yaml命令来传递新的配置文件 yolo cfg=default.yaml
使用python
允许用户在Python项目中轻松使用YOLOv8。它提供了加载和运行模型以及处理模型输出的函数。该界面设计易于使用,以便用户可以在他们的项目中快速实现目标检测。
训练
- 从预训练模型开始训练
from ultralytics import YOLO
model = YOLO("yolov8n.pt") # pass any model type
model.train(epochs=5)
- 从头开始训练
from ultralytics import YOLO
model = YOLO("yolov8n.yaml")
model.train(data="coco128.yaml", epochs=5)
验证
- 训练后验证
from ultralytics import YOLO
model = YOLO("yolov8n.yaml")
model.train(data="coco128.yaml", epochs=5)
model.val() # It'll automatically evaluate the data you trained.
- 单独验证
from ultralytics import YOLO
model = YOLO("model.pt")
# 如果不设置数据的话,就使用model.pt中的data yaml文件
model.val()
# 或者直接设置需要验证的数据。
model.val(data="coco128.yaml")
预测
- 从源文件中预测
from ultralytics import YOLO
model = YOLO("model.pt")
model.predict(source="0") # accepts all formats - img/folder/vid.*(mp4/format). 0 for webcam
model.predict(source="folder", show=True) # Display preds. Accepts all yolo predict arguments
- 返回结果
from ultralytics import YOLO
model = YOLO("model.pt")
outputs = model.predict(source="0", return_outputs=True) # treat predict as a Python generator
for output in outputs:
# each output here is a dict.
# for detection
print(output["det"]) # np.ndarray, (N, 6), xyxy, score, cls
# for segmentation
print(output["det"]) # np.ndarray, (N, 6), xyxy, score, cls
print(output["segment"]) # List[np.ndarray] * N, bounding coordinates of masks
# for classify
print(output["prob"]) # np.ndarray, (num_class, ), cls prob
使用训练器
YOLO模型类是Trainer类的高级包装器。每个YOLO任务都有自己的从BaseTrainer继承来的训练器。
from ultralytics.yolo import v8 import DetectionTrainer, DetectionValidator, DetectionPredictor
# trainer
trainer = DetectionTrainer(overrides={})
trainer.train()
trained_model = trainer.best
# Validator
val = DetectionValidator(args=...)
val(model=trained_model)
# predictor
pred = DetectionPredictor(overrides={})
pred(source=SOURCE, model=trained_model)
# resume from last weight
overrides["resume"] = trainer.last
trainer = detect.DetectionTrainer(overrides=overrides)
多任务支持
yolov8支持检测、分割、分类 三种任务。
检测
物体检测是一项涉及识别图像或视频流中物体的位置和类别的任务。
对象检测器的输出是一组包围图像中的对象的包围框,以及每个框的类标签和置信度分数。当你需要识别场景中感兴趣的物体,但不需要知道物体的确切位置或它的确切形状时,物体检测是一个很好的选择。
YOLOv8检测模型没有后缀,是默认的YOLOv8模型,即yolov8n.pt,并在COCO上进行预训练。
训练
在COCO128数据集上训练YOLOv8n 100个epoch,图像大小为640。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n.yaml") # build a new model from scratch
model = YOLO("yolov8n.pt") # load a pretrained model (recommended for training)
# Train the model
results = model.train(data="coco128.yaml", epochs=100, imgsz=640)
- CLI
yolo task=detect mode=train data=coco128.yaml model=yolov8n.pt epochs=100 imgsz=640
验证
验证训练YOLOv8n模型在COCO128数据集上的准确性。不需要传递参数,因为模型保留了它的训练数据和参数作为模型属性。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Validate the model
results = model.val() # no arguments needed, dataset and settings remembered
- CLI
yolo task=detect mode=val model=yolov8n.pt # val official model
yolo task=detect mode=val model=path/to/best.pt # val custom model
预测
使用经过训练的YOLOv8n模型对图像进行预测。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Predict with the model
results = model("https://ultralytics.com/images/bus.jpg") # predict on an image
- CLI
yolo task=detect mode=predict model=yolov8n.pt source="https://ultralytics.com/images/bus.jpg" # predict with official model
yolo task=detect mode=predict model=path/to/best.pt source="https://ultralytics.com/images/bus.jpg" # predict with custom model
导出
导出YOLOv8n模型到不同的格式,如ONNX, CoreML等。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom trained
# Export the model
model.export(format="onnx")
- CLI
yolo mode=export model=yolov8n.pt format=onnx # export official model
yolo mode=export model=path/to/best.pt format=onnx # export custom trained model
可用的YOLOv8导出格式包括:
Format | format= | Model |
---|---|---|
https://pytorch.org/ | - | yolov8n.pt |
https://pytorch.org/docs/stable/jit.html | torchscript | yolov8n.torchscript |
https://onnx.ai/ | onnx | yolov8n.onnx |
https://docs.openvino.ai/latest/index.html | openvino | yolov8n_openvino_model/ |
https://developer.nvidia.com/tensorrt | engine | yolov8n.engine |
https://github.com/apple/coremltools | coreml | yolov8n.mlmodel |
https://www.tensorflow.org/guide/saved_model | saved_model | yolov8n_saved_model/ |
https://www.tensorflow.org/api_docs/python/tf/Graph | pb | yolov8n.pb |
https://www.tensorflow.org/lite | tflite | yolov8n.tflite |
https://coral.ai/docs/edgetpu/models-intro/ | edgetpu | yolov8n_edgetpu.tflite |
https://www.tensorflow.org/js | tfjs | yolov8n_web_model/ |
https://github.com/PaddlePaddle | paddle | yolov8n_paddle_model/ |
实例分割
实例分割比对象检测更进一步,涉及识别图像中的单个对象,并将它们从图像的其余部分分割出来
实例分割模型的输出是一组掩码或轮廓,它们勾勒出图像中的每个对象,以及每个对象的类标签和置信度分数。当你不仅需要知道物体在图像中的位置,还需要知道它们的确切形状时,实例分割非常有用。
YOLOv8分割模型使用-seg
后缀,即yolov8n- seg .pt
,并在COCO上进行预训练。
训练
在COCO128-seg数据集上训练YOLOv8n-seg 100个epoch,图像大小为640。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n-seg.yaml") # build a new model from scratch
model = YOLO("yolov8n-seg.pt") # load a pretrained model (recommended for training)
# Train the model
results = model.train(data="coco128-seg.yaml", epochs=100, imgsz=640)
- CLI
yolo task=segment mode=train data=coco128-seg.yaml model=yolov8n-seg.pt epochs=100 imgsz=640
验证
在COCO128-seg数据集上验证训练过的YOLOv8n-seg模型的准确性。不需要传递参数,因为模型保留了它的训练数据和参数作为模型属性。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n-seg.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Validate the model
results = model.val() # no arguments needed, dataset and settings remembered
- CLI
yolo task=segment mode=val model=yolov8n-seg.pt # val official model
yolo task=segment mode=val model=path/to/best.pt # val custom model
预测
使用训练过的YOLOv8n-seg模型对图像进行预测。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n-seg.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Predict with the model
results = model("https://ultralytics.com/images/bus.jpg") # predict on an image
- CLI
yolo task=segment mode=predict model=yolov8n-seg.pt source="https://ultralytics.com/images/bus.jpg" # predict with official model
yolo task=segment mode=predict model=path/to/best.pt source="https://ultralytics.com/images/bus.jpg" # predict with custom model
导出
导出YOLOv8n-seg模型到不同的格式,如ONNX, CoreML等。
- python
from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n-seg.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom trained
# Export the model
model.export(format="onnx")
- CLI
yolo mode=export model=yolov8n-seg.pt format=onnx # export official model
yolo mode=export model=path/to/best.pt format=onnx # export custom trained model
分类
图像分类是三个任务中最简单的,涉及到将整个图像分类到一组预定义的类中的一个。
图像分类器的输出是一个单一的类标签和一个置信度分数。当您只需要知道图像属于什么类,而不需要知道该类对象的位置或它们的确切形状时,图像分类是有用的。
YOLOv8分类模型使用-cls
后缀,即yolov8n-cls.pt
,并在ImageNet上进行预训练。其他的使用方法和检测与分割类似,不再赘述。
配置
YOLO设置和超参数在模型的性能、速度和准确性中起着至关重要的作用。这些设置和超参数可以在模型开发过程的各个阶段影响模型的行为,包括训练、验证和预测。
正确地设置和调优这些参数可以对模型有效地从训练数据中学习并推广到新数据的能力产生重大影响。例如,选择合适的学习率、批大小和优化算法会极大地影响模型的收敛速度和精度。同样,设置正确的置信度阈值和非最大抑制(NMS)阈值也会影响模型在检测任务上的性能。
设置操作类型
YOLO模型可用于各种任务,包括检测、分割和分类。这些任务的不同之处在于它们产生的输出类型和它们要解决的特定问题。
- 检测:检测任务涉及识别和定位图像或视频中感兴趣的对象或区域。YOLO模型通过预测图像中物体的边界框和类标签,可以用于物体检测任务。
- 分割:分割任务包括将图像或视频划分为对应于不同对象或类的区域或像素。
- 分类:分类任务包括为输入(如图像或文本)分配类标签。YOLO模型可以通过预测输入图像的类别标签来进行图像分类任务
根据你要解决的特定问题,YOLO模型可以在不同的模式下使用。这些模式包括train、val和predict。
- 训练(Train):训练模式用于在数据集上训练模型。这种模式通常在模型的开发和测试阶段使用。
- Val: Val模式用于评估模型在验证数据集上的性能。这种模式通常用于调整模型的超参数和检测过拟合。
- Predict: Predict模式用于在新数据上使用模型进行预测。这种模式通常用于生产环境或将模型部署给用户时。
Key | Value | Description |
---|---|---|
task | detect | 可选择:detect, segment, classify |
mode | train | 可选择: train, val, predict |
resume | False | 当设置为True时,恢复上一次的任务。当设置为False时,从给定的model.pt中恢复。 |
model | null | 设置模型。格式因任务类型而异。支持model_name, model.yaml,model.pt |
data | null | 设置数据,支持多数类型 data.yaml, data_folder, dataset_name |
训练
YOLO模型的训练设置是指用于在数据集上训练模型的各种超参数和配置。这些设置会影响模型的性能、速度和精度。一些常见的YOLO训练设置包括批量大小、学习率、动量和权重衰减。其他可能影响训练过程的因素包括优化器的选择、损失函数的选择、训练数据集的大小和组成。重要的是要仔细调整和试验这些设置,以实现给定任务的最佳性能。
Key | Value | Description |
---|---|---|
device | ‘’ | Cuda设备,即0或0、1、2、3或cpu。选择可用的cuda0设备 |
epochs | 100 | 需要训练的epoch数 |
workers | 8 | 每个进程使用的cpu worker数。使用DDP自动伸缩 |
batch | 16 | Dataloader的batch大小 |
imgsz | 640 | Dataloader中图像数据的大小 |
optimizer | SGD | 支持的优化器:Adam, SGD, RMSProp |
single_cls | False | 将多类数据作为单类进行训练 |
image_weights | False | 使用加权图像选择进行训练 |
rect | False | 启用矩形训练 |
cos_lr | False | 使用cosine LR调度器 |
lr0 | 0.01 | 初始化学习率 |
lrf | 0.01 | 最终的OneCycleLR学习率 |
momentum | 0.937 | 作为SGD的momentum和Adam的beta1 |
weight_decay | 0.0005 | 优化器权重衰减 |
warmup_epochs | 3.0 | Warmup的epoch数,支持分数 |
warmup_momentum | 0.8 | warmup的初始动量 |
warmup_bias_lr | 0.1 | Warmup的初始偏差lr |
box | 0.05 | Box loss gain |
cls | 0.5 | cls loss gain |
cls_pw | 1.0 | cls BCELoss positive_weight |
obj | 1.0 | bj loss gain (scale with pixels) |
obj_pw | 1.0 | obj BCELoss positive_weight |
iou_t | 0.20 | iou训练时的阈值 |
anchor_t | 4.0 | anchor-multiple阈值 |
fl_gamma | 0.0 | focal loss gamma |
label_smoothing | 0.0 | |
nbs | 64 | nominal batch size |
overlap_mask | True | 分割:在训练中使用掩码重叠 |
mask_ratio | 4 | 分割:设置掩码下采样 |
dropout | False | 分类:训练时使用dropout |
预测
YOLO模型的预测设置是指用于在新数据上使用模型进行预测的各种超参数和配置。这些设置会影响模型的性能、速度和精度。一些常见的YOLO预测设置包括置信度阈值、非最大抑制(NMS)阈值和要考虑的类别数量。其他可能影响预测过程的因素包括输入数据的大小和格式,是否存在额外的特征(如掩码或每个框的多个标签),以及模型正在用于的特定任务。重要的是要仔细调整和试验这些设置,以实现给定任务的最佳性能。
Key | Value | Description |
---|---|---|
source | ultralytics/assets | 输入源。支持图片、文件夹、视频、网址 |
show | False | 查看预测图片 |
save_txt | False | 保存结果到txt文件中 |
save_conf | False | 保存condidence scores |
save_crop | Fasle | |
hide_labels | False | 隐藏labels |
hide_conf | False | 隐藏confidence scores |
vid_stride | False | 输入视频帧率步长 |
line_thickness | 3 | 边框厚度(单位:像素) |
visualize | False | 可视化模型特征 |
augment | False | 增强推理 |
agnostic_nms | False | Class-agnostic NMS |
retina_masks | False | 分割:高分辨率掩模 |
验证
YOLO模型的验证设置是指用于评估模型在验证数据集上性能的各种超参数和配置。这些设置会影响模型的性能、速度和精度。一些常见的YOLO验证设置包括批量大小、训练期间执行验证的频率以及用于评估模型性能的指标。其他可能影响验证过程的因素包括验证数据集的大小和组成,以及模型正在用于的特定任务。重要的是要仔细调整和实验这些设置,以确保模型在验证数据集上表现良好,并检测和防止过拟合。
Key | Value | Description |
---|---|---|
noval | False | |
save_json | False | |
save_hybrid | False | |
conf | 0.001 | 置信度阈值 |
iou | 0.6 | IoU阈值 |
max_det | 300 | 最大检测数 |
half | True | 使用.half()模型 |
dnn | False | 使用OpenCV DNN进行ONNX推断 |
plots | False |
数据扩充
YOLO模型的增强设置是指应用于训练数据的各种变换和修改,以增加数据集的多样性和大小。这些设置会影响模型的性能、速度和精度。一些常见的YOLO增强设置包括应用的转换类型和强度(例如随机翻转、旋转、裁剪、颜色变化),应用每个转换的概率,以及是否存在其他功能,如掩码或每个框多个标签。其他可能影响数据扩充过程的因素包括原始数据集的大小和组成,以及模型正在用于的特定任务。重要的是要仔细调整和实验这些设置,以确保增强后的数据集具有足够的多样性和代表性,以训练高性能的模型。
Key | Value | Description |
---|---|---|
hsv_h | 0.015 | Image HSV-Hue augmentation (fraction) |
hsv_s | 0.7 | Image HSV-Saturation augmentation (fraction) |
hsv_v | 0.4 | Image HSV-Value augmentation (fraction) |
degrees | 0.0 | Image rotation (+/- deg) |
translate | 0.1 | Image translation (+/- fraction) |
scale | 0.5 | Image scale (+/- gain) |
shear | 0.0 | Image shear (+/- deg) |
perspective | 0.0 | Image perspective (+/- fraction), range 0-0.001 |
flipud | 0.0 | Image flip up-down (probability) |
fliplr | 0.5 | Image flip left-right (probability) |
mosaic | 1.0 | Image mosaic (probability) |
mixup | 0.0 | Image mixup (probability) |
copy_paste | 0.0 | Segment copy-paste (probability) |
日志,检查点,绘图和文件管理
在训练YOLO模型时,日志记录、检查点、绘图和文件管理是重要的考虑因素。
- 日志记录:在训练期间记录各种指标和统计数据通常有助于跟踪模型的进展和诊断任何可能出现的问题。这可以通过使用日志库(如TensorBoard)或将日志消息写入文件来实现。
- 检查点:在训练期间,定期保存模型的检查点是一个很好的做法。如果训练过程被中断,或者你想尝试不同的训练配置,这允许你从之前的点恢复训练。绘图:可视化模型的性能和训练过程,有助于理解模型的行为方式和识别潜在问题。这可以使用matplotlib等绘图库完成,也可以使用TensorBoard等日志库来绘图。
- 文件管理:管理训练过程中生成的各种文件,例如模型检查点、日志文件和绘图,可能具有挑战性。有一个清晰和有组织的文件结构是很重要的,以便跟踪这些文件,并使其易于根据需要访问和分析它们。
有效的日志记录、检查点、绘图和文件管理可以帮助您跟踪模型的进度,并使其更容易调试和优化训练过程。
Key | Value | Description |
---|---|---|
project: | ‘runs’ | The project name |
name: | ‘exp’ | The run name. exp gets automatically incremented if not specified, i.e, exp, exp2 … |
exist_ok: | False | |
plots | False | 在验证时保存图像 |
nosave | False | 不保存任何文件 |
自定义模型
Ultralytics YOLO命令行和python接口都只是基本引擎执行器的高级抽象。让我们来看看Trainer引擎。
BaseTrainer
BaseTrainer包含通用的样板训练例程。只要遵循正确的格式,它可以针对任何基于覆盖所需功能或操作的任务进行定制。例如,你可以通过覆盖这些函数来支持你自己的自定义模型和dataloder:
get_model(cfg, weights)
- 用来创建模型的函数get_dataloder()
- 用来创建dataloader的函数
DetectionTrainer
以下是如何使用YOLOv8 DetectionTrainer并自定义它。
from ultralytics.yolo.v8.detect import DetectionTrainer
trainer = DetectionTrainer(overrides={...})
trainer.train()
trained_model = trainer.best # get best model
自定义Detection Trainer
from ultralytics.yolo.v8.detect import DetectionTrainer
class CustomTrainer(DetectionTrainer):
def get_model(self, cfg, weights):
...
def criterion(self, preds, batch):
# get ground truth
imgs = batch["imgs"]
bboxes = batch["bboxes"]
...
return loss, loss_items # see Reference-> Trainer for details on the expected format
# callback to upload model weights
def log_model(trainer):
last_weight_path = trainer.last
...
trainer = CustomTrainer(overrides={...})
trainer.add_callback("on_train_epoch_end", log_model) # Adds to existing callback
trainer.train()
YOLOv8原理解析
0.前言
YOLO(You Only Look Once)
是由 Joseph Redmon
和 Ali Farhadi
在华盛顿大学开发的流行的目标检测和图像分割模型。第一个版本的 YOLO
于 2015
年发布,并因其高速度和准确性而迅速赢得了广泛的关注。
YOLOv2
于 2016
年发布,通过引入批归一化、锚框和尺寸聚类等方法改进了原始模型。
YOLOv3
于 2018
年发布,通过使用更高效的骨干网络、添加特征金字塔并利用焦点损失进一步提高了模型的性能。
2020
年,YOLOv4
发布,引入了许多创新,例如使用 Mosaic
数据增强、新的无锚检测头和新的损失函数。
2021
年,Ultralytics
发布了 YOLOv5
,进一步提高了模型的性能,并添加了新功能,例如支持全景分割和物体跟踪。
2022
年,美团发布了 YOLOv6
,这是一款面向工业部署的目标检测模型。
2022
年,YOLOv7
发布,这是 AlexeyAB(YOLOv4作者)
和 WongKinYiu(YOLOR作者)
发布的一款实时目标检测器。
2023
年,Ultralytics
发布了 YOLOv8
,进一步提高了模型的性能,可以同时实现分类、检测和分割等任务。
1.YOLOv5
1.1 YOLOv5网络回顾
首先回顾一下 YOLOv5
的结构
-
Backbone:
New CSP-Darknet53
,网络第一层v5.0
版本前是FOCUS
结构,v5.0
版本以后是6×6
卷积。 -
Neck:
PAN
结构,v5.0
版本以前是SPP
结构,v5.0
版本以后是SPPF
结构。 -
Head:常见的耦合头,和经典的
YOLOv3
一样。 -
Loss
:
- 分类损失,采用的是
BCE loss
;只计算正样本的分类损失。 obj
损失,采用的是BCE loss
;这里的obj
指的是网络预测的目标边界框与GT Box
的CIoU
,计算的是所有样本的obj
损失。- 定位损失,采用的是
CIoU loss
;只计算正样本的定位损失。
- 分类损失,采用的是
1.2 YOLOv5网络结构图
2.YOLOv8
YOLOv8
项目地址:https://github.com/ultralytics/ultralytics
Ultralytics YOLOv8
是由 Ultralytics
开发的 YOLO
目标检测和图像分割模型的最新版本。YOLOv8
是一款先进的、最新的模型,基于之前 YOLO
版本的成功,并引入了新的特性和改进,进一步提高了性能和灵活性。
YOLOv8
的一个关键特性是其可扩展性。它被设计为一个框架,支持所有之前的 YOLO
版本,使得在不同版本之间切换并比较它们的性能变得非常容易。这使得 YOLOv8
成为那些想要利用最新的 YOLO
技术同时仍能够使用现有 YOLO
模型的用户的理想选择。
除了可扩展性之外,YOLOv8
还包括许多其他创新,使其成为一种适用于广泛目标检测和图像分割任务的吸引人选择。这些包括一个新的骨干网络、一个新的无锚点检测头和一个新的损失函数。此外,YOLOv8
高效并且可以在多种硬件平台上运行,从 CPU
到 GPU
。
总的来说,YOLOv8
是一款强大、灵活的目标检测和图像分割工具,它提供了最新的 SOTA
技术以及使用和比较所有之前 YOLO
版本的能力,是一个兼具优点的选择。
ultralytics
并没有直接将开源库命名为 YOLOv8
,而是直接使用 ultralytics
这个词,原因是 ultralytics
将这个库定位为算法框架,而非某一个特定算法,一个主要特点是可扩展性。其希望这个库不仅仅能够用于 YOLO
系列模型,而是能够支持非 YOLO
模型以及分类分割姿态估计等各类任务。
YOLOv8
主要有如下的优点:
- 用户友好的
API
- 可以同时实现分类、检测、分割和姿态估计任务
- 速度更快、准确率更高
- 全新的结构
- 新的损失函数
Anchor free
模型 | YOLOv5 (300epoch) | Params(M) | FLOPs@640(B) | YOLOv8 (500epoch) | 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 |
m | 45.4 | 21.2 | 49 | 50.2 | 25.9 | 78.9 |
l | 49.0 | 46.5 | 109.1 | 52.9 | 43.7 | 165.2 |
x | 50.7 | 86.7 | 205.7 | 53.9 | 68.2 | 257.8 |
表格中的数据来源于COCO数据集,但是在其它数据集上并不一定是v8优于v5。
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 YOLOv8整体结构图
图片来源https://github.com/open-mmlab/mmyolo/blob/dev/configs/yolov8(博主修正后)
2.3 YOLOv8yaml 文件与 YOLOv5yaml 文件对比
2.3.1 参数部分
左侧 yolov5s.yaml ,右侧 yolov8.yaml
因为YOLOv8
采用Anchor free
的范式,所以在 yaml
文件中移除了 anchor
的参数部分,并且YOLOv8
将多个不同版本的模型写到了一起,除此之外在深度因子和宽度因子后又新增了一个最大通道数的参数,我觉得这可能和YOLOv7
的模型理念有些相似,单凭宽度因子调整的通道数应该不是最优的,所以s\m\l
没有遵循同一套的缩放系数。
2.3.2 主干部分
左侧 yolov5s.yaml ,右侧 yolov8.yaml
主干部分改进主要有以下三点:
- 第一层卷积由原本的
6×6
卷积改为3×3
卷积 。 - 所有的
C3
模块改为C2f
模块 。 - 深度由
C3
模块3、6、9、3
改为C2f
的3、6、6、3
。
2.3.3 Neck部分
左侧 yolov5s.yaml ,右侧 yolov8.yaml
依然采用 PAN
结构,但是删除了降维用的 1×1
卷积。
2.3.4 Head部分
头部的改动是最大的,直接将原本的耦合头改成了解耦头,值得注意力的是,这个解耦头不再有之前的 objectness
分支,而是直接解耦成了两路,并且其回归分支使用了 Distribution Focal Loss
中提出的积分形式表示法。
2.4 正负样本分配策略
2.4.1 静态分配策略和动态分配策略有什么区别
在目标检测中,正负样本分配策略通常用于在训练期间为每个样本分配一个权重,以便模型更加关注困难的样本和重要的样本。动态分配策略和静态分配策略是两种常见的正负样本分配策略。
静态分配策略通常是在训练开始之前确定的,固定为一组预先定义的权重,这些权重不会在训练过程中改变。这种分配策略通常基于经验得出,可以根据数据集的特点进行调整,但是不够灵活,可能无法充分利用样本的信息,导致训练效果不佳。
相比之下,动态分配策略可以根据训练的进展和样本的特点动态地调整权重。在训练初期,模型可能会很难区分正负样本,因此应该更加关注那些容易被错分的样本。随着训练的进行,模型逐渐变得更加强大,可以更好地区分样本,因此应该逐渐减小困难样本的权重,同时增加易分样本的权重。动态分配策略可以根据训练损失或者其他指标来进行调整,可以更好地适应不同的数据集和模型。
总的来说,动态分配策略通常比静态分配策略更加灵活和高效,可以帮助模型更好地利用样本信息,提高训练效果。虽然动态分配策略可以在训练过程中根据模型的学习情况来适当调整正负样本的比例,但也存在一些缺点。
- 动态分配策略需要不断地监控模型在训练集上的表现,并根据表现调整正负样本的比例,因此会增加训练的计算量和时间成本。
- 动态分配策略可能会导致过拟合。因为动态分配策略会在训练过程中频繁地改变正负样本的比例,可能会导致模型过于关注某些样本,而忽略了其他样本,从而导致过拟合。
- 动态分配策略的结果难以复现。由于动态分配策略的调整是基于模型当前的学习情况,因此同样的训练过程可能会得到不同的正负样本比例,使得实验的结果难以复现和比较。
2.4.2 simOTA 策略
simOTA
是YOLOX
目标检测算法中的一种正负样本分配策略,全称为Similarity Overlap Threshold Assigner
。它的主要思想是通过计算anchor
和ground truth
之间的相似度,来动态地分配正负样本。
在传统的目标检测算法中,通常使用IoU
来度量anchor
和ground truth
之间的重叠程度,然后根据设定的阈值来判断是否将anchor
分配为正样本或负样本。但这种方法存在一个问题,就是对于一些尺寸较小的目标,由于它们的IoU
值通常比较低,因此很容易被分配为负样本,从而影响了检测性能。
simOTA
的核心思想是:根据目标的相似度来确定正负样本的阈值,进而实现样本的自适应分配。在训练时,首先将所有的目标两两配对,并计算它们的相似度得分。然后,通过对相似度分数进行统计,得到一个阈值范围,用于确定正负样本的分配阈值。具体而言,相似度得分越高,对应的IoU
阈值就越高,样本就越容易被视为正样本;相反,相似度得分越低,对应的IoU
阈值就越低,样本就更容易被视为负样本。
通过引入相似度信息,simOTA
可以更加灵活地处理目标间的差异性,从而提高模型的泛化能力和检测性能。实验结果表明,simOTA
可以显著提高YOLOX
模型在各种基准数据集上的表现,同时具有更好的鲁棒性和泛化性能。
相比传统的IoU
分配方法,simOTA
能够更好地处理尺寸较小的目标,从而提高了目标检测的性能。
2.4.4 Task-Aligned Assigner 策略
Task-Aligned Assigner
是一种TOOD
中正负样本分配策略的方法,其基本原理是在训练过程中动态地调整正负样本的分配比例,以更好地适应不同的任务和数据分布,YOLOv8
就是采用了这种策略。
具体而言,Task-Aligned Assigner
基于一个关键的假设,即在不同的任务和数据集中,正负样本的分布情况可能会有所不同,因此需要针对具体的任务和数据分布,调整正负样本的分配比例,从而达到更好的检测效果。
为了实现这一目的,Task-Aligned Assigner
提出了一个基于“任务自适应”的正负样本分配方法。具体而言,它使用一个任务感知的分配模块来估计不同任务下正负样本的分布情况,并基于这个分布情况动态调整正负样本的分配比例。
分配模块首先计算每个Anchor
和每个Ground Truth Box
之间的相似度,然后通过一个多任务学习框架,将相似度与正负样本标签的分布情况进行建模。最终,通过这个分布情况来动态地调整正负样本的分配比例,以更好地适应不同的任务和数据分布。
在具体实现中,Task-Aligned Assigner
采用了一种基于动态阈值的分配方法。具体而言,它在计算Anchor
与Ground Truth Box
之间的IoU
时,采用了一个基于任务自适应的动态阈值来判断正负样本的分配。通过这种方式,Task-Aligned Assigner
能够根据具体的任务和数据分布,自适应地调整正负样本的分配比例,从而获得更好的检测性能。
Task-Aligned Assigner
的公式如下:
t = s α × u β t=s^α\times u^β t=sα×uβ
使用上面公式来对每个实例计算 Anchor-level
的对齐程度:s
和 u
分别为分类得分和 IoU
值,α
和 β
为权重超参。t
可以同时控制分类得分和 IoU
的优化来实现 Task-Alignment
,可以引导网络动态的关注于高质量的Anchor
。采用一种简单的分配规则选择训练样本:对每个实例,选择 m
个具有最大 t
值的 Anchor
作为正样本,选择其余的 Anchor
作为负样本。然后,通过损失函数(针对分类与定位的对齐而设计的损失函数)进行训练。
YOLOv8
源码中各个参数取值如下:
2.4.5 损失计算
Loss
计算包括 2
个分支:分类和回归分支,没有了之前的 objectness
分支。
- 分类分支依然采用
BCE Loss
。 - 回归分支需要和
Distribution Focal Loss
中提出的积分形式表示法绑定,因此使用了Distribution Focal Loss
, 同时还使用了CIoU Loss
。
3
个 Loss
采用一定权重比例加权即可,默认的比例是7.5:0.5:1.5
。
详解YOLOv8网络结构/环境搭建/数据集获取/训练/推理/验证/导出/部署
本文内容很多先提供给大家本文的思维导图, 帮助大家快速定位到自己需要看的位置;
YOLOv8的简介
YOLO(You Only Look Once)系列算法因其高效、准确等特点而备受瞩目。由2023年Ultralytics公司发布了YOLO的最新版本YOLOv8是结合前几代YOLO的基础上的一个融合改进版。
本文YOLOv8网络结构/环境搭建/数据集获取/训练/推理/验证/导出/部署,从网络结构的讲解从模型的网络结构讲解到模型的部署都有详细介绍,同时在本专栏中还包括YOLOv8模型系列的改进包括个人提出的创新点,传统卷积、注意力机制、损失函数的修改教程,能够帮助你的论文获得创新点。
YOLOv8相对于Yolov5的核心改动
从YOLOv8的网络结构可以看出,其延用了YOLOv5的网络结构思想,包括基于CSP(紧凑和分离)的骨干网络(backbone)和Neck部分的设计,以及对于不同尺度模型的考虑。
改进总结:
- Backbone的改进:使用C2f模块代替C3模块,进一步轻量化,同时保持了CSP的思想,同时采用了SPPF模块。
- PAN-FPN的改进:保留了PAN的思想,但删除了上采样阶段中的卷积结构,同时将C3模块替换为C2f模块。
- Decoupled-Head的引入:采用了Decoupled-Head的思想,使得网络的训练和推理更加高效。
- Anchor-Free的思想:抛弃了Anchor-Base,采用了Anchor-Free的思想。
- 损失函数的改进:采用VFL Loss作为分类损失,同时使用DFL Loss和CIOU Loss作为回归损失。
- 样本匹配方式的改进:采用了Task-Aligned Assigner匹配方式。
这些改进使得YOLOv8在目标检测方面具有更高的精度和更快的速度,同时保持了轻量化的特点。
具体来说,YOLOv8的Backbone部分使用了C2f模块来替代了YOLOv5中的C3模块,实现了进一步的轻量化。同时,它也保留了YOLOv5等架构中使用的SPPF(空间金字塔池化)模块。
在PAN-FPN(路径聚合网络-特征金字塔网络)部分,虽然YOLOv8依旧采用了PAN的思想,但是在结构上,它删除了YOLOv5中PAN-FPN上采样阶段中的卷积结构,并将C3模块替换为了C2f模块。
这些改进使得YOLOv8在保持了YOLOv5网络结构的优点的同时,进行了更加精细的调整和优化,提高了模型在不同场景下的性能。
YOLOv8的网络结构
YOLOv8的网络结构主要由以下三个大部分组成:
- Backbone:它采用了一系列卷积和反卷积层来提取特征,同时也使用了残差连接和瓶颈结构来减小网络的大小和提高性能。该部分采用了C2f模块作为基本构成单元,与YOLOv5的C3模块相比,C2f模块具有更少的参数量和更优秀的特征提取能力。
- Neck:它采用了多尺度特征融合技术,将来自Backbone的不同阶段的特征图进行融合,以增强特征表示能力。具体来说,YOLOv8的Neck部分包括一个SPPF模块、一个PAA模块和两个PAN模块。
- Head:它负责最终的目标检测和分类任务,包括一个检测头和一个分类头。检测头包含一系列卷积层和反卷积层,用于生成检测结果;分类头则采用全局平均池化来对每个特征图进行分类。
下面我们来针对于YOLOv8的三个组成部分进行详细讲解。
Backbone
由最上面的YOLOv8网络结构图我们可以看出在其中的Backbone部分,由5个卷积模块和4个C2f模块和一个SPPF模块组成,
(其中浅蓝色为卷积模块,黄色为C2f模块深蓝色为SPPF模块 )
如果上图看的不够直观,我们来看一下YOLOv8的文件中的yaml文件,看一下它backbone部分的结构组成部分,会更加直观。
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 3, C2f, [128, True]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 6, C2f, [256, True]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f, [512, True]]
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
上面的部分就是YOLOv8的yaml文件的Backbone部分,可以看到其由5个Conv模块,四个C2f模块以及一个SPPF模块组成,下面我们来根据每个模块的组成来进行讲解。
卷积模块(Conv)
在其中卷积模块的结构主要为下图
在其中主要结构为一个2D的卷积一个BatchNorm2d和一个SiLU激活函数,整个卷积模块的作用为:
- 降采样:每个卷积模块中的卷积层都采用步长为2的卷积核进行降采样操作,以减小特征图的尺寸并增加通道数。
- 非线性表示:每个卷积层之后都添加了Batch Normalization(批标准化)层和ReLU激活函数,以增强模型的非线性表示能力。
在其中Batch Normalization(批标准化)是深度学习中常用的一种技术,用于加速神经网络的训练。Batch Normalization通过对每个小批量数据进行标准化,使得神经网络在训练过程中更加稳定,可以使用更高的学习率,并且减少了对初始化权重的依赖。Batch Normalization的基本思想是:对每个小批量数据进行标准化,使得每个特征的均值为0,方差为1,然后再通过一个可学习的缩放因子和平移因子来调整数据的分布,从而使得神经网络更容易训练。
C2f模块
在YOLOv8的网络结构中C2f模块算是YOLOv8的一个较大的改变,与YOLOv5的C3模块相比,C2f模块具有更少的参数量和更优秀的特征提取能力。下图为C2f的内部网络结构图。
在C2f模块中我们可以看到输入首先经过一个k=1,s=1,p=0,c=c_out的卷积模块进行了处理,然后经过一个split处理**(在这里split和后面的concat的组成其实就是所谓的残差模块处理)**经过数量为n的DarknetBottleneck模块处理以后将残差模块和主干模块的结果进行Concat拼接在经过一个卷积模块处理进行输出。
在其中提到的残差连接(residual connections)是一种用于构建深层神经网络的技术。它的核心思想是通过跳过层级连接来传递残差或误差。
在传统的神经网络中,信息流通过一层层的网络层,每一层都通过非线性激活函数进行转换和提取特征。然而,随着神经网络的加深,可能会出现"梯度消失"或"梯度爆炸"的问题,导致网络收敛困难或性能下降。
残差连接通过引入跨层级的连接,将输入的原始信息直接传递到后续层级,以解决梯度消失和爆炸问题。具体而言,它将网络的输入与中间层的输出相加,形成了一个"捷径"或"跳跃连接",从而允许梯度更容易地传播。
数学上,假设我们有一个输入x,通过多个网络层进行处理后得到预测值H(x)。那么残差连接的表达式为:
F(x) = H(x) + x
其中,F(x)为残差块的输出,H(x)为经过一系列网络层处理后得到的特征表示,x为输入直接连接到残差块中的跳跃连接。
通过残差连接,网络可以更容易地学习残差或误差,从而使网络更深层次的特征表达更准确。这对于训练深层神经网络非常有用,可以提高网络的性能和收敛速度。
在C2f模块中用到的DarknetBottleneck模块其中使用多个3x3卷积核进行卷积操作,提取特征信息。同时其具有add是否进行残差链接的选项。
其实整个C2f模块就是一个改良版本的Darknet
- 首先,使用1x1卷积核将输入通道数减少到原来的1/2,以减少计算量和内存消耗。
- 然后,使用多个3x3卷积核进行卷积操作,提取特征信息。
- 接着,使用残差链接,将输入直接加到输出中,从而形成了一条跨层连接。
- 接着,再次使用1x1卷积核恢复特征图的通道数。
SPPF模块
YOLOv8的SPPF模块相对于YOLOv5的SPPF模块并没有任何的改变。
这里有一篇文章讲解的是SPPF的前身SPP-NET大家有兴趣的可以了解以下。
目标检测算法(三)YOLOv5 SPPF层的前身SPP-NET精细分析和讲解并附源码地址
Neck
YOLOv8的Neck部分是该模型中的一个关键组件,它在特征提取和融合方面起着重要作用。Neck的详细描述如下:
Neck部分主要起到一个特征融合的操作, YOLOv8的Neck部分依然采用PAN-FPN的思想,下图的a,b,c为一个Neck部分的流程示意图。
整个Neck部分的步骤如下::将特征提取网络(Backbone)的输出P3,P4,P5输入进PAN-FPN网络结构,使得多个尺度的特征图进行融合;将P5经过上采样与P4进行融合得到F1,将F1经过C2f层和一次上采样与P3进行融合得到T1,将T1经过一次卷积层与F1经过融合得到F2,将F2经过一次C2f层得到T2,将T2经过一次卷积层与P5融合得到F3,将F3经过一次C2f层得到T3,最终得到T1、T2、T3就是整个Neck的产物;
上述过程可以描述为下图,我在图片上做了一些标准方便理解。
上述的过程可以在代码部分看到,我们同样看YOLOv8的yaml文件,能够更直观的看到这个步骤,大家可以看代码同时对应图片来进行分析:
head:
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 6], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f, [512]] # 12
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 4], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f, [256]] # 15 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 12], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f, [512]] # 18 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 9], 1, Concat, [1]] # cat head P5
- [-1, 3, C2f, [1024]] # 21 (P5/32-large)
Neck部分的整体功能的详细分析如下:
- Neck的作用:
Neck部分在YOLOv8模型中负责对来自Backbone的特征进行进一步处理和融合,以提高目标检测的准确性和鲁棒性。它通过引入不同的结构和技术,将多尺度的特征图进行融合,以便更好地捕捉不同尺度目标的信息。
- 特征金字塔网络(Feature Pyramid Network, FPN):
YOLOv8的Neck部分通常采用特征金字塔网络结构,用于处理来自Backbone的多尺度特征图。FPN通过在不同层级上建立特征金字塔,使得模型能够在不同尺度上进行目标检测。它通过上采样和下采样操作,将低层级的细节特征与高层级的语义特征进行融合,以获取更全面和丰富的特征表示。
- 特征融合(Feature Fusion):
Neck部分还包括特征融合的操作,用于将来自不同层级的特征进行融合。这种特征融合有助于提高模型对目标的检测准确性,尤其是对于不同尺度的目标。
- 上采样和下采样:
Neck部分通常会使用上采样和下采样操作,以调整特征图的尺度和分辨率。上采样操作可以将低分辨率的特征图放大到与高分辨率特征图相同的尺寸,以保留更多的细节信息。而下采样操作则可以将高分辨率的特征图降低尺寸,以减少计算量和内存消耗。
YOLOv8的Neck部分通过特征金字塔网络和特征融合等操作,有效地提取和融合多尺度的特征,从而提高了目标检测的性能和鲁棒性。这使得模型能够更好地适应不同尺度和大小的目标,并在复杂场景下取得更准确的检测结果。
PAN-FPN(具有特征金字塔网络的路径聚合网络)是一种用于计算机视觉中对象检测的神经网络架构。它将特征金字塔网络(FPN)与路径聚合网络(PAN)相结合,以提高目标检测的准确性和效率。
FPN 用于从不同比例的图像中提取特征,而 PAN 用于跨网络的不同层聚合这些特征。这允许网络检测不同大小和分辨率的对象,并处理具有多个对象的复杂场景。
Head
如果Backbone和Neck部分可以理解为准备工作,那么Head部分就是收获的部分,经过前面的准备工作我们得到了Neck部分的输出T1、T2、T3分别代表不同层级的特征图,Head部分就是对这三个特征图进行处理以产生模型的的输出结果的一个过程。
YOLOv8的Head部分我们先来看一下它的网络结构。
可以看到在YOLOv8的Head部分,体现了最核心的改动——>解耦头(Decoupled-Head),顾名思义就是将原先的一个检测头分解成两个部分。
在Head部分的三个解耦头分别对应着Neck部分的特征图输出T1、T2、T3。、
解耦头的工作流程是:
将网络得到的特征图T1,T2,T3分别输入解耦头头进行预测,检测头的结构如下图所示其中包含4个3×3卷积与2个1×1卷积,同时在检测头的回归分支中添加WIOU损失函数如图4所示,回归头部需要计算预测框与真实框之间的位置偏移量,然后将偏移量送入回归头部进行损失计算,然后输出一个四维向量,分别表示目标框的左上角坐标x、y和右下角坐标x、y。分类头部针对于每个Anchor Free提取的候选框对其进行RoI Pooling和卷积操作得到一个分类器输出张量每个位置上的值表示该候选框属于每个类别的概率,在最后通过极大值抑制方式筛选出最终的检测结果
我们再从YOLOv8的yaml文件来看Head部分的作用
可以看到检测头部分的输出为15,18,21分别对应着Neck部分的三个输出特征图。
到此YOLOv8的网络结构部分讲解就已经完成,下面我们来看如何利用YOLOv8进行训练操作。
环境搭建
大家如果没有搭建环境可以看我的另一篇博客,里面讲述了如何搭建pytorch环境。
Win11上Pytorch的安装并在Pycharm上调用PyTorch最新超详细过程并附详细的系统变量添加过程,可解决pycharm中pip不好使的问题
在我们配置好环境之后,在之后模型获取完成之后,我们可以进行配置的安装我们可以在命令行下输入如下命令进行环境的配置。
pip install -r requirements.txt
输入如上命令之后我们就可以看到命令行在安装模型所需的库了。
数据集获取
在我们开始训练之前,我们需要一份数据集,如何获取一个YOLOv8的数据集大家可以看我的另一篇博客从YOLO官方指定的数据集网站Roboflow下载数据模型训练
超详细教程YoloV8官方推荐免费数据集网站Roboflow一键导出Voc、COCO、Yolo、Csv等格式
我在上面随便下载了一个 数据集用它导出yolov8的数据集,以及自动给转换成txt的格式yaml文件也已经配置好了,我们直接用就可以。
模型获取
到这里假设你已经搭建好了环境和有了数据集,那么我们就可以进行模型的下载,因为yolov8目前还存在BUG并不稳定随时都有可能进行更新,所以不推荐大家通过其它的途径下载,最好通过下面的方式进行下载。
我们可以直接在终端命令下
(PS:这里需要注意的是我们需要在你总项目文件目录下输入这个命令,因为他会下载到当前目录下)
pip install ultralytics
如果大家去github上直接下载zip文件到本地可能会遇到报错如下,识别不了yolo命令,所以推荐大家用这种方式下载,
模型训练
我们来看一下主要的ultralytics目录结构,
我门打开cfg目录下的default.yaml文件可以配置模型的参数,
在其中和模型训练有关的参数及其解释如下:
参数名 | 输入类型 | 参数解释 | |
---|---|---|---|
0 | task | str | YOLO模型的任务选择,选择你是要进行检测、分类等操作 |
1 | mode | str | YOLO模式的选择,选择要进行训练、推理、输出、验证等操作 |
2 | model | str/optional | 模型的文件,可以是官方的预训练模型,也可以是训练自己模型的yaml文件 |
3 | data | str/optional | 模型的地址,可以是文件的地址,也可以是配置好地址的yaml文件 |
4 | epochs | int | 训练的轮次,将你的数据输入到模型里进行训练的次数 |
5 | patience | int | 早停机制,当你的模型精度没有改进了就提前停止训练 |
6 | batch | int | 我们输入的数据集会分解为多个子集,一次向模型里输入多少个子集 |
7 | imgsz | int/list | 输入的图片的大小,可以是整数就代表图片尺寸为int*int,或者list分别代表宽和高[w,h] |
8 | save | bool | 是否保存模型以及预测结果 |
9 | save_period | int | 在训练过程中多少次保存一次模型文件,就是生成的pt文件 |
10 | cache | bool | 参数cache 用于控制是否启用缓存机制。 |
11 | device | int/str/list/optional | GPU设备的选择:cuda device=0 or device=0,1,2,3 or device=cpu |
12 | workers | int | 工作的线程,Windows系统一定要设置为0否则很可能会引起线程报错 |
13 | name | str/optional | 模型保存的名字,结果会保存到’project/name’ 目录下 |
14 | exist_ok | bool | 如果模型存在的时候是否进行覆盖操作 |
15 | prepetrained | bool | 参数pretrained用于控制是否使用预训练模型。 |
16 | optimizer | str | 优化器的选择choices=[SGD, Adam, Adamax, AdamW, NAdam, RAdam, RMSProp, auto] |
17 | verbose | bool | 用于控制在执行过程中是否输出详细的信息和日志。 |
18 | seed | int | 随机数种子,模型中涉及到随机的时候,根据随机数种子进行生成 |
19 | deterministic | bool | 用于控制是否启用确定性模式,在确定性模式下,算法的执行将变得可重复,即相同的输入将产生相同的输出 |
20 | single_cls | bool | 是否是单标签训练 |
21 | rect | bool | 当 rect 设置为 True 时,表示启用矩形训练或验证。矩形训练或验证是一种数据处理技术,其中在训练或验证过程中,输入数据会被调整为具有相同宽高比的矩形形状。 |
22 | cos_lr | bool | 控制是否使用余弦学习率调度器 |
23 | close_mosaic | int | 控制在最后几个 epochs 中是否禁用马赛克数据增强 |
24 | resume | bool | 用于从先前的训练检查点(checkpoint)中恢复模型的训练。 |
25 | amp | bool | 用于控制是否进行自动混合精度 |
26 | fraction | float | 用于指定训练数据集的一部分进行训练的比例。默认值为 1.0 |
27 | profile | bool | 用于控制是否在训练过程中启用 ONNX 和 TensorRT 的性能分析 |
28 | freeze | int/list/optinal | 用于指定在训练过程中冻结前 n 层或指定层索引的列表,以防止它们的权重更新。这对于迁移学习或特定层的微调很有用。 |
训练的三种方式
方式一
我们可以通过命令直接进行训练在其中指定参数,但是这样的方式,我们每个参数都要在其中打出来。命令如下:
yolo task=detect mode=train model=yolov8n.pt data=data.yaml batch=16 epochs=100 imgsz=640 workers=0 device=0
需要注意的是如果你是Windows系统的电脑其中的Workers最好设置成0否则容易报线程的错误。
方式二(推荐)
通过指定cfg直接进行训练,我们配置好ultralytics/cfg/default.yaml这个文件之后,可以直接执行这个文件进行训练,这样就不用在命令行输入其它的参数了。
yolo cfg=ultralytics/cfg/default.yaml
方式三
我们可以通过创建py文件来进行训练,这样的好处就是不用在终端上打命令,这也能省去一些工作量,我们在根目录下创建一个名字为run.py的文件,在其中输入代码
from ultralytics import YOLO
model = YOLO("权重的地址")
data = "文件的地址"
model.train(data=data, epochs=100, batch=16)
无论通过上述的哪一种方式在控制台输出如下图片的内容就代表着开始训练成功了!
模型验证/测试
参数名 | 类型 | 参数讲解 | |
---|---|---|---|
1 | val | bool | 用于控制是否在训练过程中进行验证/测试。 |
2 | split | str | 用于指定用于验证/测试的数据集划分。可以选择 ‘val’、‘test’ 或 ‘train’ 中的一个作为验证/测试数据集 |
3 | save_json | bool | 用于控制是否将结果保存为 JSON 文件 |
4 | save_hybird | bool | 用于控制是否保存标签和附加预测结果的混合版本 |
5 | conf | float/optional | 用于设置检测时的目标置信度阈值 |
6 | iou | float | 用于设置非极大值抑制(NMS)的交并比(IoU)阈值。 |
7 | max_det | int | 用于设置每张图像的最大检测数。 |
8 | half | bool | 用于控制是否使用半精度(FP16)进行推断。 |
9 | dnn | bool | ,用于控制是否使用 OpenCV DNN 进行 ONNX 推断。 |
10 | plots | bool | 用于控制在训练/验证过程中是否保存绘图结果。 |
验证我们划分的验证集/测试集的情况,也就是评估我们训练出来的best.pt模型好与坏
yolo task=detect mode=val model=best.pt data=data.yaml device=0
模型推理
我们训练好自己的模型之后,都会生成一个模型文件,保存在你设置的目录下,当我们再次想要实验该模型的效果之后就可以调用该模型进行推理了,我们也可以用官方的预训练权重来进行推理。
推理的方式和训练一样我们这里就选一种来进行举例其它的两种方式都是一样的操作只是需要改一下其中的一些参数即可:
参数讲解
参数名 | 类型 | 参数讲解 | |
---|---|---|---|
0 | source | str/optinal | 用于指定图像或视频的目录 |
1 | show | bool | 用于控制是否在可能的情况下显示结果 |
2 | save_txt | bool | 用于控制是否将结果保存为 .txt 文件 |
3 | save_conf | bool | 用于控制是否在保存结果时包含置信度分数 |
4 | save_crop | bool | 用于控制是否将带有结果的裁剪图像保存下来 |
5 | show_labels | bool | 用于控制在绘图结果中是否显示目标标签 |
6 | show_conf | bool | 用于控制在绘图结果中是否显示目标置信度分数 |
7 | vid_stride | int/optional | 用于设置视频的帧率步长 |
8 | stream_buffer | bool | 用于控制是否缓冲所有流式帧(True)或返回最新的帧(False) |
9 | line_width | int/list[int]/optional | 用于设置边界框的线宽度,如果缺失则自动设置 |
10 | visualize | bool | 用于控制是否可视化模型的特征 |
11 | augment | bool | 用于控制是否对预测源应用图像增强 |
12 | agnostic_nms | bool | 用于控制是否使用无关类别的非极大值抑制(NMS) |
13 | classes | int/list[int]/optional | 用于按类别筛选结果 |
14 | retina_masks | bool | 用于控制是否使用高分辨率分割掩码 |
15 | boxes | bool | 用于控制是否在分割预测中显示边界框。 |
yolo task=detect mode=predict model=best.pt source=images device=0
这里需要需要注意的是我们用模型进行推理的时候可以选择照片也可以选择一个视频的格式都可以。支持的视频格式有
- MP4(.mp4):这是一种常见的视频文件格式,通常具有较高的压缩率和良好的视频质量
- AVI(.avi):这是一种较旧但仍广泛使用的视频文件格式。它通常具有较大的文件大小
- MOV(.mov):这是一种常见的视频文件格式,通常与苹果设备和QuickTime播放器相关
- MKV(.mkv):这是一种开放的多媒体容器格式,可以容纳多个视频、音频和字幕轨道
- FLV(.flv):这是一种用于在线视频传输的流式视频文件格式
模型输出
当我们进行部署的时候可以进行文件导出,然后在进行部署。
YOLOv8支持的输出格式有如下
- ONNX(Open Neural Network Exchange):ONNX 是一个开放的深度学习模型表示和转换的标准。它允许在不同的深度学习框架之间共享模型,并支持跨平台部署。导出为 ONNX 格式的模型可以在支持 ONNX 的推理引擎中进行部署和推理。
- TensorFlow SavedModel:TensorFlow SavedModel 是 TensorFlow 框架的标准模型保存格式。它包含了模型的网络结构和参数,可以方便地在 TensorFlow 的推理环境中加载和使用。
- PyTorch JIT(Just-In-Time):PyTorch JIT 是 PyTorch 的即时编译器,可以将 PyTorch 模型导出为优化的 Torch 脚本或 Torch 脚本模型。这种格式可以在没有 PyTorch 环境的情况下进行推理,并且具有更高的性能。
- Caffe Model:Caffe 是一个流行的深度学习框架,它使用自己的模型表示格式。导出为 Caffe 模型的文件可以在 Caffe 框架中进行部署和推理。
- TFLite(TensorFlow Lite):TFLite 是 TensorFlow 的移动和嵌入式设备推理框架,支持在资源受限的设备上进行高效推理。模型可以导出为 TFLite 格式,以便在移动设备或嵌入式系统中进行部署。
- Core ML(Core Machine Learning):Core ML 是苹果的机器学习框架,用于在 iOS 和 macOS 上进行推理。模型可以导出为 Core ML 格式,以便在苹果设备上进行部署。
这些格式都提供了不同的优势和适用场景。选择合适的导出格式应该考虑到目标平台和部署环境的要求,以及所使用的深度学习框架的支持情况。
模型输出的参数有如下
参数名 | 类型 | 参数解释 | |
---|---|---|---|
0 | format | str | 导出模型的格式 |
1 | keras | bool | 表示是否使用Keras |
2 | optimize | bool | 用于在导出TorchScript模型时进行优化,以便在移动设备上获得更好的性能 |
3 | int8 | bool | 用于在导出CoreML或TensorFlow模型时进行INT8量化 |
4 | dynamic | bool | 用于在导出CoreML或TensorFlow模型时进行INT8量化 |
5 | simplify | bool | 用于在导出ONNX模型时进行模型简化 |
6 | opset | int/optional | 用于指定导出ONNX模型时的opset版本 |
7 | workspace | int | 用于指定TensorRT模型的工作空间大小,以GB为单位 |
8 | nms | bool | 用于在导出CoreML模型时添加非极大值抑制(NMS) |
命令行命令如下:
yolo task=detect mode=export model=best.pt format=onnx
到此为止本文的讲解就结束了,希望对大家对于YOLOv8模型理解有帮助,希望本文能够帮助到大家。