目标检测
预备知识
基本原理:
\qquad 很多时候图像里有多个我们感兴趣的目标,我们不仅想知道它们的类别,还想得到它们在图像中的具体位置。在计算机视觉里,我们将这类任务称为目标检测(object detection)或物体检测。
\qquad 目标检测在多个领域中被广泛使用。例如,在无人驾驶里,我们需要通过识别拍摄到的视频图像里的车辆、行人、道路和障碍的位置来规划行进线路。机器人也常通过该任务来检测感兴趣的目标。安防领域则需要检测异常目标,如歹徒或者炸弹。
边界框:
在目标检测里,我们通常使用边界框(bounding box)来描述目标位置。边界框是一个矩形框,可以由矩形左上角的 x x x和 y y y轴坐标与右下角的 x x x和 y y y轴坐标确定。我们根据下面的图的坐标信息来定义图中狗和猫的边界框。图中的坐标原点在图像的左上角,原点往右和往下分别为 x x x轴和 y y y轴的正方向。
锚框:
目标检测算法通常会在输入图像中采样大量的区域,然后判断这些区域中是否包含我们感兴趣的目标,并调整区域边缘从而更准确地预测目标的真实边界框( ground truth bounding box)。
法:它以每个像素为中心生成多个大小和宽高比(aspect ratio)不同的边界框,这些边界框被称为锚框(anchor box)。
交并比:
Jaccard 系数(Jaccard index)可以衡量两个集合的相似度。给定集合 A 和 B ,它们的 Jaccard 系数即二者交集大小除以二者并集大小:
J
(
A
,
B
)
=
∣
A
∩
B
∣
∣
A
∪
B
∣
(1)
J(A,B)=\frac{|\mathcal{A}\cap\mathcal{B}|}{|\mathcal{A}\cup\mathcal{B}|}\tag{1}
J(A,B)=∣A∪B∣∣A∩B∣(1)
非极大值抑制:
目标检测窗口格子划分较小时,同一目标可能被检测到多次,此时可以采用非极大值抑制(Non-max suppression)的方法解决。首先比较所有格子输出结果中检测到物体的概率,由大到小排序,保留概率最大的预测框,然后依次与比它小的预测框求交并比,若交并比很大,则把存在物体概率较低的那个预测框抑制掉。
目标检测实现过程
首先导入相关包和库:
%matplotlib inline
from PIL import Image
import numpy as np
import math
import torch
import sys sys.path.append("..")
import d2lzh_pytorch as d2l
假设输入图像高为 h h h,宽为 w w w。我们分别以图像的每个像素为中心生成不同形状的锚框。设大小为 s ∈ ( 0 , 1 ] s\in(0,1] s∈(0,1]且宽高比为 r > 0 r >0 r>0,那么锚框的宽和高将分别 为 w s r ws\sqrt{r} wsr和 h s r hs\sqrt{r} hsr。当中心位置给定时,已知宽和高的锚框是确定的。
下面我们分别设定好一组大小s1,
⋯
\cdots
⋯,sn和一组宽高比r1,
⋯
\cdots
⋯,rm。如果以每个像素为中心时使用所有的大小与宽高比的组合,输入图像将一共得到 whn*m个锚框。虽然这些锚框可能覆盖了所有的真实边界框,但计算复杂度容易过高。因此,我们通常只对包含s1或r1的大小与宽高比的组合感兴趣,即
(
s
1
,
r
1
)
,
(
s
1
,
r
2
)
,
⋯
,
(
s
1
,
r
m
)
,
(
s
2
,
r
1
)
,
(
s
3
,
r
1
)
,
⋯
,
(
s
n
,
r
1
)
(2)
(s_{1},r_{1}),(s_{1},r_{2}),\cdots,(s_{1},r_{m}),(s_{2},r_{1}),(s_{3},r_{1}),\cdots,(s_{n},r_{1})\tag{2}
(s1,r1),(s1,r2),⋯,(s1,rm),(s2,r1),(s3,r1),⋯,(sn,r1)(2)
生成锚框的方法通过下述的MultiBoxPrior函数实现:
d2l.set_figsize()
img = Image.open('img/catdog.jpg')
w, h = img.size
print("w = %d, h = %d" % (w, h)) # w = 728, h = 561
def MultiBoxPrior(
feature_map, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
pairs = [] # pair of (size, sqrt(ration))
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)
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
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_x和shifts_y是将宽高进行归一化处理然后用meshgrid函数生成一个向量矩阵, 最后reshape成一行向量。
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)
X = torch.Tensor(1, 3, h, w) # 构造输入数据
Y = MultiBoxPrior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shape # torch.Size([1, 2042040, 4])
返回锚框变量y 的形状为(1,锚框个数 4)。将锚框变量 y 的形状变为(图像高,图像宽,以相同像素为中心的锚框个数,4)后,我们就可以通过指定像素位置来获取所有以该像素为中心的锚框了。下面的例子里我们访问以(250 250)为中心的第一个锚框。它有 4 个元素,分别是锚框左上角的 x x x和 y y y轴坐标和右下角的 x x x和 y y y轴坐标,其中 x x x和 y y y轴的坐标值分别已除以图像的宽和高,因此值域均为0和1之间。
boxes = Y.reshape((h, w, 5, 4))
boxes[250, 250, 0, :]# * torch.tensor([w, h, w, h],
dtype=torch.float32)
输出如下:
tensor([-0.0316, 0.0706, 0.7184, 0.8206])
为了描绘图像中以某个像素为中心的所有锚框,我们先定义show_bboxes函 数以便在图像上画出多个边界框:
def show_bboxes(axes, bboxes, labels=None, colors=None):
def _make_list(obj, default_values=None):
if obj is None:
obj = default_values
elif not isinstance(obj, (list, tuple)):
obj = [obj]
return obj
labels = _make_list(labels)
colors = _make_list(colors, ['b', 'g', 'r', 'm', 'c'])
for i, bbox in enumerate(bboxes):
color = colors[i % len(colors)]
rect = d2l.bbox_to_rect(bbox.detach().cpu().numpy(),color)#画出边界框
axes.add_patch(rect)
if labels and len(labels) > i:
text_color = 'k' if color == 'w' else 'w'
axes.text(
rect.xy[0], rect.xy[1], labels[i],
va='center', ha='center', fontsize=6, color=text_color,
bbox=dict(facecolor=color, lw=0)
)
变量boxes中xx和yy轴的坐标值分别已除以图像的宽和高。在 绘图时,我们需要恢复锚框的原始坐标值,并因此定义了变量bbox_scale,我们可以画出图像中以(250, 250)为中心的所有锚框了。可以看到,大小为 0.75且宽高比为1的锚框较好地覆盖了图像中的狗:
d2l.set_figsize()
fig = d2l.plt.imshow(img)
bbox_scale = torch.tensor([[x, y, w, h]], dtype=torch.float32)
show_bboxes(fig.axes, boxes[250, 250, :, :] * bbox_scale,['s=0.75, r=1', 's=0.75, r=2', 's=0.55,r=0.5', 's=0.5, r=1', 's=0.25, r=1'])
输出如下:
YOLO算法
YOLO算法简述
YOLO作为一种非常具有代表性的一步算法,也继承了其主要特点,即无需像RCNN一样生成候选框,可以直接预测得到结果。这使得YOLO具有两步法所不具备的快速性。由于YOLO所具有的优异的性能,自从其被提出以来,研究员们开发出了大量的变种,并应用到了各类领域,其主要分支见图2-3。随着版本的迭代YOLO的性能也逐渐增强。
一步法的基本思想是将目标检测问题转化成回归和分类问题。需要将图像分成若干个网格单元(grid cell),再预测出每个网格中潜在的目标的大小和位置,最后通过非极大值抑制等方法对包围框进行筛选,网格划分及目标预测,
问题1:有一个框里有多个,有个多个框里有一个,怎么办?
多个框里有一个目标,取目标中心点所在框;一个框里有多个,暂不能解决。
问题2:多类目标怎么办?
使用独热编码扩展类别数。
问题3:小目标怎么办?
使用单独网络拟合小目标,即设置多个bounding box。
YOLO网络结构
YOLO的结构如图6所示并对应图7的概略图,网络结构包含24 个卷积层和 2 个全连接层;其中前 20 个卷积层用来做预训练,后面 4 个是随机初始化的卷积层,和 2 个全连接层。
YOLO的输入:YOLO v1在PASCAL VOC数据集上进行训练,因此输入图片为
448
×
448
×
3
448\times448\times3
448×448×3。实际应用中若需要使用其它尺寸,则需要resize或切割成要求尺寸。
YOLO模型处理:将图片分割为
S
2
S^{2}
S2 个grid(
S
=
7
S = 7
S=7),每个网格单元(grid cell)的大小都是相等的,如图8
;每个网格单元都可以检测是否包含目标;YOLO v1中,每个网格单元只能检测出一种物体(大小可以不同)。
YOLO网络输出是一个
7
×
7
×
30
7\times7\times30
7×7×30的张量。对应
7
×
7
7\times7
7×7个网格单元。每个网格单元对应2个包围框(bounding box),预测不同大小和宽高比,对应检测不同目标。每个包围框包含5个分量,分别是物体的中心位置
(
x
,
y
)
(x, y)
(x,y)和它的高(
h
h
h ) 、宽($ w$ ) ,以及这次预测的置信度($ c$ ) 。图9中,每个框代表1个预测的包围框,框的粗细代表不同的置信度,框越粗代表置信度越高。
例中,图片被分成了49个单元格,每个单元格预测2个包围框,因此图10中共包含98个包围框。
包围框与置信度
YOLO的包围框
设共有 S 2 S^{2} S2个网格单元,每个网格单元的包围框个数为𝐵,分类器可以识别出𝐶种不同的物体,那么所有整个ground truth的长度为 S × S × ( B × 5 + C ) S\times S\times(B \times 5 + C) S×S×(B×5+C)。YOLO v1中,这个数量是30;YOLO v2和以后版本使用了自聚类的anchor box为包围框, v2版本为 B = 5 B = 5 B=5, YOLO v3中 B = 9 B = 9 B=9。
归一化
四个关于位置的值,分别是$ x, y, h 和 和 和w$,均为整数,实际预测中收敛慢。因此,YOLO对数据进行了归一化,使其限定在在0-1的范围内。
置信度
置信度计算公式如下:
C
=
P
r
(
o
b
j
)
∗
I
O
U
t
r
u
t
h
p
r
e
d
(3)
C=Pr(obj)*IOU^{pred}_{truth}\tag{3}
C=Pr(obj)∗IOUtruthpred(3)
其中:
P
r
(
o
b
j
)
Pr(obj)
Pr(obj)是一个网格单元存在物体的概率;
I
O
U
t
r
u
t
h
p
r
e
d
IOU_{truth}^{pred}
IOUtruthpred是预测的包围框和真实物体位置包围框的交集面积与并集面积之比。
训练值(ground truth)
P
r
(
o
b
j
)
Pr(obj)
Pr(obj)的ground truth如图11所示,三个目标中点对应格子为1,其它为0。
YOLO的训练数据与网络输出结构
YOLO的训练数据与网络输出结构如图12。
损失函数
YOLO的损失函数如下:
L
=
λ
c
o
o
r
d
∑
i
=
0
S
2
∑
j
=
0
B
1
i
j
o
b
j
[
(
x
i
−
x
^
i
)
2
+
(
y
i
−
y
^
i
)
2
]
+
λ
c
o
o
r
d
∑
i
=
0
S
2
∑
j
=
0
B
1
i
j
o
b
j
[
(
w
i
−
w
^
i
)
2
+
(
y
i
−
y
^
i
)
2
]
+
∑
i
=
0
S
2
∑
j
=
0
B
1
i
j
o
b
j
(
C
i
−
C
^
i
)
2
+
λ
n
o
o
b
j
∑
i
=
0
S
2
∑
j
=
0
B
1
i
j
n
o
o
b
j
(
C
i
−
C
^
i
)
2
+
∑
i
=
0
S
2
1
i
o
b
j
∑
c
∈
c
l
a
s
s
e
s
(
p
i
(
c
)
−
p
^
i
(
c
)
)
2
(4)
\begin{array}{l} L=\lambda_{coord}\sum\limits_{i=0}^{S^{2}}\sum\limits_{j=0}^{B}1_{ij}^{obj}[(x_{i}-\hat{x}_{i})^2+(y_{i}-\hat{y}_{i})^{2}]\\ \qquad+\lambda_{coord}\sum\limits_{i=0}^{S^{2}}\sum\limits_{j=0}^{B}1_{ij}^{obj}[(\sqrt{w_{i}}-\sqrt{\hat{w}_{i}})^2+(\sqrt{y_{i}}-\sqrt{\hat{y}_{i}})^{2}]\\ \qquad+\sum\limits_{i=0}^{S^{2}}\sum\limits_{j=0}^{B}1_{ij}^{obj}(C_{i}-\hat{C}_{i})^2\\ \qquad+\lambda_{noobj}\sum\limits_{i=0}^{S^{2}}\sum\limits_{j=0}^{B}1_{ij}^{noobj}(C_{i}-\hat{C}_{i})^2\\ \qquad+\sum\limits_{i=0}^{S^{2}}1_{i}^{obj}\sum\limits_{c\in classes}(p_{i}(c)-\hat{p}_{i}(c))^{2}\\ \end{array} \tag{4}
L=λcoordi=0∑S2j=0∑B1ijobj[(xi−x^i)2+(yi−y^i)2]+λcoordi=0∑S2j=0∑B1ijobj[(wi−w^i)2+(yi−y^i)2]+i=0∑S2j=0∑B1ijobj(Ci−C^i)2+λnoobji=0∑S2j=0∑B1ijnoobj(Ci−C^i)2+i=0∑S21iobjc∈classes∑(pi(c)−p^i(c))2(4)
损失本别为边界框中心点的损失、边界框宽高损失、置信度损失(包括物体)、置信度损失(不包括物体)、类别损失。
训练与NMS
预测中一个物体可能被多个边界框包围,而实际物体只对应一个边界框,此时使用NMS解决。
NMS 核心思想是:选择得分最高的作为输出,与该输出重叠的去掉,不断重复这一过程直到所有备选处理完。
NMS算法要点
1
◯
\textcircled{1}
1◯首先丢弃概率小于预定 IOU 阈值(例如 0.5 )的所有边界框;对于剩余的边界框;
2
◯
\textcircled{2}
2◯选择具有最高概率的边界框并将其作为输出预测;
3
◯
\textcircled{3}
3◯计算 “作为输出预测的边界框”,与其他边界框的相关联 IoU值;舍去 IoU 大于阈值的边界框;其实就是舍弃与“作为输出预测的边界框” 很相近的框框;
4
◯
\textcircled{4}
4◯重复步骤 2 ,直到所有边界框都被视为输出预测或被舍弃;
预训练与训练
∙
\bullet
∙YOLO 先使用 ImageNet 数据集对前 20 层卷积网络进行预训练,然后使用完整的网络,在 PASCAL VOC 数据集上进行对象识别和定位的训练和预测;
∙
\bullet
∙训练中采用了 drop out 和数据增强来防止过拟合;
∙
\bullet
∙YOLO 的最后一层采用线性激活函数 因为要回归 bb 位置 )),其它层都是采用 Leaky ReLU 激活函数:
Φ
(
x
)
=
{
x
,
i
f
x
>
0
0.1
x
,
o
t
h
e
r
w
i
s
e
(5)
\begin{array}{l} \varPhi(x)= \begin{cases} x,& if\enspace x >0\\ 0.1x,& otherwise \end{cases} \end{array} \tag{5}
Φ(x)={x,0.1x,ifx>0otherwise(5)
语义分割
与其它问题区别
语义分割:找到同一画面中的不同类型目标区域;
实例分割:同一类型目标要分出来具体实例(谁是谁);
目标检测:标出来外包围矩形;
发展历程
深度学习图像分割算法发展历程如图13
基本思想
语义分割的目标为对图中每一个像素进行分类,得到对应标签。其基本思想为滑动窗口。为加快计算,可以采用FCN,其结构如图14
1
×
1
1\times 1
1×1卷积的操作如图15所示
上池化(反池化)操作如图16所示:
FCN具体实现
FCN卷积部分如下图所示:
FCN中第6、7、8层都是通过 1 × 1 1\times1 1×1卷积得到的,第6层的输出是 4096 × 7 × 7 4096\times7\times7 4096×7×7,第7层的输出是 4096 × 7 × 7 , 4096\times7\times7, 4096×7×7,第8层的输出是 1000 × 7 × 7 1000\times7\times7 1000×7×7即1000个大小是 7 × 7 7\times7 7×7的特征图(称为heatmap)。
反卷积部分如图18所示:
其中跳级结构如图19所示:
评价指标
常见语义分割评价指标包括:
Pixel Accuracy(Global Acc):
∑
i
n
i
i
∑
i
t
i
\displaystyle{\frac{\sum_{i}n_{ii}}{\sum_{i}t_{i}}}
∑iti∑inii
mean Accuracy:
1
n
c
l
s
∑
i
n
i
i
t
i
\displaystyle{\frac{1}{n_{cls}}\sum_{i}\frac{n_{ii}}{t_{i}}}
ncls1i∑tinii
mean IoU:
1
n
c
l
s
∑
i
n
i
i
t
i
+
∑
j
n
i
j
−
n
i
i
\displaystyle{\frac{1}{n_{cls}}\sum_{i}\frac{n_{ii}}{t_{i}+\sum_{j}n_{ij}-n_{ii}}}
ncls1i∑ti+∑jnij−niinii
其中:
n
i
j
n_{ij}
nij为leibie1
i
i
i被预测成类别
j
j
j的像素个数,
n
c
l
s
n_{cls}
ncls为目标类别个数(包含背景),
t
i
=
∑
j
n
i
j
t_{i}=\sum_{j}n_{ij}
ti=∑jnij为目标类别
i
i
i的总像素个数。