CornerNet是一个比较综合的目标检测论文,如果要详细看的话要补充好多的知识,所以像我们基础比较薄弱的看起来比较吃力,但是一点点慢慢来,总有一些事情是要坚持下去的,比如说…写博客。
【Paper】CornerNet:Detecting Objects as Paired Keypoints
【Code】https://github.com/princeton-vl/CornerNet
在开始分享CornerNet之前,我们先来明确几个问题(有一定目标检测基础的可以跳过这一趴)。
【目前的目标检测算法主要分为两大类:One-stage和Two-stage】
One-stage目标检测算法: 不需要region proposal阶段,直接产生物体的类别概率和位置坐标值,经过单次检测即可直接得到最终的检测结果,因此有着更快的检测速度,比较典型的算法如YOLO,SSD,YOLOv2,YOLOv3,RetinaNet等。
Two-stage目标检测算法: two-stage检测算法将检测问题划分为两个阶段,首先产生候选区域(region proposals),然后对候选区域分类(一般还需要对位置精修),这一类的典型代表是R-CNN, Fast R-CNN, Faster R-CNN,Mask R-CNN等等。它们识别错误率低,但速度较慢,不能满足实时检测场景。
One-stage | Two-stage | |
---|---|---|
代表算法 | YOLO,SSD,YOLOv2,YOLOv3, RetinaNet,RefineNet | R-CNN, Fast R-CNN, Faster R-CNN, Mask R-CNN,FCN , RFCN |
算法特点 | 速度更快,但精度一般 | 精度很高,但速度一般 |
【如何让目标检测算法性能更优?】
上个问题我们说到目标检测已经有很多算法了,我们怎能让这些目标检测算法更优呢?要不然在One-stage算法上提高精度,要不然在Two-stage上提高速度。 但在优化算法的过程中,我们要首先找到造成One-stage和Two-stage算法上不足的原因。在大神的Focal Loss这篇论文中,大佬认为造成两种目标检测算法性能不同的原因是正负样本的类别不平衡;这种不平衡的原因主要有两方面:一是在画Anchor时造成的不平衡,二是在计算损失的时候负样本占据了有利地位;Focal Loss解决的是第二方面,而CornerNet解决的是第一方面。
【目标检测的小伙伴——人体姿态估计】
人体姿态估计也是深度学习中比较重要的一个分支,即给定一幅图像或者一段视频,人体姿态估计就是去重建人的关节和肢干。从估计人体的数量来看,分为单人姿态估计和多人姿态估计;从数据集纬度来看,分为2D人体姿态估计和3D人体姿态估计。其中,在2D人体多人姿态估计中,有两种思路:
(1)自底向上Bottom-up:先找到所有的人体关键点,再分组关联成不同人;
(2)自顶向下Top-down:先检测人的bounding box,再用单人姿态估计方法检测每个人;
【本文重点!!!】
如果你对上述几个问题有所了解,那么CornerNet的思路也就很好理解了。简单来说,CornerNet是一种One-stage的目标检测器,它改变了提出Anchor的方式,提出了一种新的池化方法,基于优秀的网络框架和损失函数,得到了好的目标检测效果,即可以总结为下面这个公式:
CornerNet = Corner idea + Corner Pooling + Hourglass + Focal Loss
CornerNet的网络结果如上图,backbone用的是Hourglass,后面连接两个预测模块——Top-left Corners和Bottom-right Corners,在每个预测模块里面包含着本文提出的Corner Pooling和对应的损失函数。下面的内容是CornerNet的具体分享,满满干货有选择摄入啦~
【01 Corner idea的提出】
CornerNet中anchor的提出是受了Newell的人体姿态检测的影响。在Newell的Associative Embedding: End-to-End Learning for Joint Detection and Grouping这篇论文里面,他提出了人体姿态检测的一站式的bottom-up方法, 可以用于多人姿态检测和目标分割。在这篇文章之前,多人姿态检测方法大多用的是bottom-up的方式,先检测关节点,然后分组,目标分割是检查相关像素然后分组,多目标追踪是检测个体然后分成不同轨迹,所以这些方法一般都是Two-stage的流程。而Newell认为可以将两个阶段用嵌入向量(Embedding vector)连接起来,将检测和分组合并到一个过程,构建完全端到端网络的过程。
CornerNet基于这种想法(如上图),将画anchor的方式转变为左上角(top-left角点,下简称TL点)和右下角(bottom-right角点,下简称BR点),而之前目标检测算法是用anchor的中心点和宽、高来描述anchor。作者利用 预测两个角点heatmaps 的方法来确定anchor:在每个角点heatmap有C个通道(请注意:这里的C通道是C个类,不含背景类,大小是
H
∗
W
H*W
H∗W),并且是关于
C
n
C_n
Cn类的二值mask分割(这也就意味了
C
n
C_n
Cn通道上的feature map可以看做
C
n
C_n
Cn类的二值分类mask结果——即是
C
n
C_n
Cn类和非
C
n
C_n
Cn类,这也为我们的损失函数的来源之一)。角点的heatmap将会预测出哪些点最有可能是Corners角点。
那么,这就遇到了另一个问题——哪两个角点组成一个anchor。作者在associative embedding论文中受到了启发,检测环节直接给检测结果编号,表明它属于哪个物体,所得到的这些编号标签就代表了分组。 在训练中让神经网络去得到准确的编号标签,特别设计loss function,促使统一物体有相同标签,不同的物体不同,编号标签是不区分大小不规定为定值的。在上图中,经过embeddings的heatmap就有了实例信息,绿色为一组,橙色为一组,就可以确定实例的检测anchor啦。
在确定了角点是哪些点并且给了编号标签以后,我们再进步考虑一下anchor的问题——如果我们找到的角点不准确怎么办。作者也对此问题做了工作,在损失函数中加了一份偏移量的惩罚项,具体的我们将在损失函数中介绍。
【补充1】卷积神经网络的可视化
在上文中我们提到了目标检测中的Heatmaps,是一种可视化的方式;在CNN中可视化有三种,分别是:
(A)卷积核输出的可视化:可视化卷积核经过激活函数后的结果;
(B)卷积核的可视化:辅助理解卷积核是如何理解图像的;
(C)热力图的可视化:关注在分类中的关键部位,在目标检测中检测定位;
推荐看大神的博客:
(1)芥末的无奈——基于Keras的卷积神经网络(CNN)可视化
(2)sunchengquan——Python-Seaborn热图绘制
【补充2】Embedding vector
Embedding的表面含义就是“嵌入”,实质上是一种映射,是一种从语义空间到向量空间的映射,同时尽可能在向量空间保持原样本在语义空间的关系,如语义接近的两个词汇在向量空间中的位置也比较接近,在NLP中比较常见。Embedding vector与one-hot有些类似但又有不同,embedding是含有语义信息的,并且是向量大小是可控的。
我们顺便来看一下associative embedding中的用embedding vector的编码分组(如下图),我们可以看出来在同一张图像中的不同实例下颜色是不同的,这就代表着不同人的编码标签,标签没有大小信息,只判断是否为同一组。这也是CornerNet的想法来源之一。推荐看大神的博客:
(1)lyshello123——Embedding理解与代码实现
(2)是neinei啊——Word Embedding总结
(3)库页——姿态检测整理–06-Associative Embedding
【02 Corner pooling】
我们希望左上角点和右下角点能够在我们的操作中凸显出来,更好更精确的定位,作者提出了一种新的池化方式——Corner pooling。我们想得到某个location(i,j)的值,若它是左上角点,那么我们将从它最右边向左和最下边向上进行逐个大小比较,再将两个值相加(具体过程如下公式和下图)。
t
i
j
=
{
m
a
x
(
f
t
i
j
,
t
(
i
+
1
)
j
)
i
f
i
<
H
f
t
H
j
o
t
h
e
r
w
i
s
e
t_{ij}= \begin{cases} max(f_{t_{ij}},t_{(i+1)j})\quad if\quad i<H\\ f_{t_{H_j}}\quad otherwise\end{cases}
tij={max(ftij,t(i+1)j)ifi<HftHjotherwise
t l j = { m a x ( f l i j , l i ( j + 1 ) ) i f j < W f l i W o t h e r w i s e t_{lj}= \begin{cases} max(f_{l_{ij}},l_{i(j+1)})\quad if\quad j<W\\ f_{l_{iW}}\quad otherwise\end{cases} tlj={max(flij,li(j+1))ifj<WfliWotherwise
【03 Focal Loss的变形损失函数】
之前去参加CVPR大佬们的论文分享会,其中一个大佬说了一句话还是很受触动的——Deep Learning Model有两个重要的因素,一个是backbone,另一个是loss function。我们来看一下在CornerNet里面想要更加准确的描述目标检测问题,损失函数应该怎样来设计。根据这篇论文的思路,我们可以大体将损失函数分为三个部分:角点的分类损失,角点的分组损失,角点偏移量损失; 总损失公式如下:
L
=
L
d
e
t
+
α
L
p
u
l
l
+
β
L
p
u
s
h
+
γ
L
o
f
f
L = L_{det}+ \alpha L_{pull} + \beta L_{push}+\gamma L_{off}
L=Ldet+αLpull+βLpush+γLoff
首先,我们要明确,目标检测现在比较好用的损失函数是Focal Loss,所以论文中也将Focal Loss作为基础(小伙伴们可以会看我们Focal Loss的博客),先附上Focal Loss的公式:
F
L
(
p
t
)
=
−
(
1
−
p
t
)
γ
l
o
g
(
p
t
)
FL(p_t)=-(1-p_t)^{\gamma}log(p_t)
FL(pt)=−(1−pt)γlog(pt)
我们可以这样理解,Focal Loss最简单的应用就是在二分类上,是某一类或者不是某一类,咦,是不是很眼熟!这个分类有没有什么联想!这不就是我们前面在Heatmap上C个通道的二分类mask么~所以,在有ground truth的heatmap上,作者设计了一种Focal Loss的变形,公式如下:
L
d
e
t
=
−
1
N
∑
c
=
1
C
∑
i
=
1
H
∑
j
=
1
W
{
(
1
−
p
c
i
j
)
α
log
(
p
c
i
j
)
,
i
f
y
c
i
j
=
1
(
1
−
y
c
i
j
)
β
(
p
c
i
j
)
α
log
(
1
−
p
c
i
j
)
,
o
t
h
e
r
w
i
s
e
L_{det} = - \frac{1}{N} \sum^{C}_{c=1} \sum^{H}_{i=1} \sum^{W}_{j=1} \begin{cases} (1-p_{cij})^{\alpha} \log(p_{cij}) , if y_{cij}=1 \\ (1-y_{cij})^{\beta}(p_{cij})^{\alpha} \log(1-p_{cij}), otherwise\end{cases}
Ldet=−N1c=1∑Ci=1∑Hj=1∑W{(1−pcij)αlog(pcij),ifycij=1(1−ycij)β(pcij)αlog(1−pcij),otherwise
其中, p c i j p_{cij} pcij是在heatmap上的location(i,j)上对于C类的得分,并且经过了非标准化的高斯增强(The unnormalized Gaussians),也就是说明,如果 y c i j = 1 y_{cij}=1 ycij=1,这说明这个点是高斯中间点;N是这张图像中检测框的数量; α \alpha α和 β \beta β是来控制每个点对于损失函数贡献量的超参数(在实验中设置 α = 2 \alpha=2 α=2和 β = 4 \beta=4 β=4);公式中 ( 1 − y c i j ) β (1-y_{cij})^{\beta} (1−ycij)β可以看做对于ground truth检测定位的减少惩罚项。
【问题解释】如果我们将heatmap理解为数据的高斯分布,对于损失函数来说,高斯分布的拟合是回归问题,为什么可以转化为分类问题?
如上图1,这一张Feature map表示的对于 C 1 C_1 C1类的Heatmap,在我们计算损失之前,我们已经得到了左上角点和右下角点(红色的角点),并且已知的是ground truth(白色的框)。
我们首先要考虑一个问题:损失函数是什么——损失函数是用数学的方式来描述ground truth和我们训练或者预测得到的值的差,这种数学方式可微可导可回传,并且在精度上升的基础上损失函数在不断下降。这也就说明在描述同一问题的时候可以设计不同的损失函数,只要可以描述这种差距就可以啦。
从角点来考虑损失函数:对于两个左上角点(A点和a点)来说,因为我们是从Heatmap中得到的角点,Heatmap是一种高斯的数据分布,中心点响应最强烈,我们要用a点拟合A点,是一个回归问题;
从其他角度来考虑损失函数:如上图2,我们从整个anchor区域来考虑一下损失函数。图2(a)和图2(b)分别是ground truth和我们预测的结果,因为这是对于 C 1 C_1 C1类的Feature map,所以在单个通道上我们可以认为这是一种二分类情况,是C类和不是C类,我们发现Feature map被分为了四种区域(如图2©)——绿色区域(都认为是非C类)、黄色区域(都认为是C类)、白色区域(ground truth认为是C类而我们预测认为不是)和红色区域(ground truth认为不是C类而我们预测认为是),那我们就可以规定我们要找的差异就是白色区域和红色区域,因为这两个区域分类信息是不一致的;既然分类信息不一致按我们我们可以用focal loss来做基础的损失函数,就把回归问题转化成了分类问题。
上述的Focal Loss的变形损失函数根据公式我们可以看做角点在分组之前就已经造成的损失,那在角点分组过程中的损失需要怎样定义呢?角点的分组有两种情况,一是同一组的角点被分类为同一组,二是同一组的角点被分类为不同组,我们想让准确率提高时损失函数下降,可以考虑用焦点之间的距离来描述损失。我们用pull loss来描述聚合角点,用push loss来描述分离角点。
L
p
u
l
l
=
1
N
∑
k
=
1
N
[
(
e
t
k
−
e
k
)
2
+
(
e
b
k
−
e
k
)
2
]
L_{pull} = \frac{1}{N} \sum^{N}_{k=1}[(e_{t_k}-e_{k})^2+(e_{b_k}-e_{k})^2]
Lpull=N1k=1∑N[(etk−ek)2+(ebk−ek)2]
L p u s h = 1 N ( N − 1 ) ∑ k = 1 N ∑ j = 1 , j = k N m a x ( 0 , Δ − ∣ e k − e j ∣ ) L_{push} = \frac{1}{N(N-1)} \sum^N_{k=1} \sum^N_{j=1,j=k} max(0, \Delta-|e_k-e_j|) Lpush=N(N−1)1k=1∑Nj=1,j=k∑Nmax(0,Δ−∣ek−ej∣)
其中,
e
t
k
e_{t_k}
etk是左上角对于物体k的嵌入向量,
e
b
k
e_{b_k}
ebk是右下角对于物体k的嵌入向量,
Δ
\Delta
Δ在所有的实验中设为1。
此外,我们还有偏移量的损失,对于角点k,我们用两个偏移量来描述角点和ground truth之间的差距,偏移量用
o
k
o_k
ok来表示,公式如下:
o
k
=
(
x
k
n
−
⌊
x
k
n
⌋
,
y
k
n
−
⌊
y
k
n
⌋
)
o_k = (\frac{x_k}{n}-\lfloor \frac{x_k}{n} \rfloor,\frac{y_k}{n}-\lfloor \frac{y_k}{n} \rfloor)
ok=(nxk−⌊nxk⌋,nyk−⌊nyk⌋)
作者用一个对于所有种类的共享集合来左上角点的偏移量,用另一个对于所有种类的共享集合来右下角点的偏移量。在训练中,我们用了平滑的
L
1
L_1
L1损失定义,公式如下:
L
o
f
f
=
1
N
∑
k
=
1
N
S
m
o
o
t
h
L
o
s
s
(
o
k
,
o
^
k
)
L_{off}=\frac{1}{N} \sum ^{N}_{k=1} SmoothLoss(o_k,\hat o_k)
Loff=N1k=1∑NSmoothLoss(ok,o^k)
上述就是我们CornerNet的全部内容啦,如果有理解不到位的地方还请各位大佬们多多指正,蟹蟹大家的关注和理解,下周我们要开始分割的内容啦,大家下周见!