如何利用MTCNN模型实现人脸检测?
点击上方关注,All in AI中国
作者:Chi-Feng Wang
由于我最近一直在探索MTCNN模型(https://github.com/ipazc/mtcnn),我决定尝试着训练一下它。即使仅仅从概念上考虑,训练MTCNN模型也是一个挑战。它是一个级联卷积网络,它是由三个不能一起训练的独立神经网络组成的。我决定开始训练P-net,这是第一个网络(相关补充:https://towardsdatascience.com/how-does-a-face-detection-program-work-using-neural-networks-17896df8e6ff)。
P-net是传统的12-net:它以一幅12x12像素的图像作为输入,并输出一个矩阵结果,告诉你是否有一个人脸 ,如果有,(进而告诉你)每个脸的边框和面部标志的坐标。因此,我必须首先创建一个仅由12x12像素图像组成的数据集。
开发该模型的团队使用WIDER-FACE数据集(http://mmlab.ie.cuhk.edu.hk/projects/WIDERFace/)来训练边框坐标,使用CelebA数据集来训练脸部特征(http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html)。为了简单起见,我一开始只训练边框坐标。
WIDER-FACE数据集包括32,203张图像,这其中的393,703张人脸均为在不同情况下所拍摄。这些图像被分割成一个训练集、一个验证集和一个测试集。在训练集中,图像根据拍摄场合做出了区分:
每个文件夹内都有数百张照片,有数千张脸:
然而,所有这些照片都明显大于12x12像素。我不得不将它们每一个裁剪成多个12x12个正方形,其中一些包含人脸,而有些则没有。
我考虑过简单地创建一个12x12内核,该内核在每个图像之间移动,并每移动2像素复制一次图像。然而,这将给我留下数以百万计的照片,而且其中大多数照片都没有脸。为了确保我能有一个更好的训练过程,我希望我的训练照片中有大约50%的照片包含脸。此外,脸部因人而异。也就是说我的照片需要包含不同大小的脸。
这就是我决定要做的:首先,要载入照片,去掉有多张脸的照片,因为(多张脸)只会使裁剪过程变得更加复杂。
然后,我将对每张照片以4份不同的比例进行"拷贝",这样照片中的(每一张)脸就有12像素的,11个像素的,10像素的,还有9像素的。
小于9x9像素的人脸太小,无法识别。为了说明这个观点,看一下下面这一张9x9像素的年轻贾斯汀·比伯的脸:https://youtu.be/45rAJyXIOZM
对于每个按比例缩放的照片副本,我将尽可能多地按12x12的像素进行裁剪。例如,在贾斯汀·比伯的这张12x11的像素图像中,我可以裁剪出2张图像。
所以说使用较小的比例,我们可以裁剪出非常多的12x12图像。对于每个裁剪的图像,我需要转换边框坐标0到1之间的值,其中图像的左上角是(0,0),右下角是(1,1)。这样做的好处是便于处理计算和以及令缩放过的图像和边框恢复到它们原来的大小。
最后,我将边框坐标保存到一个.txt文件中。我运行了几次,发现每张脸都有大约60张裁剪过的照片。我所要做的就是再创建60张没有脸的裁剪过的图片。
生成负(无脸)图像比生成正(有脸)图像容易。类似地,我还为每幅图像/照片创建了多个缩放过副本,其大小分别为12、11、10和9像素,然后随机绘制了一个12x12像素框。如果像素框正好落在边框中,就再画一个像素框。如果像素框与边框有未重合部分,就裁剪图像的这一部分。
最后,生成了大约5000张正图像和5000张负图像。这已经足够做一个非常简单的短期训练了。
训练明显要容易的多。我使用了原始文件中的代码,构建了P-Net。然后,读取了正和负图像,以及它们的每一组框坐标,把每个框都作为一个数组。这里"定义"每个负图像的边框坐标都是[0,0,0,0,0]。
然后,用索引把这些图像打乱:因为一开始首先加载都是正图像,也就是说所有的正图像都在数组的开头。如果不把它打乱,前几批训练数据都是正的图像。
最后,定义了一个交叉熵损失函数:即每个边框坐标的误差平方和的概率。
进行循环训练。大约在30次(迭代)后,准确率达到了80%左右。考虑到数据集中只有10000张图片,这个准确率还不错。
在保存了权值以后,将它们重新加载到完整的MTCNN文件中,并使用新训练的P-Net进行了测试。就像以前一样,它仍然可以准确地识别人脸,并在它们周围绘制边框。MTCNN模型的一个巨大优势是,即使P-Net精度下降,R-Net和O-Net仍然能够改进边框。
与其处理R-Net和O-Net数据的繁琐过程相比,然后在Github上找到了MTCNN模型(https://github.com/wangbm/MTCNN-Tensorflow),其中包含了模型的训练文件。该模型同样只使用WIDER-FACE数据集训练边框坐标(而不是人脸特征)。
就像我所做的一样,这个模型在训练前将每个图像(P-Net的像素为12x12, R-Net为24x24, O-Net为48x48)进行裁剪。与简单算法不同,该团队将基于IoU的图像分类为正或负(相交于Union,即12x12图像与边框之间的相交区域,除以12x12图像和边框的总面积),并包括一个单独的"只有部分脸"的类别。
创建一个单独的"部分脸"类别,并允许网络学习部分覆盖的脸。这样,即使你戴上太阳镜,或者把脸转开一半,网络仍然可以对你的脸进行识别。
此外,在R-Net和O-Net的训练中,他们对有难度的样本进行难分样本挖掘。即使经过训练,P-Net也不是完美的。它有时会错误的识别出一些没有脸的图片,来当做正面的(有脸的)图片,这样的图像被称为误报。由于R-Net的工作是细化边框和减少误报率,所以在训练P-Net后,我们可以将P-Net的误报记录带入R-Net的训练数据中,这样做的确提高识别的准确性。这个过程称为对难分样本挖掘挖掘。同样,他们也在O-Net训练中应用了对难分样本挖掘。
这个模型的另一个有趣的方面是它们的损失函数。它们没有为人脸检测和边框坐标定义一个损失函数,而是分别定义了一个损失函数。在训练过程中,每进行一次反向传播,他们就会在两个损失函数之间来回切换。我以前从未见过像这样定义损失函数的——我一直认为定义一个包含一切的损失函数会更简单。那么,这样来回切换是否能提高训练的准确性?
训练这个模型花了3天。大型数据集使训练和生成难例成为一个缓慢的过程。此外,GPU在我第一次训练时内存不足,迫使我重新训练R-net和O-net(这又花了一天时间)。
我以前没有研究过这个,但是分配GPU内存是训练过程的另一个重要部分。在每个训练项目结束时,都要注意他们想要使用多少GPU内存,以及他们是否会允许增长。如果是,程序可以要求更多的内存。如果没有,程序将在程序开始时分配内存,并且不会在整个训练过程中使用更多的内存。但这将使得进程更慢,不过这的确降低了GPU内存耗尽的风险。
点击这里阅读有关实现MTCNN模型的文章:https://medium.com/@reina.wang/mtcnn-face-detection-cdcb20448ce0
点击这里阅读MTCNN模型的网络结构:https://medium.com/@reina.wang/face-detection-neural-network-structure-257b8f6f85d1
点击这里了解MTCNN模型的工作原理:https://towardsdatascience.com/how-does-a-face-detection-program-work-using-neural-networks-17896df8e6ff
下载与MTCNN相关的文件和资源:
在Github下载训练过的模型:https://github.com/ipazc/mtcnn
在Github下载可训练的模型:https://github.com/wangbm/MTCNN-Tensorflow
相关研究文章:http://arxiv.org/abs/1604.02878
我的P-Net训练代码:https://github.com/reinaw1012/pnet-training