YOLO算法理解
本文是yolov1-yolov3(https://blog.csdn.net/weixin_52862386/article/details/139563416)的延续,所以有一些内容在上篇文章已经叙述,下面更多的叙述它们的改进和一些新的思想。yolo的损失函数特别多,主要的更新都是框的loss上做改进,所以后面再写一篇关于loss function的。
YOLOv4
YOLOv4是在YOLOv3的基础上提出了一些改进trick,实现了map的提升同时仍具备65FPS的推理速度。作者提出了Bag of freebies和Bag of specials俩个概念。其中Bag of freebies是指仅改变训练策略或者提高训练cost从而提高模型精度的方法,例如数据增强;Bag of specials是指仅插入一些模块或者后处理后能提高模型精度,且对推理速度影响比较小的方法,如加入attention机制、托大感受野等方法。然后作者通过一些Bag of freebies和Bag of specials的方法实现了精度的提升。
Bag of specials
Cross-stage partial connections(CSP)
CSP一种降低计算量,同时精度没有太大影响的方法。它的思想是将特征按channel进行切分,将一半的特征按原路径进行计算,剩下一般直接和结果进行concat,从而实现计算量减少的目的。
上图展示了CSPNet应用于Resnet的过程,假设原本Resnet的输出为
d
1
d_1
d1,Base layer的输出为
d
2
d_2
d2,那么在CSPResnet,Part1和Part2的维度都为
d
2
/
2
d_2/2
d2/2,Partial Transition的输入为
c
o
n
c
a
t
(
[
d
/
2
,
d
/
2
]
)
concat([d/2,d/2])
concat([d/2,d/2]),最终输出为
d
1
d_1
d1。简单而言就是部分特征不计算。实现方式可以参考下面的代码(代码来自yolov5)
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):
"""Initializes a standard bottleneck layer with optional shortcut and group convolution, supporting channel
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
def forward(self, x):
"""Processes input through two convolutions, optionally adds shortcut if channel dimensions match; input is a
tensor.
"""
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
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):
"""Initializes CSP bottleneck with optional shortcuts; args: ch_in, ch_out, number of repeats, shortcut bool,
groups, expansion.
"""
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
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)))
def forward(self, x):
"""Performs forward pass by applying layers, activation, and concatenation on input x, returning feature-
enhanced output.
"""
y1 = self.cv3(self.m(self.cv1(x)))
y2 = self.cv2(x)
return self.cv4(self.act(self.bn(torch.cat((y1, y2), 1))))
Spatial Pyramid Pooling(SPP)
Spatial Pyramid Pooling叫做空间金字塔池化,它的作用是将任意大小的特征图Pooling到固定的大小,从而使得输入全连接层时具有固定的维度。
上图为SPP的结构图,它将卷积图的输出使用adptive-pooling的方式,分别pooling到
4
×
4
4\times 4
4×4、
2
×
2
2\times 2
2×2和
1
×
1
1\times 1
1×1的大小,然后将它们打平后concatenate,得到
4
×
4
+
2
×
2
+
1
×
1
=
21
4\times4+2\times2+1\times1=21
4×4+2×2+1×1=21的固定长度。通过这种方式就可以实现任意大小的特征图flatten至固定长度。
PAN path-aggregation block
PAN网络是在FPN的基础增加了从Bottom-up的模块,提高模型对不同尺度特征的学习能力。FPN的网络结构如下图所示
FPN是在对图像做特征提取后,再进行一次上采样的操作。再看PANnet的结构图
可以发现PAN网络增加了Bottom-up的模块,即对特征图进行下采样再融合。原始的PAN特征图
P
k
P_k
Pk和
N
k
−
1
N_{k-1}
Nk−1的上采样是通过addition融合的,在yolov4中采用了concatenation的方式。具体改变如下图所示,这样就可以比较好地理解yolov4地PAN模块
SAM
SAM模快来自CBAM: Convolutional Block Attention Module,它是指空间注意力机制模块。在CBAM中,SAM模的实现在特征图的channel维度上使用maxpooling和avg-pooling,然后concatenate,再通过卷积至一维,在经过激活函数,得到空间的注意力分数(
1
×
H
×
W
1\times H\times W
1×H×W)。下图是SAM的实现方法
在yolov4中,对SAM模块进行了修改,直接通过一个卷积层和激活函数,获得注意力分数。这里的不同就是它没有进行池化,算出的注意力是每一个位置的,即(
C
×
H
×
W
C\times H\times W
C×H×W)。
Mish activation
mish激活函数的表达式为
m
i
s
h
(
x
)
=
x
tanh
(
ln
(
1
+
e
x
)
)
mish(x) = x\tanh(\ln(1+e^x))
mish(x)=xtanh(ln(1+ex))它的函数图像如下
可以发现函数图像是连续的但不单调,但它相较于Relu函数,处理x在负半轴上的问题以及图像不连续的问题,但是它先减后增,理论上不太合理,但它在负半轴上的值较小,所以这个问题就不太明显。
Multi-input weighted residual connections(MiWRC)
Bag of freebies
文中提出了许多的方法,这里主要讲几个比较重要的,数据增强中的Mosaic,处理标签不平衡的Class label smoothing,训练中batch的优化方法 CmBN和Dynamic mini-batch size
Mosaic方法
Mosaic的数据增强方法是将四个图片拼接在一起,具体见下面的代码
def load_mosaic(self, index):
"""Loads a 4-image mosaic for YOLOv5, combining 1 selected and 3 random images, with labels and segments."""
labels4, segments4 = [], []
s = self.img_size
yc, xc = (int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border) # mosaic center x, y
indices = [index] + random.choices(self.indices, k=3) # 3 additional image indices
random.shuffle(indices)
for i, index in enumerate(indices):
# Load image
img, _, (h, w) = self.load_image(index)
# place img in img4
if i == 0: # top left
img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8) # base image with 4 tiles
x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc # xmin, ymin, xmax, ymax (large image)
x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h # xmin, ymin, xmax, ymax (small image)
elif i == 1: # top right
x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
elif i == 2: # bottom left
x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, w, min(y2a - y1a, h)
elif i == 3: # bottom right
x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)
img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b] # img4[ymin:ymax, xmin:xmax]
padw = x1a - x1b
padh = y1a - y1b
# Labels
labels, segments = self.labels[index].copy(), self.segments[index].copy()
if labels.size:
labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padw, padh) # normalized xywh to pixel xyxy format
segments = [xyn2xy(x, w, h, padw, padh) for x in segments]
labels4.append(labels)
segments4.extend(segments)
# Concat/clip labels
labels4 = np.concatenate(labels4, 0)
for x in (labels4[:, 1:], *segments4):
np.clip(x, 0, 2 * s, out=x) # clip when using random_perspective()
# img4, labels4 = replicate(img4, labels4) # replicate
# Augment
img4, labels4, segments4 = copy_paste(img4, labels4, segments4, p=self.hyp["copy_paste"])
img4, labels4 = random_perspective(
img4,
labels4,
segments4,
degrees=self.hyp["degrees"],
translate=self.hyp["translate"],
scale=self.hyp["scale"],
shear=self.hyp["shear"],
perspective=self.hyp["perspective"],
border=self.mosaic_border,
) # border to remove
return img4, labels4
Mosaic是先选定一张图片,然后再随机选择其他三张图片,从而凑齐四张图片。然后选择Mosaic的中心点,这里是从
(
i
m
a
g
e
_
s
i
z
e
/
/
2
,
1.5
∗
(
i
m
a
g
e
_
s
i
z
e
/
/
2
)
)
(image\_size//2,1.5*(image\_size//2))
(image_size//2,1.5∗(image_size//2))中任意选择一个整数,从而获取
x
,
y
x,y
x,y。这里Mosaic是给定一个
(
2
∗
i
m
a
g
e
_
s
i
z
e
,
2
∗
i
m
a
g
e
_
s
i
z
e
)
(2*image\_size,2*image\_size)
(2∗image_size,2∗image_size)的画布,所以中心点是在画布的中心区域进行选取的。然后根据中心点将画布分为四块,分别填入四张图片。最后将画布中的中心部分
[
−
i
m
a
g
e
_
s
i
z
e
/
/
2
:
i
m
a
g
e
_
s
i
z
e
/
/
2
,
−
i
m
a
g
e
_
s
i
z
e
/
/
2
:
i
m
a
g
e
_
s
i
z
e
/
/
2
]
[-image\_size//2:image\_size//2,-image\_size//2:image\_size//2]
[−image_size//2:image_size//2,−image_size//2:image_size//2]提取出来作为增强后的图片,这一步的实现是在random_perspective中。
这个方法是可以丰富图片的信息,但是这种方法要根据任务场景去使用,因为它是会丢失一些重要信息的,对于一些上下文信息需求比较大的场景,这种方式明显会丢失很多重要的信息。
Class label smooth
class label smooth是一种soft label的方式,它使分类标签不再是(0,1),而是通过如下公式进行转换
l
a
b
e
l
=
l
a
b
e
l
∗
(
1
−
ϵ
)
+
ϵ
/
K
label = label*(1-\epsilon)+\epsilon/K
label=label∗(1−ϵ)+ϵ/K这里
ϵ
\epsilon
ϵ是一个比较小的常数如0.01,
K
K
K是分类的总数。
当做二分类任务时,标签0和1分别变成0.005和0.995。这种方式可以防止模型过拟合,提高模型的泛化能力。也可以当标签中存在错标的情况时,降低噪音数据带来的影响。
CmBN和Dynamic mini-batch size
CmBN是对Mini Batch进行标准化的一种新方法,具体见下图
这里假设一个batch有4个mini-batch,在BN层中,它对每个mini-batch都计算权重、均值和方差,然后进行标准化,完成一个batch之后更新权重。在CBN则是每次都计算和更新权重,跨越四个mini batch计算均值和方差,然后对数据进行标准化。而CmBN则是在一个batch中,每个mini-batch都计算权重,均值和方差,然后进行标准化,但是这里第
k
(
k
>
1
)
k(k>1)
k(k>1)会将
(
0
−
k
)
(0-k)
(0−k)的数据一起计算方差和均值,在进行标准化,所以也称之为跨mini batch标准化。
Dynamic mini-batch size是指训练中使用动态mini-batch大小,原文中的解释使用随机训练维度时随着像素变小自动增加mini-batch的大小。个人理解,这是指multi-scale训练是,当选择像素比较小时,可以采用较大的mini-batch。
YOLOv5
yolov5相较于yolov4的变化不大,不过yolov5的工程化做的非常好,许多方法的实现更精简高效,提供了许多模块的实现,使用者可以根据需求去修改config文件来调用相应的模块
autoanchor
autoanchor的方法是anchor生成自动化了,首先它会将数据中的标签和给定的anchor进行匹配,然后在给定阈值在匹配的anchor超过98%,则认为默认anchor是匹配的。否则的话,根据labels使用kmeans进行聚类,生成9个prior-anchor。这里可以分析一下它的anchor匹配方式,yolov5的实现方式如下
def check_anchors(dataset, model, thr=4.0, imgsz=640):
"""Evaluates anchor fit to dataset and adjusts if necessary, supporting customizable threshold and image size."""
m = model.module.model[-1] if hasattr(model, "module") else model.model[-1] # Detect()
shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float() # wh
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))
s = f"\n{PREFIX}{aat:.2f} anchors/target, {bpr:.3f} Best Possible Recall (BPR). "
if bpr > 0.98: # threshold to recompute
LOGGER.info(f"{s}Current anchors are a good fit to dataset ✅")
else:
LOGGER.info(f"{s}Anchors are a poor fit to dataset ⚠️, attempting to improve...")
na = m.anchors.numel() // 2 # number of anchors
anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False)
new_bpr = metric(anchors)[0]
if new_bpr > bpr: # replace anchors
anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors)
m.anchors[:] = anchors.clone().view_as(m.anchors)
check_anchor_order(m) # must be in pixel-space (not grid-space)
m.anchors /= stride
s = f"{PREFIX}Done ✅ (optional: update model *.yaml to use these anchors in the future)"
else:
s = f"{PREFIX}Done ⚠️ (original anchors better than new anchors, proceeding with original anchors)"
LOGGER.info(s)
这里仔细分析一下实现的代码
shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float() # wh
这里是对输入的label进行resize,将标签中的长宽转化为resize之后的长宽。然后用均匀分布将长宽进行稍微的缩放。
然后这里着重看metric函数,可以看到首先计算了 r r r
r = wh[:, None] / k[None]
这里是通过计标签中的宽高和prior anchor的宽高相除,求出真实宽和真实高与先验框宽和先验框高的比例。
x = torch.min(r, 1 / r).min(2)[0]
这里是由于真实宽和先验框存在 d t > d p d_t>d_p dt>dp或者 d t < d p d_t<d_p dt<dp的情况,由于无法确定哪个比较大,所以通过去倒数的方式,取出最小值,那么就可以保证 r r r值是小比大的情况。然后这里对长和宽的最小比率中选择它们之间的最小值。假设真实宽高为 d t , h t d_t,h_t dt,ht,先验框的宽高为 d p , h p d_p,h_p dp,hp,比率 r r r为 r = min ( min ( d t d p , d p d t ) , min ( h t h p , h p h t ) ) r=\min(\min(\frac{d_t}{d_p},\frac{d_p}{d_t}),\min(\frac{h_t}{h_p},\frac{h_p}{h_t})) r=min(min(dpdt,dtdp),min(hpht,hthp))
best = x.max(1)[0] # best_x
这里是由于先验框有九个,每个都与真实框进行对比,所以只需找出9个框中最匹配的那个。
bpr = (best > 1 / thr).float().mean()
这里是将它们匹配比率大于 1 / t h r 1/thr 1/thr的认为是匹配上了,然后统计匹配的比率。这里默认thr为4,即 r > 0.25 r>0.25 r>0.25则认为匹配,这个约束特别小了。
if bpr > 0.98: # threshold to recompute
LOGGER.info(f"{s}Current anchors are a good fit to dataset ✅")
这里是指当匹配率超过0.98,则认为默认的先验框是合理的,否则就进行kmeans聚类。
Focus层
yolov5的foucus层,是实现了类似于yolov2中的Fine-Grained Features操作,具体代码如下,这里也没有什么比较新颖的地方。
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):
"""Initializes Focus module to concentrate width-height info into channel space with configurable convolution
parameters.
"""
super().__init__()
self.conv = Conv(c1 * 4, c2, k, s, p, g, act=act)
# self.contract = Contract(gain=2)
def forward(self, x):
"""Processes input through Focus mechanism, reshaping (b,c,w,h) to (b,4c,w/2,h/2) then applies convolution."""
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))
yolov5严格而言是一个工程任务,它将yolov1-4的工作做了整合,使其具备很高的适用性,方便人们的使用,所以这也是它在发布后备受关注的主要原因,同时应该也是因为这样所以没有发表论文。
YOLOv6
yolov6是美团发布的,他在22年发布了yolov6(YOLOv6: A Single-Stage Object Detection Framework for Industrial Applications)
yolov6的主要创新点为
- 针对不同的工业场景提出了一系列,主要是引入了多分支模块。
- 在分类和回归任务上采用了自蒸馏的策略,并动态调整来自teacher model和标签的知识,帮助student model更高效地学习知识。
- 验证了一些标签分配、损失函数和数据增强策略的有效性
- 使用了ReOptimizer和channel wise distillation来改变检测中的量化策略
Network
yolov6的网络结构如下,其中Backbone有俩个,一个是EfficientRep用于小模型,另一个是CSPStackRep用于大模型。在Neck模块采用改进的PAN模块Rep-PAN。在Head模块采用了Efficient decoupled head,同时采用achor-free的anchor point-based的范式。
Backbone
Rep(Re-param)
Rep是(RepVGG: Making VGG-style ConvNets Great Again)中提出的一种新方法,它可以降低模型的推理速度,即在训练时用multi-branch,而在推理时转换成single-branch。下图是RepVGG的结构图
通过上图可以发现,RepVGG借鉴了ResNet的方式,在训练时引入了残差连接,但是在推理时仍保持
3
×
3
3\times 3
3×3卷积堆叠的形式。接下来了解一些具体如何实现。
上图展示了转换的过程。对于
1
×
1
1\times 1
1×1的卷积可以看成
3
×
3
3\times 3
3×3的卷积,其中仅有中心点的值有权重,其他部分都为0,这样就可以实现
1
×
1
1\times1
1×1卷积转换为
3
×
3
3\times3
3×3卷积。对于
x
x
x转换为
x
x
x,可以看成
3
×
3
3\times3
3×3卷积,但中心点的权重为1,且卷积的stride为1。这样就可以等效实现残差连接的效果。最后将三个卷积合并成一个
3
×
3
3\times3
3×3的卷积。同时,需要考虑BN层的问题,因为实际推理中的实现形式为
M
(
2
)
=
b
n
(
M
(
1
)
∗
W
(
3
)
,
μ
(
3
)
,
σ
(
3
)
,
γ
(
3
)
,
β
(
3
)
)
+
b
n
(
M
(
1
)
∗
W
(
1
)
,
μ
(
1
)
,
σ
(
1
)
,
γ
(
1
)
,
β
(
1
)
)
+
b
n
(
M
(
1
)
,
μ
(
0
)
,
σ
(
0
)
,
γ
(
0
,
β
(
0
)
)
\begin{align*}M^{(2)} =& bn(M^{(1)}*W^{(3)},\mu^{(3)},\sigma^{(3)},\gamma^{(3)},\beta^{(3)})\\ &+bn(M^{(1)}*W^{(1)},\mu^{(1)},\sigma^{(1)},\gamma^{(1)},\beta^{(1)})\\ &+bn(M^{(1)},\mu^{(0)},\sigma^{(0)},\gamma^{(0},\beta^{(0)})\end{align*}
M(2)=bn(M(1)∗W(3),μ(3),σ(3),γ(3),β(3))+bn(M(1)∗W(1),μ(1),σ(1),γ(1),β(1))+bn(M(1),μ(0),σ(0),γ(0,β(0))
其中,
μ
,
σ
,
γ
,
β
\mu,\sigma,\gamma,\beta
μ,σ,γ,β都是BN层的参数,
W
(
3
)
W^{(3)}
W(3)指
3
×
3
3\times 3
3×3卷积的权重,
W
(
1
)
W^{(1)}
W(1)指
3
×
3
3\times 3
3×3卷积的权重,
M
(
i
)
M^{(i)}
M(i)则是第
i
i
i层的输入。
由于BN层的公式为
b
n
(
x
,
μ
,
σ
,
γ
,
β
)
=
x
−
μ
σ
×
γ
+
β
bn(x,\mu,\sigma,\gamma,\beta)=\frac{x-\mu}{\sigma}\times\gamma+\beta
bn(x,μ,σ,γ,β)=σx−μ×γ+β
当
x
x
x为
M
∗
W
(
3
)
M*W^{(3)}
M∗W(3)时,有
b
n
(
M
∗
W
(
i
)
,
μ
,
σ
,
γ
,
β
)
=
M
∗
W
(
3
)
−
μ
σ
×
γ
+
β
=
γ
∗
W
(
3
)
σ
×
M
+
(
β
−
μ
σ
)
=
M
W
′
+
β
′
\begin{align*}bn(M*W^{(i)},\mu,\sigma,\gamma,\beta)=&\frac{M*W^{(3)}-\mu}{\sigma}\times\gamma+\beta\\ =&\frac{\gamma*W^{(3)}}{\sigma}\times M+\left(\beta -\frac{\mu}{\sigma}\right)\\ =&MW'+\beta'\end{align*}
bn(M∗W(i),μ,σ,γ,β)===σM∗W(3)−μ×γ+βσγ∗W(3)×M+(β−σμ)MW′+β′通过这种方法可以实现BN层和卷积层的参数融合。
EfficientRep和CSPStackRep
EfficientRep的结构见下图,其中stride为2时代表下采样2倍,只有2个branch,没有残差连接的branch。RepBLock则是由N个RepConv组成,这里stride为1。
CSPStackRep则是CSPNet的基础上进行改进,引入RepConv,将CSPnet block改进为CSPStackRep block。理解了Rep的过程之后,这些改进操作其实就是用RepConv改进之前的Block
Head
Head部分采用的是PAN结构,但是这里也是将其中的CSPblock换成RepBlock。
Efficient decoupled head
yolov5的头部是通过权重共享的方式,而yolov6是学习了yolox的方式将它们解耦,分解为分类头和回归头。论文中提到了它用了hybrid-channel的方式,将三层的卷积降至一层(如下图所示),但是它说了头部的宽度由neck和backbone决定,个人认为,这里应该类似于yolov5中定义了大中小三种头。
Anchor point-based
anchor point-based是一种anchor free的方式,它是基于一个点然后预测距离anchor四个边框点的距离,如中心点为 ( x , y ) (x,y) (x,y),左上点为 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),右下点为 ( x 2 , y 2 ) (x_2,y_2) (x2,y2),然后预测 ( x − x 1 , y − y 1 , x 2 − x , y 2 − y 1 ) (x-x1,y-y1,x2-x,y2-y1) (x−x1,y−y1,x2−x,y2−y1)这四个距离。对于point的选取方式,在yolov6的实现方式取每个grid的中心点作为point。
Label Assignment
作者首先使用了yolox中的SimOTA的方法,但是发现其效果表现得并不好,所以作者又使用了TOOD(TOOD方法在另一篇文章中也介绍了)中TAL的方法。SimOTA后面在yolox中再介绍,这里介绍TAL(task alignment learning)方法,TAL提出了新的度量标准
t
t
t,其中
t
=
s
α
+
μ
β
t= s^{\alpha}+\mu^{\beta}
t=sα+μβ其中
s
s
s为预测分类分数,
μ
\mu
μ为预测的iou大小,同时
α
\alpha
α和
β
\beta
β是超参数。
将分数按
t
t
t的值进行排序,将
t
t
t值的前
k
k
k个作为
p
o
s
pos
pos样本,其他的作为
n
e
g
neg
neg样本。