目标检测基础
-
目标检测框的显示
要在图像中画出一个检测框,只需要知道左上角以及右下角两个点的坐标即可,但在利用matplotlib进行显示的时候,需要将其转换为(左上x,左上y,宽,高)。 -
锚框(Anchor)
在生成多个检测框时,有一个重要的概念,锚框,也就是检测框。在这里着重记录一下代码里是生成锚框的。
首先我们看图,最外面蓝色的框为图像大小,宽为w,高为h。里面红色的小框表示需要画出的锚框,框为 w a w_a wa,高为 h a h_a ha。
我们首先将图像的宽和高归一化长度为1,那么锚框的宽就为 s s 1 = w a w ss1 = \frac{w_a}{w} ss1=wwa,高为 s s 2 = h a h ss2 = \frac{h_a}{h} ss2=hha。锚框归一化后的宽高比为 r = s s 1 s s 2 r=\frac{ss1}{ss2} r=ss2ss1,面积为 s 2 = s s 1 × s s 2 = w a h a w h s^2=ss1\times ss2=\frac{w_ah_a}{wh} s2=ss1×ss2=whwaha,显然,归一化后的锚框面积显示的是真正的锚框面积与真正图像面积的比例。因此,联立上面的四式,可以解得 w a = w s r w_a=ws\sqrt{r} wa=wsr, h a = h s / r h_a=hs/\sqrt{r} ha=hs/r。
所以,在已知图像的宽高 w , h w,h w,h时,要想生成多个锚框,只需要改变 s , r s,r s,r的取值即可。
假设我们给定了一组 s 1 , ⋯ , s n s_1,\cdots,s_n s1,⋯,sn以及 r 1 , ⋯ , r m r_1,\cdots,r_m r1,⋯,rm,它们之间有 n m nm nm种组合方法,如果我们以每一个像素点为中心,都生成 n m nm nm个框的话,那一共需要生成 w h n m whnm whnm个框。这么多框,计算复杂度过高,因此我们只保留 n m nm nm种组合种包含 s 1 s_1 s1和 r 1 r_1 r1的,这样组合只剩下 ( n + m − 1 ) (n+m-1) (n+m−1)种,那么生成的总框数为 w h ( n + m − 1 ) wh(n+m-1) wh(n+m−1)个。
def MultiBoxPrior(feature_map, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5]):
"""
# 按照「9.4.1. 生成多个锚框」所讲的实现, anchor表示成(xmin, ymin, xmax, ymax).
https://zh.d2l.ai/chapter_computer-vision/anchor.html
Args:
feature_map: torch tensor, Shape: [N, C, H, W].
sizes: List of sizes (0~1) of generated MultiBoxPriores.
ratios: List of aspect ratios (non-negative) of generated MultiBoxPriores.
Returns:
anchors of shape (1, num_anchors, 4). 由于batch里每个都一样, 所以第一维为1
"""
pairs = [] # pair of (size, sqrt(ration))
# 生成n + m -1个框
for r in ratios:
pairs.append([sizes[0], math.sqrt(r)])
for s in sizes[1:]:
pairs.append([s, math.sqrt(ratios[0])])
pairs = np.array(pairs)
# 生成相对于坐标中心点的框(x,y,x,y)
ss1 = pairs[:, 0] * pairs[:, 1] # size * sqrt(ration)
ss2 = pairs[:, 0] / pairs[:, 1] # size / sqrt(ration)
base_anchors = np.stack([-ss1, -ss2, ss1, ss2], axis=1) / 2
#将坐标点和anchor组合起来生成hw(n+m-1)个框输出
h, w = feature_map.shape[-2:]
shifts_x = np.arange(0, w) / w
shifts_y = np.arange(0, h) / h
shift_x, shift_y = np.meshgrid(shifts_x, shifts_y)
shift_x = shift_x.reshape(-1)
shift_y = shift_y.reshape(-1)
shifts = np.stack((shift_x, shift_y, shift_x, shift_y), axis=1)
anchors = shifts.reshape((-1, 1, 4)) + base_anchors.reshape((1, -1, 4))
return torch.tensor(anchors, dtype=torch.float32).view(1, -1, 4)
-
框都画出来了,如何衡量锚框与真是边界框的相似度呢?这里采用的是生成的锚框与真实框(ground-truth)的交并比(Intersection over Union,IoU)。
我们通常将Jaccard系数称为交并比(Intersection over Union,IoU),即两个边界框相交面积与相并面积之比,如图所示。交并比的取值范围在0和1之间:0表示两个边界框无重合像素,1表示两个边界框相等。 -
标注训练集的锚框
在训练集中,我们将每个锚框视为一个训练样本。为了训练目标检测模型,我们需要为每个锚框标注两类标签:一是锚框所含目标的类别,简称类别;二是真实边界框相对锚框的偏移量,简称偏移量(offset)。在目标检测时,我们首先生成多个锚框,然后为每个锚框预测类别以及偏移量,接着根据预测的偏移量调整锚框位置从而得到预测边界框,最后筛选需要输出的预测边界框。
我们知道,在目标检测的训练集中,每个图像已标注了真实边界框的位置以及所含目标的类别。在生成锚框之后,我们主要依据与锚框相似的真实边界框的位置和类别信息为锚框标注。那么,该如何为锚框分配与其相似的真实边界框呢?
这里课件种讲解得十分详细,简要记录一下。首先找到将各个锚框与真实边界框的交并比列成矩阵,先找到与每个真实边界框对应的交并比最大的锚框,分别赋予对应的标签。找完所有与真实边界框最相似的锚框后,剩下的锚框取交并比最大的值,如果此值大于设定阈值,则这个锚框对应交并比最大的真实边界框,否则为背景。
锚框的类别有了,接下来需要标注偏移量,设锚框
A
A
A及其被分配的真实边界框
B
B
B的中心坐标分别为
(
x
a
,
y
a
)
(x_a,y_a)
(xa,ya)和
(
x
b
,
y
b
)
(x_b,y_b)
(xb,yb),
A
A
A和
B
B
B的宽分别为
w
a
w_a
wa和
w
b
w_b
wb,高分别为
h
a
h_a
ha和
h
b
h_b
hb,一个常用的技巧是将
A
A
A的偏移量标注为
(
x
b
−
x
a
w
a
−
μ
x
σ
x
,
y
b
−
y
a
h
a
−
μ
y
σ
y
,
log
w
b
w
a
−
μ
w
σ
w
,
log
h
b
h
a
−
μ
h
σ
h
)
,
\left( \frac{ \frac{x_b - x_a}{w_a} - \mu_x }{\sigma_x}, \frac{ \frac{y_b - y_a}{h_a} - \mu_y }{\sigma_y}, \frac{ \log \frac{w_b}{w_a} - \mu_w }{\sigma_w}, \frac{ \log \frac{h_b}{h_a} - \mu_h }{\sigma_h}\right),
(σxwaxb−xa−μx,σyhayb−ya−μy,σwlogwawb−μw,σhloghahb−μh),
其中常数的默认值为 μ x = μ y = μ w = μ h = 0 , σ x = σ y = 0.1 , σ w = σ h = 0.2 \mu_x = \mu_y = \mu_w = \mu_h = 0, \sigma_x=\sigma_y=0.1, \sigma_w=\sigma_h=0.2 μx=μy=μw=μh=0,σx=σy=0.1,σw=σh=0.2。如果一个锚框没有被分配真实边界框,我们只需将该锚框的类别设为背景。类别为背景的锚框通常被称为负类锚框,其余则被称为正类锚框。
- 最后记一下非极大值抑制
在模型预测阶段,我们先为图像生成多个锚框,并为这些锚框一一预测类别和偏移量。随后,我们根据锚框及其预测偏移量得到预测边界框。当锚框数量较多时,同一个目标上可能会输出较多相似的预测边界框。为了使结果更加简洁,我们可以移除相似的预测边界框。常用的方法叫作非极大值抑制(non-maximum suppression,NMS)。
我们来描述一下非极大值抑制的工作原理。对于一个预测边界框
B
B
B,模型会计算各个类别的预测概率。设其中最大的预测概率为
p
p
p,该概率所对应的类别即
B
B
B的预测类别。我们也将
p
p
p称为预测边界框的置信度。在同一图像上,我们将预测类别非背景的预测边界框按置信度从高到低排序,得到列表
L
L
L。从
L
L
L中选取置信度最高的预测边界框
B
1
B_1
B1作为基准,将所有与
B
1
B_1
B1的交并比大于某阈值的非基准预测边界框从
L
L
L中移除。这里的阈值是预先设定的超参数。此时,
L
L
L保留了置信度最高的预测边界框并移除了与其相似的其他预测边界框。 接下来,从
L
L
L中选取置信度第二高的预测边界框
B
2
B_2
B2作为基准,将所有与
B
2
B_2
B2的交并比大于某阈值的非基准预测边界框从
L
L
L中移除。重复这一过程,直到
L
L
L中所有的预测边界框都曾作为基准。此时
L
L
L中任意一对预测边界框的交并比都小于阈值。最终,输出列表
L
L
L中的所有预测边界框。
之所以设定阈值,是因为图像中可能存在两个相同的检测目标。
图像风格迁移
样式迁移,就是要合成图像保留内容图像的内容以及样式图像的样式。
这里主要记录一下三个损失函数。
第一个是内容损失。与线性回归中的损失函数类似,内容损失通过平方误差函数衡量合成图像与内容图像在内容特征上的差异。
第二个是样式损失。样式损失也一样通过平方误差函数衡量合成图像与样式图像在样式上的差异。为了表达样式层输出的样式,我们先通过extract_features函数计算样式层的输出。假设该输出的样本数为1,通道数为 c c c,高和宽分别为 h h h和 w w w,我们可以把输出变换成 c c c行 h w hw hw列的矩阵 X X X。矩阵 X X X可以看作是 c c c由个长度为 h w hw hw的向量 x 1 , ⋯ , x 2 x_1,\cdots,x_2 x1,⋯,x2组成的。其中向量 x i x_i xi代表了通道 i i i上的样式特征。这些向量的格拉姆矩阵(Gram matrix) X X T ∈ R c × c XX^T\in\mathbb{R}^{c\times c} XXT∈Rc×c中 i i i行 j j j列的元素 x i j x_{ij} xij即向量 x i x_i xi与 x j x_j xj的内积,它表达了通道 i i i和通道 j j j上样式特征的相关性。我们用这样的格拉姆矩阵表达样式层输出的样式。需要注意的是,当 h w hw hw的值较大时,格拉姆矩阵中的元素容易出现较大的值。此外,格拉姆矩阵的高和宽皆为通道数 c c c。为了让样式损失不受这些值的大小影响,下面定义的gram函数将格拉姆矩阵除以了矩阵中元素的个数,即 c h w chw chw。
第三个是总变差损失。有时候,我们学到的合成图像里面有大量高频噪点,即有特别亮或者特别暗的颗粒像素。一种常用的降噪方法是总变差降噪(total variation denoising)。假设
x
i
j
x_{ij}
xij表示坐标为(i,j)的像素值,降低总变差损失
∑
i
,
j
∣
x
i
,
j
−
x
i
+
1
,
j
∣
+
∣
x
i
,
j
−
x
i
,
j
+
1
∣
\sum_{i,j} \left|x_{i,j} - x_{i+1,j}\right| + \left|x_{i,j} - x_{i,j+1}\right|
i,j∑∣xi,j−xi+1,j∣+∣xi,j−xi,j+1∣
能够尽可能使邻近的像素值相似。