CornerNet: Detecting Objects as Paired Keypoints (anchor free)解读
CSDN地址:https://blog.csdn.net/hancoder/article/details/94066206
知乎:https://zhuanlan.zhihu.com/p/71411741
之前目标检测器基本都是基于anchor来预测得分和坐标,如faster rcnn等。
cornerNet之前方法使用anchor的缺点:
作者Hei Law,Jia Deng
本文主要思路其实来源于一篇多人姿态估计的论文。简要介绍一下姿态估计:基于CNN的2D多人姿态估计方法,通常有2个思路(Bottom-Up Approaches和Top-Down Approaches):
CornerNet中anchor的提出是受了Newell的人体姿态检测的影响。在Newell的Associative Embedding: End-to-End Learning for Joint Detection and Grouping这篇论文里面,他提出了人体姿态检测的一站式的bottom-up方法, 可以用于多人姿态检测和目标分割。Associative Embedding有如下特点:
核心思想1:CornerNet基于Associative Embedding中预测人体关节点的想法,把关节点换成anchor的左上点和右下点,那么我们就将画anchor框的方式转变为左上角(top-left角点)和右下角(bottom-right角点),而之前目标检测算法是用anchor的中心点和宽、高来描述anchor。这个想法的实现不是很难,之前的方法我们可以想到全卷积FCN,不断卷积,输出和输入图像相同大小的卷积图,让通道数等于类别即可,我们就知道了每个像素点对应的类别概率。cornerNet中也是大概这种思路,最后通道数为:有左上角点和右下角点2种可能性×每种角点的每个类别的概率。实际操作的时候不同之处只是把2×C个串联通道分成了两个并联的C通道,即最后预测了两个corner角点heatmaps来确定anchor:在每个角点heatmap有C个通道,代表C个类(不含背景类),heatmap大小是H×W。既然是类,代表就可以通过这个heatmap来表示每个像素上对应类别的概率。如果加上一个mask,即有了真值ground truth,那么我们可以很容易算出预测值与真实值之间的差距,让网络通过loss不断学习从而形成好的预测结果。
核心思想2:那么,这就遇到了另一个问题----哪两个角点组成一个anchor。作者在associative embedding论文中受到了启发,检测环节直接给检测结果编号,表明它属于哪个物体,所得到的这些编号标签就代表了分组。如下图,绿色中最高的概率形成了坐框,橙色中最高的概率形成了右框。这个部分loss的设计初看可能有些困难,但我们现在只需要知道我们的label是很好赋的即可,让loss促使同一物体有相同标签,不同物体标签不同。还需注意的是如果简单地编号123…7,那么如果我们用平方和MSE进行计算的话,1和7的差距明显比1和2的差距大,所以我们需要在loss上面下点功夫,让编号标签统一认为距离超过1即是不同物体,超过1loss不再增加。loss的设计后面再讲。
核心思想3:上面的分析似乎是在输入图像大小==输出特征图大小的基础上进行的,这无疑会消耗电脑的内存,增加训练成本,如何让输出图像小一点也能达到差不多的效果呢?作者给出的思路是offset,即让网络学习偏移量大小。详见loss部分。
简单看一下框架。最后那个放大框只是放大了左上角点的模块,右下角点也有同样的结构。下面从输入分析框架。
网络输入的图像大小为511×511,通过stride=2,channel=128的7×7conv和stride=2,channel=256的residual block,共下采样4倍,输入到backbone中的图像大小是128×128×256。这样预测结果像素与像素之间就不对应了,对此解决方法是后面的offset loss
特征提取网络也就是Backbone使用的是Hourglass网络,这也是从人体姿态估计处借用来的灵感。沙漏网络在关键点检测上已经证明了其有效性。
沙漏网络即一系列降采样+一系列上采样恢复到输入尺寸,下采样使计算复杂度降低,上采样采用的是最近邻上采样,其中还包含了skip layer,即residual模块用以补充下采样maxpooling丢失的信息。其中没有使用最大池化,而是使用了步长为2的卷积进行下采样了5倍。通道数依次是{256,384,384,384,512}。整个hourglass network的深度是104层。
本文的backbone是两个Hourglass网络进行串联拼接stack,两个网络的总深度是104。在第一个沙漏网络的输入和输出上添加了3×3的conv+BN,对应元素相加后接着一个Relu和residual。这个结果作为第二个沙漏网络的输入。这里读起来有点混乱,未能弄清。我猜测是不是输入作用3×3作为res分支,另外一个分支经过一系列conv后最后用3×3,然后两个分支相加,不过这不是什么重点。了解其思想借鉴了resnet即可。
不像FPN等,沙漏网络只使用最好一层特征图进行预测,没有在多层上预测。
网络有两个分支,一个分支预测Top-left Corners,另一个预测Bottom-right corners。下面图相当于只画出了Top-left Corners。
首先一条分支进入Top-left Corners,另一条分支是右下角点。每个分支又分为3个分支:1个分支的作用类似于res模块,另外两个分支是corner pool。
CornerPool这是一种新型的池化层,可帮助卷积网络更好地定位边界框的角点。 原理:边界框的一角通常在目标之外,参考下图圆形典型的情况,角点明显不在物体上。
在很多情况下,角点不能根据当前的信息进行定位。相反,为了确定像素位置是否有左上角,我们需要水平地向右看目标的最上面边界,垂直地向底部看物体的最左边边界,这样才能判断当前点能不能代表框的坐上点。 这个过程即corner pooling layer:
简单概括即当前点的值=max{ 该点右面/下面的所有点 }。有个方向计算后的特征图元素相加。可以结合下图验证一下。
在上面corner pool相加的结果上,即上面三个分支合并后又重新分为3个分支:heatmaps用于预测哪些点最有可能是Corners点;embeddings主要预测每个点所属的目标;最后的offsets用于对点的位置进行修正,下面进行详细介绍
可以结合loss函数理解
用的是Adam算法
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
α = 0.1 , β = 0.1 , γ = 1 \alpha=0.1,\beta=0.1,\gamma=1 α=0.1,β=0.1,γ=1
但是算loss之前必须先了解label是如何赋的,才能计算预测值与真值之间的差距:
在论文第六页有如下描述:
对于各个角点,只有一个真值gt是“正位置”positive location,而其他所有的位置(角点)都是负的。在训练过程中,我们并不是同等地惩罚负位置,而是减少了在正位置附近(一个圆半径范围)的负位置的惩罚。这么做的原因是如果一对角点接近他们各自的真值位置,即使没有和正位置完全一对一像素匹配,但在正位置附近也可以推测出来一个好的框,这个框与gt框的IOU足够大。而这个半径r是一个变值,他的作用类似于faster rcnn中的赋label规则:anchor如果与真值box的IOU大于一个阈值0.7,那么认为这个anchor就是正的。
如下图,红色实线框是ground truth,绿色虚线是一个预测框,可以看出这个预测框的两个角点和ground truth并不重合,但是该预测框基本框住了目标,因此也是很好的预测框。我们要将“还不错的框”和“错的框”区别,这就是为什么要对不同负样本点的损失函数采取不同权重值的原因。
实现思路是只要真值框与预测框的交并比IOU大于一个阈值t就可以认为他是不错的角点,这个过程是通过在真值像素上设置半径r实现的。这样只要让阈值t大于0.7,即可求得半径r。即半径是根据圆圈内的角点组成的框和ground truth的IOU值大于0.7而设定的。在半径范围内的y值符合高斯分布:
另 σ = 1 3 r σ=\frac13r σ=31r,即可根据 e − x 2 + y 2 2 σ 2 e^{-\frac {x^2+y^2}{2σ^2}} e−2σ2x2+y2给正位置附近的负位置赋label。(简单验证一下x=0,y=0时应该赋label=1)
检测loss即第一个分支的分类loss借鉴了focal loss。
L d e t = − 1 N ∑ c = 1 C ∑ i = 1 H ∑ j = 1 W = { ( 1 − p c i j ) α l o g ( p c i j ) i f : y c i j = 1 ( 1 − y c i j ) β ( p c i j ) α l o g ( 1 − p c i j ) o t h e r w i s e L_{det}=\frac {-1}N\sum_{c=1}^C\sum_{i=1}^H\sum_{j=1}^W= \begin{cases} (1-p_{cij})^α log(p_{cij}) & if :y_{cij} = 1 \\ (1-y_{cij})^β (p_{cij})^α log(1-p_{cij})& otherwise \end{cases} Ldet=N−1c=1∑Ci=1∑Hj=1∑W={(1−pcij)αlog(pcij)(1−ycij)β(pcij)αlog(1−pcij)if:ycij=1otherwise
focal loss理解:
这个值和目标检测算法中预测的offset类似却完全不一样,说类似是因为都是偏置信息,说不一样是因为在目标检测算法中预测的offset是表示预测框和anchor之间的偏置,而这里的offset是表示在取整计算时丢失的精度信息:
从输入图像到特征图之间会有尺寸缩小,取整会带来精度丢失,这尤其影响小尺寸目标的回归,Faster RCNN中的 ROI Pooling也是有类似的精度丢失问题,在Faster rcnn中的解决方式是:保留小数+插值=AlignPool。
假设缩小倍数是n(下采样因子),那么输入图像上的(x,y)点对应到特征图上位置为: ( ⌊ x k n ⌋ , ⌊ y k n ⌋ ) (\left\lfloor\frac{x_k}n\right\rfloor,\left\lfloor\frac{y_k}n\right\rfloor) (⌊nxk⌋,⌊nyk⌋)
角点k的精度损失o_k为:
o k = ( x k n − ⌊ x k n ⌋ , y k n − ⌊ y k n ⌋ ) o_k=(\frac{x_k}n-\left\lfloor\frac{x_k}n\right\rfloor, \frac{y_k}n-\left\lfloor\frac{y_k}n\right\rfloor) ok=(nxk−⌊nxk⌋,nyk−⌊nyk⌋)
所以通过公式2计算offset,然后通过公式3的smooth L1损失函数监督学习该参数,和常见的目标检测算法中的回归支路类似。L1函数能防止梯度爆炸。
L o f f = 1 N ∑ k = 1 n S m o o t h L 1 L o s s ( o k , o ^ k ) L_{off}=\frac 1N \sum_{k=1}^n SmoothL_1Loss(o_k,\hat o_k) Loff=N1k=1∑nSmoothL1Loss(ok,o^k)
N是目标个数,预测了一套左上角点的offset,还有一套右上角点的offset
这部分是受associative embedding那篇文章的启发,简而言之就是**基于不同角点的embedding vector之间的距离找到每个目标的一对角点,如果一个左上角角点和一个右下角角点属于同一个目标,那么二者的embedding vector之间的距离应该很小。**不同目标预测出来的embedding值应当尽可能地远 。
下面的引用的意思是计算量应该尽可能地少;不同物体的标签只要表明不同即可,即之前提到的MSE大小的问题。
associative embedding:为了减少计算量,我们应该避免直接计算每一对关节点之间的损失,相应的,我们对每个人都产生了一个reference embedding,reference embedding的生成方法就是对人的关节点的embedding值取平均。有了reference embedding后,
对于单个人来说,我们计算每个关节点预测的embedding和reference embedding的平方距离;
对于两个不同的人来说,我们比较他们之间的reference embedding,
随着它们之间距离的增加,惩罚将以指数方式降为0. 接下来,我们对这一过程进行形式化。
embedding这部分的训练是通过两个损失函数实现的,
L p u l l = 1 N ∑ k = 1 N [ ( e t k − e k ) 2 + ( e b k − e k ) 2 ] , L p u s h = 1 N ( N − 1 ) ∑ k N ∑ j = 1 J ≠ k N m a x ( 0 , △ − ∣ e k − e j ∣ ) L_{pull}=\frac 1N\sum_{k=1}^N [(e_{tk}-e_k)^2 + (e_{bk}-e_k)^2],\\ L_{push}=\frac 1{N(N-1)}\sum_k^N\sum_{j=1 \\J≠k}^Nmax(0,△-|e_k-e_j|) Lpull=N1k=1∑N[(etk−ek)2+(ebk−ek)2],Lpush=N(N−1)1k∑Nj=1J̸=k∑Nmax(0,△−∣ek−ej∣)
embedding loss的理解:
为了更透彻理解这部分,我想先给出人体姿势associative embedding论文的loss函数,同本文一样这部分loss分别pull和push两部分。图中有n个人,每个人有k个节点标签组成姿势。xnk代表第n个人第k个节点的坐标值。k代表每个人有k个节点, h ˉ n \bar hn hˉn为代表第n个人节点标签的平均值。虽然我没有看这部分代码,但我想 h ˉ n \bar hn hˉn应该是学习处理的吧?但也通过gt给了一些group的信息。这样loss的前半部分就通过训练使hnk与 h ˉ n \bar hn hˉn接近,即第n个人的节点的预测标签值应该趋近于第n个人标签的真值平均值。而后半部分联想e-x图像,x越大总体越小,即想让任两个人的标签的平均值尽可能相差地大。但e-x的斜率是逐渐减少的,x到一定程度后就已经接近0了,所以训练会让两个人的标签平均值相差一些即可(超过1)。
继续分析本文的loss函数
L p u l l = 1 N ∑ k = 1 N [ ( e t k − e k ) 2 + ( e b k − e k ) 2 ] , L p u s h = 1 N ( N − 1 ) ∑ k N ∑ j = 1 J ≠ k N m a x ( 0 , △ − ∣ e k − e j ∣ ) L_{pull}=\frac 1N\sum_{k=1}^N [(e_{tk}-e_k)^2 + (e_{bk}-e_k)^2],\\ L_{push}=\frac 1{N(N-1)}\sum_k^N\sum_{j=1 \\J≠k}^Nmax(0,△-|e_k-e_j|) Lpull=N1k=1∑N[(etk−ek)2+(ebk−ek)2],Lpush=N(N−1)1k∑Nj=1J̸=k∑Nmax(0,△−∣ek−ej∣)
N代表gt框的个数,ek代表第k个框左上和右下编码向量(标签值)平均值。△=1。
对于pull函数:即让etk和ebk接近于ek,趋向于相同值,这样左上和右下标签值指向同一标签,被分作一个框。
对于push函数:有N个gt框,作者想让gt框两两作差,实现不同框的标签值不同,即有N(N-1)个差值。△=1,k和j是不同值,那么训练让不同的框的左上右下标签【平均值】|ek-ej|相差最少1以上。因为两个框之间标签的距离大小是无必要很大的,只要距离大于1让网络可以得出不是相同框的结论即可。即,如果ek和ej相差1以内,那么max函数的结果是1-|ek-ej|是一个大于0的数,loss还会让训练继续使loss趋向于0。当ek和ej相差1以上,1-|ek-ej|小于0,max整体的结果恒为0,所以相当于ek和ej相差1以后网络不再关心这两者的差值,转为关心其他两两框的差值。但其实这个过程是同步的。
没有预训练,输入511×511,输出128×128
α=0.1,β=0.1,γ=1是各个L的权重。α和β比1大效果会不好
49batchsize,10GPU。
在MS COCO上实现了42.1%的AP
我们假设了两个原因,为什么检测角点会比检测边界框中心或proposals更好。
- 1)anchor boxes的中心点难以确定,是依赖于目标的四条边的,但是顶点却只需要目标框的两个边,所以角点更容易提取,而且还使用了corner pooling,因而表现比anchor好。
- 2)采用了更加高效的空间检测框机制,顶点可以更有效地提取离散的边界空间,这里只使用O(w∗h)个角点就代表了O(W2∗h2)的可能检测框anchor box。
附录:
本文使用的初级模块称为Residual Module,得名于其中的旁路相加结构(在这篇论文中2称为residual learning)
第一行为卷积路,由三个核尺度不同的卷积层(白色)串联而成,间插有Batch Normalization(浅蓝)和ReLU(浅紫);
第二行为跳级路,只包含一个核尺度为1的卷积层;如果跳级路的输入输出通道数相同,则这一路为单位映射。
所有卷积层的步长为1,pading为1,不改变数据尺寸,只对数据深度(channel)进行变更。
Residual Module由两个参数控制:输入深度M和输出深度N。可以对任意尺寸图像操作。
上下两个半路都包含若干Residual模块(浅绿),逐步提取更深层次特征。但上半路在原尺度进行,下半路经历了先降采样(红色/2)再升采样(红色*2)的过程。
降采样使用max pooling,升采样使用最近邻插值。
一阶Hourglass
二阶Hourglass
四阶Hourglass
每次降采样之前,分出上半路保留原尺度信息;
每次升采样之后,和上一个尺度的数据相加;
两次降采样之后,使用三个Residual模块提取特征;
两次相加之间,使用一个Residual模块提取特征。
这里主要引用其他解读文章较为出色的总体话语。
为了将检测结果对应到个人,作者使用非极大值抑制(non-maximun suppression)来取得每个关节heatmap峰值,然后检索其对应位置的标签,再比较所有身体位置的标签,找到足够接近的标签分为一组,这样就将关节点匹配单个人身上
为了减少运算量,我们应该避免直接计算每一对关节点之间的损失,相应的,我们对每个人都产生一个reference embedding,reference embedding的生成方法就是对人的关节点的embedding值取平均。有了reference embedding后,对于单个人来说,我们计算每个关节点预测的embedding和reference embedding的平方距离;对于两个不同的人来说,我们比较他们之间的reference embedding,随着它们之间距离的增加,惩罚将以指数方式降为0. 接下来,我们对这一过程进行形式化。
Hourglass:https://blog.csdn.net/shenxiaolu1984/article/details/51428392
https://zhuanlan.zhihu.com/p/53407590