CornerNet讲解和tensorflow复现
cornernet是2018ECCV的oral,是检测领域内的一个新的分支,打破了传统的由faster-rcnn和yolo两大派系的壁垒,另辟蹊径。论文
我已经用tensorflow复现了CornerNet ,记得star哦,复现不易。
先看效果图
下面是cornernet的结构
cornernet的backbone是使用hourglass架构,这种架构的优点就是考虑到了多尺度问题,下面就是hourglass的结构图:Stacked Hourglass Networks for Human Pose Estimation
上面就是hourglass的整体的网络结构。就是几个沙漏进行级联。
其中每个沙漏如下:
详细的请看原文,上面提供了连接,这里只是顺带提一下,因为这篇博客的主角不是hourglass。
这个运算过程大致为:计算top-left时,1.计算top:对feature map从下往上看,最后一行不变,倒数第二行的数值跟最后一行比较,如果大于最后一行,就保留当前数值,否则就更新成最后一行的数值,以此类推。2.计算left:对feature map从右往左看,最后一列保存不变,倒数第二列的数值跟最后一列比较,如果大于最后一列,就保留当前数值,否则就更新成最后一列的数值,以此类推。然后两者相加,就是top-left的数值。
作用:作者在论文里面说,由于预测的那两个点,并不是在‘’内容‘’上面,而是在‘’内容‘’旁边,例如下图,橘色的点并不是打在人物身上,而是打在了人物旁边,但是这个地方所提供的信息并没有作用,而真正有作用的是人物,所以需要通过corner pool的方式将人物上的信息,转移到旁边来,以便模型在预测点的时候能更准确。
corner pooling的反向传播:你没看错,这部分作者是自己写了反向传播部分(用c语言),因为是新的方法,所以常见的框架都没有提供这种操作的反向传播。我在这部分用tensorflow实现了前向和反向传播的部分,详情请看代码。
def TopPool(inputs):
#forward
def forward(inputs):
out=tf.expand_dims(tf.reduce_max(inputs,1),1)
i=tf.constant(1)
batch,h,w,c=inputs.get_shape().as_list()
def cond(i,out):
return i < h
def body(i,out):
d=tf.expand_dims(tf.reduce_max(inputs[:,i:,:,:],1),1)
out=tf.concat((out,d),1)
i = i + 1
return i,out
_,out = tf.while_loop(cond, body, [i,out],shape_invariants= [i.get_shape(), tf.TensorShape([batch,None,w,c])])
return out
#backward
def backward(inputs,dy):
zeros=tf.expand_dims(tf.zeros_like(inputs[:,-1,:,:]),1)
ones=tf.expand_dims(tf.ones_like(inputs[:,-1,:,:]),1)
mask=tf.expand_dims(tf.ones_like(inputs[:,-1,:,:]),1)
batch,h,w,c=inputs.get_shape().as_list()
i=tf.constant(h-1)
def cond(i,mask):
return i > 0
def body(i,mask):
max_value=tf.expand_dims(tf.reduce_max(inputs[:,i:,:,:],1),1)
temp_mask=tf.where(tf.greater(tf.expand_dims(inputs[:,i-1,:,:],1),max_value),ones,zeros)
mask=tf.concat((temp_mask,mask),1)
i = i - 1
return i,mask
_,mask = tf.while_loop(cond, body, [i,mask],shape_invariants= [i.get_shape(), tf.TensorShape([batch,None,w,c])])
return mask*dy
@tf.custom_gradient
def new_grad(x):
def grad(dy):
return backward(x,dy)
return forward(x), grad
return new_grad(inputs)
既然我们能利用hourglass产生heatmap的图,也就是corner的点,那怎么知道哪两个点是一组的呢,看看下面这张图,一共预测了4个点,那如何确定哪两个点是一组(哪两个是绿色的,哪两个是橘色的)
这个地方作者借鉴了这篇文章的一个做法,就是ssociative embedding,这种方法最先用在human pose里面,用来做多人的关键点检测的ssociative embedding: End-to-end learning forjoint detection and groupin
工作原理看上两个图,在预测的heatmaps的地方,已经产生了所有的4个点,现在对4个点进行分组,在train的时候,已知label了,也就是已经知道了每对corner的位置,所以在Embeddings这个预测模块的相应位置,提取出值,然后对这些值求方差和均值,同一对的corner优化这个方差尽可能小,对于不同对的corner则使刚刚求出来的均值做差取绝对值,尽可能优化使绝对值最大。通过pull和push的操作最终训练好,模型的分组操作。
可以看看下面的公式来理解:
对于测试的部分看代码更好理解,说起来还不太好说,所以我就直接把代码放上,看看代码
def decode(self,heat_tl,heat_br,tag_tl,tag_br,offset_tl,offset_br,k=100,ae_threshold=0.5,num_dets=1000):
batch=tf.shape(heat_br)[0]
heat_tl=tf.nn.sigmoid(heat_tl)
heat_br=tf.nn.sigmoid(heat_br)
#nms
heat_tl=nms(heat_tl)
heat_br=nms(heat_br)
value_tl,position_tl,class_tl,y_tl,x_tl=top_k(heat_tl,k)
value_br,position_br,class_br,y_br,x_br=top_k(heat_br,k)
#expand to square
x_tl=tf.cast(expand_copy(x_tl,k,False),tf.float32)
y_tl=tf.cast(expand_copy(y_tl,k,False),tf.float32)
x_br=tf.cast(expand_copy(x_br,k,True),tf.float32)
y_br=tf.cast(expand_copy(y_br,k,True),tf.float32)
offset_tl=map_to_vector(offset_tl,position_tl)
offset_br=map_to_vector(offset_br,position_br)
# offset_tl=tf.reshape(offset_tl,(tf.shape(offset_tl)[0],k,1,2))
# offset_br=tf.reshape(offset_br,(tf.shape(offset_br)[0],1,k,2))
offset_tl=tf.reshape(offset_tl,(offset_tl.get_shape().as_list()[0],k,1,2))
offset_br=tf.reshape(offset_br,(offset_br.get_shape().as_list()[0],1,k,2))
x_tl=x_tl+offset_tl[:,:,:,0]
y_tl=y_tl+offset_tl[:,:,:,1]
x_br=x_br+offset_br[:,:,:,0]
y_br=y_br+offset_br[:,:,:,1]
offset_tl=tf.reshape(offset_tl,(batch,k,1,2))
offset_br=tf.reshape(offset_br,(batch,1,k,2))
#all k boxes
bboxes=tf.stack((x_tl,y_tl,x_br,y_br),axis=-1)
tag_tl=map_to_vector(tag_tl,position_tl)
tag_tl=tf.reshape(tag_tl,(batch,k,1))
tag_br=map_to_vector(tag_br,position_br)
tag_br=tf.reshape(tag_br,(batch,1,k))
dists=tf.abs(tag_tl-tag_br)
value_tl=expand_copy(value_tl,k,False)
value_br=expand_copy(value_br,k,True)
scores=(value_tl+value_br)/2
invalid=-tf.ones_like(scores)
#======debug=====
debug_scores=tf.reshape(scores,(batch,-1))
debug_scores,debug_indexs=tf.nn.top_k(debug_scores,10)
debug_bboxes=tf.reshape(bboxes,(batch,-1,4))
debug_bboxes=map_to_vector(debug_bboxes,debug_indexs,transpose=False)
#======debug=====
class_tl=tf.cast(expand_copy(class_tl,k,False),tf.float32)#[batch,k,k]
class_br=tf.cast(expand_copy(class_br,k,True),tf.float32)
mask_scores=tf.where(tf.cast(tf.equal(class_tl,class_br),tf.int32)>0,scores,invalid)
mask_scores=tf.where(dists<ae_threshold,mask_scores,invalid)
mask_scores=tf.where(x_tl<x_br,mask_scores,invalid)
mask_scores=tf.where(y_tl<y_br,mask_scores,invalid)
mask_scores=tf.reshape(mask_scores,(batch,-1))
scores,indexs=tf.nn.top_k(mask_scores,num_dets)
scores=tf.expand_dims(scores,-1)
bboxes=tf.reshape(bboxes,(batch,-1,4))
bboxes=map_to_vector(bboxes,indexs,transpose=False)
class_=tf.reshape(class_br,(batch,-1,1))
class_=map_to_vector(class_,indexs,transpose=False)
value_tl=tf.reshape(value_tl,(batch,-1,1))
value_tl=map_to_vector(value_tl,indexs,transpose=False)
value_br=tf.reshape(value_br,(batch,-1,1))
value_br=map_to_vector(value_br,indexs,transpose=False)
detection=tf.concat([bboxes,scores,value_tl,value_br,class_],-1)
return detection,debug_bboxes
本文loss分为了三个loss:1.对heatmap求focal loss 2.对分组求pull和push loss 3.由label缩小产生的位移偏差loss
1.focal loss:focal loss最初是由何凯明提出来的,用于解决’‘难易训练样本之间的权衡问题’’,有的样本很容易学,于是权重就比较低,有些样本比较难学,于是权重就比较高。另外作者除了使用focal loss外,还用了heatmap预测时常用的高斯点,这一点也体现到loss里面去了,所以跟原始的focal loss有些不同。改动的主要目的就是由于在标签点附近的点,其实偏离也不是很大,所以他的loss权重也应该小一点。
原始的focal loss
本文的focal loss
2.分组的pull和push loss
3.偏移loss