1. 概述

人脸识别在实践的日子中有着广泛的运用,得益于深度学习的开展,使得人脸识别的准确率得到大幅度提高。然而,为了做好人脸识别,第一步需求做的是对人脸检测,主要是经过对图片剖析,定位出图片中的人脸。近年来,深度学习在人脸检测方面也得到了大力开展,在2016年Kaipeng Zhang, Zhanpeng Zhang等人提出了人脸检测算法MTCNN(Multi-task Cascaded Convolutional Networks)模型[1],MTCNN算法的作用也是得到了许多实践项意图验证,在工业界得到了广泛的运用,在我个人的实践项目中也得到了较多运用。在MTCNN算法中,主要有三点的创新:

  • MTCNN的全体结构是一个多使命的级联结构,同步对人脸检测和人脸对齐两个项目学习;
  • 在级联的结构中运用了三个卷积网络,并将这三个网络级联起来;
  • 在练习的过程中运用到了在线困难样本发掘的办法;

这三个方面的规划都是为了可以提高终究的检测和对齐的作用。

2. 算法原理

2.1. MTCNN的基本原理

MTCNN是多使命级联CNN的人脸检测深度学习模型,在MTCNN中是经过三个卷积网络的级联:

  • 第一阶段的网络产出人脸的候选窗口
  • 第二阶段的第一阶段产出的候选串口批改,去除去不符合要求的候选窗口
  • 第三阶段在第二阶段的基础上进一步批改,并给出终究的五个脸部的landmark

在网络的练习过程中综合考虑人脸边框回归和面部关键点检测。MTCNN的网络全体架构如下图所示:

人脸检测和对齐算法MTCNN

由上图中可以看到,MTCNN主要由四个模块:

  • 图画金字塔(Image Pyramid):经过对原始图画进行不同尺度的变换,得到图画金字塔,以适应不同巨细的人脸的进行检测,在MTCNN中,是将图画resize成了三种巨细,分别为1212312\times 12\times 32424324\times 24\times 34848348\times 48\times3,这三种巨细分别对应了以下三个阶段模型的输入
  • 阶段1(Proposal Network): 对上述的图画金字塔中1212312\times 12\times 3的图画提取Bounding-Box,并运用NMS过滤掉大部分的窗口
  • 阶段2(Refine Network): 对上述的图画金字塔中2424324\times 24\times 3的图画,依据阶段1中提取出的Bounding-Box进一步批改,去除去不符合要求的bounding box
  • 阶段3(Output Network): 对上述的图画金字塔中4848348\times 48\times 3的图画,依据阶段2中提取出的Bounding-Box进行终究的剖析,以得到终究的成果

2.2. 三个阶段的网络

2.2.1. 第一阶段P-Net

P-Net的网络结构如下图所示:

人脸检测和对齐算法MTCNN

在P-Net中,包含了三个卷积+Max-Pooling操作,其间,卷积核的巨细统一为333\times 3,对于上述的网络成果,详细的参数剖析如下:

  • data:巨细为1212312\times 12\times 3
  • 第一组卷积(包含conv,PReLU,Max-Pooling)
    • conv:输入(1212312\times 12\times 3),输出(10101010\times 10\times 10,卷积核巨细为333\times 3,padding为00,步长为11,卷积核的个数为1010
    • PReLU:输入(10101010\times 10\times 10),输出(10101010\times 10\times 10
    • Max-Pooling:输入(10101010\times 10\times 10),输出(55105\times 5\times 10,核的巨细为222\times 2,padding为00,步长为22
  • 第二组卷积(包含conv,PReLU)
    • conv:输入(55105\times 5\times 10),输出(33163\times 3\times 16,卷积核巨细为333\times 3,padding为00,步长为11,卷积核的个数为1616
    • PReLU:输入(33163\times 3\times 16),输出(33163\times 3\times 16
  • 第三组卷积(包含conv,PReLU)
    • conv:输入(33163\times 3\times 16),输出(11321\times 1\times 32,卷积核巨细为333\times 3,padding为00,步长为11,卷积核的个数为3232
    • PReLU:输入(11321\times 1\times 32),输出(11321\times 1\times 32

终究得到3232个巨细为111\times 1的特征图,下面分为三个使命分别描绘:

  • face classification:输入(11321\times 1\times 32),输出(1121\times 1\times 2,卷积核巨细为111\times 1,padding为00,步长为11,卷积核的个数为22
  • bounding box regression:输入(11321\times 1\times 32),输出(1141\times 1\times 4,卷积核巨细为111\times 1,padding为00,步长为11,卷积核的个数为44
  • facial landmark localization:输入(11321\times 1\times 32),输出(11101\times 1\times 10,卷积核巨细为111\times 1,padding为00,步长为11,卷积核的个数为1010

注:三个使命的输出都是直接在终究一层的特征图上运用卷积操作。

参阅[2]的代码完成,P-Net的代码如下:

class PNet(NetWork):
    def setup(self, task='data', reuse=False):
        with tf.variable_scope('pnet', reuse=reuse):
            (
                self.feed(task) .conv( # 第一组卷积
                    3,
                    3,
                    10,
                    1,
                    1,
                    padding='VALID',
                    relu=False,
                    name='conv1') .prelu(
                    name='PReLU1') .max_pool(
                    2,
                    2,
                    2,
                    2,
                    name='pool1') .conv( # 第二组卷积
                    3,
                    3,
                    16,
                    1,
                    1,
                    padding='VALID',
                    relu=False,
                    name='conv2') .prelu(
                        name='PReLU2') .conv( # 第三组卷积
                            3,
                            3,
                            32,
                            1,
                            1,
                            task=task,
                            padding='VALID',
                            relu=False,
                            name='conv3',
                            wd=self.weight_decay_coeff) .prelu(
                                name='PReLU3'))
        if self.mode == 'train':
            if task == 'cls': # face classification
                (self.feed('PReLU3')
                     .conv(1, 1, 2, 1, 1, task=task, relu=False,
                           name='pnet/conv4-1', wd=self.weight_decay_coeff))
            elif task == 'bbx': # bounding box regression
                (self.feed('PReLU3')
                     .conv(1, 1, 4, 1, 1, task=task, relu=False,
                           name='pnet/conv4-2', wd=self.weight_decay_coeff))
            elif task == 'pts': # facial landmark localization
                (self.feed('PReLU3')
                     .conv(1, 1, 10, 1, 1, task=task, relu=False,
                           name='pnet/conv4-3', wd=self.weight_decay_coeff))
            self.out_put.append(self.get_output())
        else:
            (self.feed('PReLU3')
                 .conv(1, 1, 2, 1, 1, relu=False, name='pnet/conv4-1')
                 .softmax(name='softmax'))
            self.out_put.append(self.get_output())
            (self.feed('PReLU3')
                 .conv(1, 1, 4, 1, 1, relu=False, name='pnet/conv4-2'))
            self.out_put.append(self.get_output())

2.2.2. 第二阶段R-Net

R-Net的网络结构如下图所示:

人脸检测和对齐算法MTCNN

第二阶段的模型与第一阶段基本一致,只是在终究一层的特征图后接上了一个全衔接层,一起在衔接三个不同使命时也是运用了全衔接的操作,参阅[2]的代码如下:

class RNet(NetWork):
    def setup(self, task='data', reuse=False):
        with tf.variable_scope('rnet', reuse=reuse):
            (
                self.feed(task) .conv( # 第一个卷积
                    3,
                    3,
                    28,
                    1,
                    1,
                    padding='VALID',
                    relu=False,
                    name='conv1') .prelu(
                    name='prelu1') .max_pool(
                    3,
                    3,
                    2,
                    2,
                    name='pool1') .conv( # 第二个卷积
                    3,
                    3,
                    48,
                    1,
                    1,
                    padding='VALID',
                    relu=False,
                    name='conv2') .prelu(
                        name='prelu2') .max_pool(
                            3,
                            3,
                            2,
                            2,
                            padding='VALID',
                            name='pool2') .conv( # 第三个卷积
                                2,
                                2,
                                64,
                                1,
                                1,
                                padding='VALID',
                                task=task,
                                relu=False,
                                name='conv3',
                                wd=self.weight_decay_coeff) .prelu(
                                    name='prelu3') .fc( # 全衔接层
                                        128,
                                        task=task,
                                        relu=False,
                                        name='conv4',
                                        wd=self.weight_decay_coeff) .prelu(
                                            name='prelu4'))
        if self.mode == 'train':
            if task == 'cls': # face classification,运用fc
                (self.feed('prelu4')
                     .fc(2, task=task, relu=False,
                         name='rnet/conv5-1', wd=self.weight_decay_coeff))
            elif task == 'bbx': # bounding box regression,运用fc
                (self.feed('prelu4')
                     .fc(4, task=task, relu=False,
                         name='rnet/conv5-2', wd=self.weight_decay_coeff))
            elif task == 'pts': # facial landmark localization,运用fc
                (self.feed('prelu4')
                     .fc(10, task=task, relu=False,
                         name='rnet/conv5-3', wd=self.weight_decay_coeff))
            self.out_put.append(self.get_output())
        else:
            (self.feed('prelu4')
                 .fc(2, relu=False, name='rnet/conv5-1')
                 .softmax(name='softmax'))
            self.out_put.append(self.get_output())
            (self.feed('prelu4')
                 .fc(4, relu=False, name='rnet/conv5-2'))
            self.out_put.append(self.get_output())

2.2.3. 第三阶段O-Net

第三阶段的网络O-Net时MTCNN网络的终究输出,ONet的模型结构如下所示:

人脸检测和对齐算法MTCNN

第三阶段的模型与第二阶段基本一致,在终究一层的特征图后也是接上了一个全衔接层,一起在衔接三个不同使命时也是运用了全衔接的操作,参阅[2]的代码如下:

class ONet(NetWork):
    def setup(self, task='data', reuse=False):
        with tf.variable_scope('onet', reuse=reuse):
            (
                self.feed(task) .conv( # 第一组卷积
                    3,
                    3,
                    32,
                    1,
                    1,
                    padding='VALID',
                    relu=False,
                    name='conv1') .prelu(
                    name='prelu1') .max_pool(
                    3,
                    3,
                    2,
                    2,
                    name='pool1') .conv( # 第二组卷积
                    3,
                    3,
                    64,
                    1,
                    1,
                    padding='VALID',
                    relu=False,
                    name='conv2') .prelu(
                        name='prelu2') .max_pool(
                            3,
                            3,
                            2,
                            2,
                            padding='VALID',
                            name='pool2') .conv( # 第三组卷积
                                3,
                                3,
                                64,
                                1,
                                1,
                                padding='VALID',
                                relu=False,
                                name='conv3') .prelu(
                                    name='prelu3') .max_pool(
                                        2,
                                        2,
                                        2,
                                        2,
                                        name='pool3') .conv( # 第四组卷积
                                            2,
                                            2,
                                            128,
                                            1,
                                            1,
                                            padding='VALID',
                                            relu=False,
                                            name='conv4') .prelu(
                                                name='prelu4') .fc( # 全衔接层
                                                    256,
                                                    relu=False,
                                                    name='conv5') .prelu(
                                                        name='prelu5'))
        if self.mode == 'train':
            if task == 'cls': # face classification,运用fc
                (self.feed('prelu5')
                     .fc(2, task=task, relu=False,
                         name='onet/conv6-1', wd=self.weight_decay_coeff))
            elif task == 'bbx': # bounding box regression,运用fc
                (self.feed('prelu5')
                     .fc(4, task=task, relu=False,
                         name='onet/conv6-2', wd=self.weight_decay_coeff))
            elif task == 'pts': # facial landmark localization,运用fc
                (self.feed('prelu5')
                     .fc(10, task=task, relu=False,
                         name='onet/conv6-3', wd=self.weight_decay_coeff))
            self.out_put.append(self.get_output())
        else:
            (self.feed('prelu5')
                 .fc(2, relu=False, name='onet/conv6-1')
                 .softmax(name='softmax'))
            self.out_put.append(self.get_output())
            (self.feed('prelu5')
                 .fc(4, relu=False, name='onet/conv6-2'))
            self.out_put.append(self.get_output())
            (self.feed('prelu5')
                 .fc(10, relu=False, name='onet/conv6-3'))
            self.out_put.append(self.get_output())

2.3. 练习方针

在上述的三个网络中,都包含了三个方针,分别为face classification,bounding box regression和facial landmark localization。

2.3.1. Face Classification

人脸分类的方针是用于判别网络生成的窗口部分是否是人脸,这个一个典型的分类问题,可以运用交叉熵的丢失函数,详细的方针如下所示:

Lidet=−(yidetlog(pi)+(1−yidet)(1−log(pi)))L_i^{det}=-\left ( y_i^{det}log\left ( p_i \right )+\left ( 1-y_i^{det} \right )\left ( 1-log\left ( p_i \right ) \right ) \right )

其间,pip_i是模型产出的成果,yidet∈{0,1}y_i^{det}\in \left \{ 0,1 \right \}表明的是标示的成果。

2.3.2. Bounding Box Regression

Bounding Box的意图是为了生成人脸的方针框,在核算的过程中,需求核算当时的bounding box和标示的bounding box之间的差异,这个可以由回归问题表明,详细的方针如下所示:

Libox=∥yibox−yibox∥22L_i^{box}=\left \| \hat{y}_i^{box}-y_i^{box} \right \|^2_2

其间,yibox\hat{y}_i^{box}是模型产出的成果,yibox∈R4y_i^{box}\in \mathbb{R}^4表明的是标示的bounding box,其间每一个bounding box是由四维数据组成,分别为:左上点坐标,长和宽。

2.3.3. Facial Landmark Localization

Facial Landmark Localization的意图是要生成人脸的landmark,与Bounding Box一样,需求比较模型产出的成果与标示成果之间的差异,也是可以经过回归问题来表明彼此之间的差异,详细的方针如下所示:

Lilandmark=∥yilandmark−yilandmark∥22L_i^{landmark}=\left \| \hat{y}_i^{landmark}-y_i^{landmark} \right \|^2_2

其间,yilandmark\hat{y}_i^{landmark}是模型产出的成果,yilandmark∈R10y_i^{landmark}\in \mathbb{R}^{10}表明的是标示的landmark,其间每一个人脸的landmark是包含了五个点,分别为左眼,右眼,鼻子,嘴的左角,嘴的右角。

2.3.4. 多方针的交融

有了上述的三个方针函数,在练习的过程中,需求一个统一的方针的方针函数将上述的三个方针函数交融,详细可以由下面公式表明:

min  ∑i=1N∑j∈{det,box,landmark}jijLijmin\; \sum_{i=1}^{N}\sum_{j\in \left \{ det,box,landmark \right \}}\alpha _j\beta _i^jL_i^j

其间,j\alpha _jij\beta _i^j是两个超参,但是在[1]中,给出了固定的值,其间ij∈{0,1}\beta _i^j\in \left \{ 0,1 \right \}j\alpha _j的值为:

  • P-Net和R-Net:det=1\alpha _{det}=1box=0.5\alpha _{box}=0.5landmark=0.5\alpha _{landmark }=0.5
  • O-Net:det=1\alpha _{det}=1box=0.5\alpha _{box}=0.5landmark=1\alpha _{landmark }=1

2.4. 其他

除了上述对模型以及方针函数的剖析,在MTCNN中,还有两点,一个是在模型中运用的是PReLU激活函数,另一个是在练习过程中,为了能提高模型的作用,运用到了在线困难样本发掘(online hard sample mining)。

2.4.1. PReLU激活函数

PReLU激活函数[3]与ReLU的对比如下图所示:

人脸检测和对齐算法MTCNN

PReLU的详细方式为:

f(yi)={yiifyi>0aiyiifyi≤0f\left ( y_i \right )=\begin{cases} y_i & \text{ if } y_i> 0 \\ a_iy_i & \text{ if } y_i\leq 0 \end{cases}

2.4.2. 在线困难样本发掘

在线困难样本发掘(Online Hard Sample Mining)旨在练习过程中找到难以练习正确的样本。在实践的过程中,在每个练习的mini-batch中,对当时的batch中的所有样本核算丢失值,并排序,选择出top的70%作为hard samples,在反向核算的过程中,只核算这些样本的梯度值。在参阅[2]中并未完成这部分的代码。

3. 总结

在现如今再回过头来看MTCNN这个模型,无论是模型还是思路上都现已比较落后,但在当时的条件下,确实因为其较好的表现,在业界得到了许多的运用。回忆MTCNN算法,全体的结构是一个多使命的级联结构,同步对人脸检测和人脸对齐两个项目学习,并且在级联的结构中运用了三个卷积网络,并将这三个网络级联起来,一步一步对成果精修,使得可以得到终究理想的作用,一起,在练习的过程中运用到了在线困难样本发掘的办法,进一步协助整个过程的练习。从现在再回过头来看MTCNN,存在着以下的几个问题:

  • 三个网络模型(P-Net,R-Net和O-Net)是分隔独自练习的,没有做到端到端
  • 模型结构较为简单,卷积网络在后续得到了更多的开展

补充:在三个网络模型中,至于网络模型结构的规划,没有get到其规划的原理。

参阅文献

[1] Zhang K, Zhang Z, Li Z, et al. Joint face detection and alignment using multitask cascaded convolutional networks[J]. IEEE signal processing letters, 2016, 23(10): 1499-1503.

[2] github.com/zhaozhiyong…

[3] He K, Zhang X, Ren S, et al. Delving deep into rectifiers: Surpassing human-level performance on imagenet classification[C]//Proceedings of the IEEE international conference on computer vision. 2015: 1026-1034.