mtcnn 理解二
参考网址:http://www.sfinst.com/?p=1683
人脸检测,解决两个问题:1)识别图片中有没有人脸?2)如果有,人脸在哪?因此,许多人脸应用(人脸识别、面向分析)的基础是人脸检测。
大多数人脸检测采用的流程为两阶段:
1) 找出所有可能是人脸的候选区域
2) 从候选区域中选择出最可能是人脸的区域
本文的主角MTCNN,大致是这种套路,也集成了其优缺点为:准和慢。
MTCNN人脸检测是2016年的论文提出来的,MTCNN的“MT”是指多任务学习(Multi-Task),在同一个任务中同时学习”识别人脸“、”边框回归“、”人脸关键点识别“。相比2015年的CVPR(边框调整和识别人脸分开做)的结构,MTCNN是有创新的。
参考的代码是David Sandberg的复现,
https://github.com/davidsandberg/facenet/blob/master/src/align/detect_face.py
该复现是github上MTCNN的复现中星星最多,实现得最像原作的版本。代码中包含着大量的Magic Number,初读,令人不知所云
预测过程:
step1: 第一阶段的目标是生成人脸候选框。MTCNN推断流程的第一阶段,蕴含了许多CNN的技巧,个人认为是比较精华也具有启发性的部分。并且MTCNN的推断过程中,第一阶段时间消耗占80%左右,所以如果需要优化和理解MTCNN的读者,在第一阶段投入再多精力都不为过。
1.1 为什么需要对图片做“金字塔”变换?
于各种原因,图片中的人脸的尺度有大有小,让识别算法不被目标尺度影响一直是挑战;目标检测本质上来说上目标区域内特征与模板权重的点乘操作;那么如果模板尺度与目标尺度匹配,自然会有很高的检测效果。更多详尽的应对方案总结见MTCNN的这篇文章(https://arxiv.org/pdf/1606.03473.pdf)。
MTCNN使用了图像金字塔来解决目标多尺度问题,即把原图按照一定的比例(如0.5),多次等比缩放得到多尺度的图片,很像个金字塔。P-NET的模型是用单尺度(12*12)的图片训练出来的,推断的时候,想要识别各种尺度的人脸更准,需要把待识别的人脸的尺度先变化到接近模型尺度(12*12)。
缺点就是:慢。第一,生成图片金字塔慢;第二,每种尺度的图片都需要输入进模型,相当于执行了多次的模型推断流程。
1.2设置合适的最小人脸尺寸和缩放因子为什么可以优化计算效率?缩放因子为什么官方选择0.709?
minsize是指你认为图片中需要识别的人脸的最小尺寸(单位:px)。factor是指每次对边缩放的倍数。
我们已经知道,第一阶段会多次缩放原图得到图片金字塔,目的是为了让缩放后图片中的人脸与P-NET训练时候的图片尺度(12px * 12px)接近。怎么实现呢?先把原图等比缩放`12/minsize`,再按缩放因子`factor`(例如0.5)用上一次的缩放结果不断缩放,直至最短边小于或等于12。根据上述算法,minsize越大,生成的“金字塔”层数越少,resize和pnet的计算量越小。这里以一个例子说明:如果待测图片1200px*1200px,想要让缩放后的尺寸接近模型训练图片的尺度(12px*12px)。
为什么缩放因子factor官方选择0.709?
图片金字塔缩放时,默认把宽,高都变为原来的1/2,缩放后面积变为原来的1/4;如果认为1/4的缩放幅度太大,你会怎么办?把面积缩放为原来的1/2。对的,这是很直观的想法,所以这里的缩放因子0.709 ≈ sqrt(2)/2,这样宽高变为原来的sqrt(2)/2,面积就变为原来的1/2。并且从比MTCNN更早提出的级联人脸检测CVPR2015_cascade CNN(http://users.eecs.northwestern.edu/~xsh835/assets/cvpr2015_cascnn.pdf)的实现中也能找到端倪。从实际意义上看,factor应该设置为小于1,数值越大第一阶段计算量越大,但可能找出更多的候选框。
1.3为什么把图片输入模型的时候要对每个像素做(x – 127.5)/128的操作?
归一化操作,加快收敛速度。
由于图片每个像素点上是[0, 255]的数,都是非负数,对每个像素点做(x – 127.5)/128,可以把[0, 255]映射为(-1, 1)。具体的理论原因可以自行搜索,但实践中发现,有正有负的输入,收敛速度更快。训练时候输入的图片需要先做这样的预处理,推断的时候也需要做这样的预处理才行。
备注:有的时候实现是(x – 127.5)*0.0078,是因为1/128=0.0078
1.4 P-NET网络的输出怎么还原出原图上人脸区域坐标?
这时需要看下P-NET的网络结构:
注意观察上图的特点:
- 3次卷积和1次池化操作后,原来12*12*3的矩阵变为1*1*32
- 利用这个1*1*32的向量,再通过一个1*1*2的卷积,得到了”是否是人脸”的分类结果(我们先聚焦于分类任务)
总结起来就是,输入一个12*12*3的区域到P-NET网络,可以输出“有人脸”的概率
可以联想到下面这样的场景,我们令输入图片矩阵为A,描述方式为(x1_origin,y1_origin ,x2_origin ,y2_origin ),将这个图片A缩放到12x12x3的尺寸,描述方式为(x1,y1,x2,y2),经过卷积运算后,这个12*12*3的矩阵被运算成了有无人脸的得分S, S的值是[0, 1]的数,代表有人脸的概率。即A通过一系列矩阵运算,变化到S。
由于输入矩阵A是通过原图缩放scale倍得到的,还原到原图上就是
x1_origin = x1 / scale
y1_origin = y1 / scale
x2_origin = x2 / scale
y2_origin = y2 / scale
至此,我们就解释清楚如何根据P-NET的输出S,还原到原图上的各区域上有人脸的概率。
当然,一方面我们可以通过切threshold,过滤一些得分低的区域;另一方面可以通过NMS算法,过滤重叠度高的区域。这些都能够在MTCNN的推断代码中找到对应。这里就不过多说明。
1.5什么叫边框回归?在MTCNN怎么利用边框回归的结果?为什么可以这样做?
这个有点长,可以参考另一博客
2 有什么优化方案?
MTCNN的推断是CPU密集型运算,如果是图片超过1080,生成图像金字塔的过程可能是流程中最耗时的过程。因为金字塔结构,第一阶段需要地计算很多尺度的图片。但超过1080的图片中,你需要识别的最小人脸真的需要12*12吗?minsize是不是也变大了?另外,如果你事先已经知道图片中人脸的大小,是不是可以调整一下minsize?结合你的实践场景可以思考下。
以耗时最大的第一阶段为主要优化的关键点,以下说明一些我尝试过的办法:
- 第一阶段受输入图片尺寸影响较大,可以让minsize随图片尺寸而改变,大图用大minsize
- 图片金字塔的生成过程,对上一次的resize结果进行resize而不是对全图resize
- 并行化第一阶段,收益不大,不如少resize几次。
这里对性能方面的研究做一些总结:
- MTCNN的推断流程性能优化从第一阶段入手,关键是降低迭代次数,可以利用minsize;
- MTCNN的推断流程中,模型计算耗时没有想象中那么大,反而可能是不断显存和内存之间来回复制数据导致效率不高;
代码实现:
- 原文实现:https://github.com/kpzhang93/MTCNNfacedetection_alignment
- Tensorflow的优秀复现:https://github.com/AITTSMD/MTCNN-Tensorflow
- C++复现:https://github.com/foreverYoungGitHub/MTCNN
- 本文参考的复现:https://github.com/davidsandberg/facenet