Fork me on GitHub

MTCNN模型学习

模型理解

MTCNN(Tutil-Task CNN):这里的多任务指的是人脸检测、人脸对齐、特征点定位,严格来说这里并不做人脸识别。模型采用级联结构,由三个不同的层组成:

Proposal-Net:生成候选框
Refine-Net:优化候选框,去掉重叠率较小的
Output-Net:继续优化,并定位特征点

实际上,每一步都会有三个输出:人脸判断,边框预测,特征点定位;只是在不同的阶段侧重点不同,前两个阶段侧重于判断人脸以及筛选边框,最后的O-Net则更关注特征点。

P-Net环节

(1)会随机生成一些边界框,其位置、尺寸存在一定的随机性,但仍然会紧紧依赖于所给的图片;
(2)输入是图片,输出是人脸判断、边框检测、特征点定位,目标函数是这三者的预测值与真实值的差距和,只是此阶段人脸判断的误差占比会最大;
(3)图片分为三类:negative,positive,part-face,此环节的主要任务是对这三类图片分类,并在positive类上产生脸部的边界框;
(4)边界框可能会有很多是重叠的、不准确的,这时就要用回归的方式不断调整参数,回归检测的标准是输入的标签,里面有准确的脸部边框数据;
(5)激活函数为pReLU,优化算法为动量梯度下降,在进行一定的学习次数之后,模型可以比较准确的进行人脸分类,并预测脸框的位置。

R-Net环节

(1)会以P-Net环节的优化模型作为基础,再次生成边界框(这时已经可以较准确的判断人脸了);
(2)同样是利用卷积神经网络来进行学习,只是卷积的模型会稍有区别(输入变为24*24,卷积结构改变);
(3)虽然这一层是优化层,主要用来去掉大量无效的边界框,但是三类输出的损失占比同P-Net层是一样的,相当于只是加深了网络而已。

O-Net环节

(1)主要是进行人脸特征点的定位学习,这里三类输出的损失占比和前两个稍有不同,特征点的损失和人脸分类相同,而弱化边界框
(2)所以在这里,应该是默认人脸分类、边界框定位已经准确了,最后在边界框内部修正特征点的位置
(3)实际学习中,特征点的位置用的并不是绝对坐标,而是在边界框中的相对位置,坐标范围是[0,1]

网络的重点在于:使用了边界框回归,非极大值抑制NMS;边界框回归是用来不断调整边界框的位置,NMS是用来摒弃那些不合格的边界框(刚开始产生的框是非常多的)。
网络的模型并不难懂,关键在于在python+TensorFlow环境下,如何处理和操作数据(这里的数据量是非常大的,比cifar100多)


数据的处理与模型训练


从runAll.sh文件内容可以看到,三层网络是级联关系,分阶段学习;三阶段中有一部分操作是相同的,调用的同一脚本(仅限于我所看到的框架源码)

gen_hard_bbox_pnet.py

主要是为pnet层产生所需的数据,说白了就是边界框,分成三类:negative,positive,part-face,用这些边界框去截取原图像,得到框选出的区域(可能是人脸,可能不是) 里面会涉及到框的随机位置产生、随机尺寸,还有可能相对于真实人脸框的偏移、缩放等;要的就是增加训练数据的复杂性,使得模型鲁棒性更强。
数据部分是随机生成的,它相对于真实人脸的位置不确定,大部分是错误的人脸区域;还有一部分是从真实人脸的附近产生,接近正解(其实这一部分要不要都无所谓)。
从这里可以看到,训练框的产生依赖于真实框,真实框是事先定义好的,需要从文件中读取进来;然后通过对训练框的学习来优化模型参数

gen_landmark_aug.py

产生landmark数据,这个数据也是从真实数据中做一定的偏移得到的,只不过不是直接针对landmark来偏移,而是建立landmark在图片中的相对位置,旋转图片,然后对应的相对位置也发生改变

gen_tfrecords.py

本地数据创建完成,包括:image,box,landmark,这里的一张图片可能包含多个box和landmark,但是box和landmark是成对出现的
该脚本读取本地数据,然后存入tfrecord中,以后每次训练需要相关的数据就从tfrecord中读取,这样做的好处是速度快、占用内存小
tfrecord是TensorFlow中一种管理数据的的方式,首先是要生成一个tfrecord文件,然后使用的时候读取这个文件,从里面恢复数据;tfrecord文件的格式是固定的

train.py

训练脚本,同cifar100类似,里面定义了多线程管理机制,从tfrecord读取数据,利用feed_dict传入模型,得到预测值之后计算损失函数值;
定义步数,学习率,优化算法,等等,然后创建一个session来运行计算图,保存计算结果(包括可视化数据)

gen_hard_bbox_rnet_onet.py

这是R-Net和O-Net阶段产生边界框的文件,与gen_hard_bbox_pnet.py地位相同,但是具体实现略有差别
因为在P-Net阶段已经产生了tfrecord文件,那就没必要再从原始文件中去读取数据做操作了,直接在P-Net所产生的tfrecord数据基础上做操作
这一步的操作依然是产生边界框,所以还是需要偏移、旋转、缩放,这是最为主要的操作,完了之后将数据重新写入到tfrecord文件
虽然改变了tfrecord文件,但是之前的数据用过了,后面的阶段自己会产生数据,所以也就无所谓

commom_utils.py

定义了一些常用的方法,比如计算iou,方格化图片,等

tfrecord_utils.py,tfrecord_reader.py

定义了一些tfrecord文件的操作方法

landmark_utils.py

定义了画边界框、特征点的方法

mtcnn_model.py

定义了三个模型的结构,如何形成卷积神经网络的,卷积个数、大小、池化等等

总结

训练的时候,可以修改runAll.sh文件,分别只用到pnet、rnet、onet三部分,分别执行三次,就可以完成模型的训练。模型训练完成之后,结果会保存在tmp文件夹中,model中就是三个阶段的模型


实例测试

测试代码在testing文件夹中,运行”python test_images.py —stage=pnet”可以测试pnet,rnet/onet类似

  1. test_images.py:获取模型的路径、待测试的图片路径,利用mtcnndector来计算人脸位置,并输出;调用了MtcnnDetector.py脚本
  2. MtcnnDetector.py:detect_face是入口,会调用pnet、rnet、onet的检测方法,然后不同的阶段分别进行预测,预测时用到detector方法;调用detector.py脚本
  3. detector.py:这是真正做预测的地方,首先根据test_images.py保存的模型路径来读取模型数据,然后将数据传入模型,进行预测,返回数据

输入文件:存放在images文件夹下,几张图片即可
输出文件:存放在resoults_xxx文件夹下,是检测到的人脸图像

下图是三个阶段的评判阈值,修改这里的参数,结果会有差别;
第一个参数用在pnet层,大于这个值被认为合格,这个值太大可能导致后面rnet、onet检测不到结果;后面同理。

问题:(1)onet相对于rnet、rnet相对于pnet,框的数量在减少,这个减少的操作在哪里完成?应该是有一个判断舍弃的?
答:通过py_nms函数完成的,这个函数将框与框之间的重叠区域界定到一个范围;实际上如果大部分都是重叠的,说明两个框表示同一张人脸,如果两个框重叠区域较小,那肯定要去掉一个,保存最优的;如果没有重叠区域,要么是错误,要么是属于两个人脸。

-------------本文结束感谢您的阅读-------------
ChengQian wechat
有问题可以通过微信一起讨论!