小总结-特征点检测的两种方法-FC回归和heat map
最近研究了一下特征点检测最后一步的的两种主流方法,整理了分享出来。其实说的是经过一系列网络运算之后,最后一步如何由网络的feature map直接预测出特征点坐标位置。
---------Agenda-------
- overview
- 基本的heat map是怎么实现的
- fc VS heat map
- heat map v2.0
----------------------------
-------------------------------------------------------------------开始吧------------------------------------------------------------------
主流的做关键点检测有两大流派
- 一种是用FC fully connection layer 去在最后一层去回归出每个点的坐标值, 比如要输出68个关键点,那么就是回归出68x2个坐标值
- 另一种是用heatmap去得到每个关键点的热力图,如下图所示,每个关键点一个channel, 直接用图的形式去得到最后的68个channel的output, 然后通过argmax去得到关键点坐标
基本的heat map是怎么实现的
FC的方法比较好理解,下面着重介绍下最基本的heatmap的方法怎么去做的
def get_preds_fromhm(hm, center=None, scale=None): """Obtain (x,y) coordinates given a set of N heatmaps. If the center and the scale is provided the function will return the points also in the original coordinate frame.
Arguments: hm {torch.tensor} -- the predicted heatmaps, of shape [B, N, W, H]
Keyword Arguments: center {torch.tensor} -- the center of the bounding box (default: {None}) scale {float} -- face scale (default: {None}) """ B, C, H, W = hm.shape hm_reshape = hm.reshape(B, C, H * W) idx = np.argmax(hm_reshape, axis=-1) # 首先通过argmax得到每个channel最大的相应的位置 scores = np.take_along_axis(hm_reshape, np.expand_dims(idx, axis=-1), axis=-1).squeeze(-1) preds, preds_orig = _get_preds_fromhm(hm, idx, center, scale)
return preds, preds_orig, scores |
@jit(nopython=True) def _get_preds_fromhm(hm, idx, center=None, scale=None): """Obtain (x,y) coordinates given a set of N heatmaps and the coresponding locations of the maximums. If the center and the scale is provided the function will return the points also in the original coordinate frame. Arguments: hm {torch.tensor} -- the predicted heatmaps, of shape [B, N, W, H]
Keyword Arguments: center {torch.tensor} -- the center of the bounding box (default: {None}) scale {float} -- face scale (default: {None}) """ B, C, H, W = hm.shape idx += 1 preds = idx.repeat(2).reshape(B, C, 2).astype(np.float32) preds[:, :, 0] = (preds[:, :, 0] - 1) % W + 1 preds[:, :, 1] = np.floor((preds[:, :, 1] - 1) / H) + 1 #然后得到每个热力图最热的部位的x和y的坐标,分别存在preds[:,:,0]和preds[:,:,1]中
for i in range(B): for j in range(C): hm_ = hm[i, j, :] pX, pY = int(preds[i, j, 0]) - 1, int(preds[i, j, 1]) - 1 if pX > 0 and pX < 63 and pY > 0 and pY < 63: diff = np.array( [hm_[pY, pX + 1] - hm_[pY, pX - 1], hm_[pY + 1, pX] - hm_[pY - 1, pX]]) preds[i, j] += np.sign(diff) * 0.25 # 去计算热力图上最热部位的左右两边的热力值,如果右边的大,那么热力图的最热点(preds)的值就往右边移1/4个pixel,反之亦然。同样适用于上下两个像素的值,通过这种方式去得到一个接近亚像素的坐标值
preds -= 0.5 #取中心的坐标
preds_orig = np.zeros_like(preds) if center is not None and scale is not None: for i in range(B): for j in range(C): preds_orig[i, j] = transform_np( preds[i, j], center, scale, H, True)
return preds, preds_orig |
就比较简单了,咱们多讨论一下 这种方法显然不是特别合理,首先说如果就用argmax肯定是不行的,因为可能边上的点其实跟你几乎一模一样就小一点点,那这样像素的精度只能达到1个pixel,其实很多关键点检测是缩小了去做的,那么达到的关键点坐标放大回去就会有几个像素的偏差,很多任务中会造成抖动,显然是不可接受的。这就是很多文章中会提及的heatmap的方法存在一个理论误差下值。 那么左边这段代码的方法方法可以稍微考虑下左右像素,实现亚像素精度。但是如果左右两个像素值其实比中心小很多,然后你还去区分左边多一点还是右边多一点,然后硬往那边挪1/4个像素,其实也是不合理的。
|
|||||||||
在训练标签的准备中,需要由数据标签坐标值去准备高斯热图 在训练的时候,所有的像素点都对Loss有贡献,但是在实际Inference的时候,只有5个像素点能决定最终的坐标,输出和坐标之间不能完全对应 像右边这种图,在算Loss的时候,中间的是5x5x4 = 100, 而右边的是(9-5)^2=16,似乎是右边的更好,但是在取了argmax之后,其实是中间的结果更好,就是上面所说的训练的时候计算的loss不能完全对应到输出坐标。 |
|
|
FC vs heat map
“”实战中发现fc更倾向于预测一个合理得模板,heatmap方法相对关注局部一点得特征,相对来说,heatmap方法更容易出现点位错乱的情况,而fc更多的是单个点精度不足导致偏差
regression模型很容易训练,甚至即是很少的数据也能够训练。当然与此带来的就是极度的容易过拟合和泛化问题(如果说数据能够觉得那当我没说)。key points之间的能够保持某种潜在的“秩序”(比如face alignment中双眼+鼻子+两嘴角构成的5个点,这5个点总是会保持方位上的秩序)。这种秩序性更体现在,如果输入是一张完全不相关的图像,regression依然可以给出一个形状上非常合理的结果(反观heatmap所给的key points则会完全乱了套)”
在Numerical Coordinate Regression with Convolutional Neural Networks这篇文章中有详细比较
FC的方法直接得到一个坐标点是不靠谱的,因为坐标点这个东西太隐晦了,比如说在目标检测的时候,为什么不直接FC输出4个值呢?因为这样效果不会好,所以要有什么maskRcnn还有yolo还有SSD的方法。用FC在小数据集上很容易出现过拟合。
而高斯热图的方法就对CNN友好很多
就人脸关键点检测这个任务来说,FC的方法又有其优越性,毕竟人脸其实是一个相对人体更加刚性的结构,FC的方方能八九不离十的猜出一个好不错的特征点。而且人脸的很多特征点其实并不是角点,比如眼睛上部的那个点,还有脸颊上的点,在标注的时候都有猜的成分在。高斯热图想要在这里有一个高响应比较困难。
但是高斯热图的方法计算量比较大,毕竟最后几层需要输出与输入等分辨率的而且channel数很多的热图。
heat map v2.0
那么有一种改进后的方法就来了,叫做DSNT differentiable spatial to numerical transform (DSNT) layer,他在heat map方法上进行了改进。paper link Numerical Coordinate Regression with Convolutional Neural Networks
怎么做的呢?其实就是把所有的取值做了softmax, 然后得到下图第一张,然后生成一下每个点x的坐标,每个点y的坐标,即下面图的中间和右边部分。
然后去计算x的期望和y的期望,用这个期望作为预测出的x和y。就是下面两个式子算的。这样可得亚像素的精度,而且是任意精度,不是上面基本的heat map方法的0.25像素大小的精度。非常合理。
说完合理的部分,下面说一说可能会有的问题,那就是如果光用跟这个去约束,可能得到的heat map会很奇怪,比如我们在GT的坐标的左右对称有两个高响应点也可以得到很小的loss,其实这个是会有问题的,就像下面几种方法得到的坐标位置都不差,但是还是在目标点处的一个高响应是最理想的,实验证明,如果对heat map的形状进行约束,这样可以指导网络得到更好的效果。所以loss上还会加一些约束,得到一个尽可能接近高斯相应的heat map。
具体怎么做的呢?
就是用KL散度去计算得到的heat map响应和高斯图之间的相似度即可。加一个Loss。
----------------------------------------------------------END-----------------------------------------------------------------------------------------------------
Related paper
Numerical Coordinate Regression with Convolutional Neural Networks https://arxiv.org/pdf/1801.07372.pdf
Learning Feature Pyramids for Human Pose Estimation https://arxiv.org/pdf/1708.01101.pdf
Human pose estimation via Convolutional Part Heatmap Regression https://arxiv.org/pdf/1609.01743.pdf
How far are we from solving the 2D & 3D Face Alignment problem? (and a dataset of 230,000 3D facial landmarks) https://arxiv.org/pdf/1703.07332.pdf