mtcnn人脸检测_人脸检测之MTCNN算法详解

e0fe78d13eda5fa6b7ee4391ff621b51.png

一、介绍

从 MTCNN(Multi-task Cascaded Convolutional Networks)的名字当中便可知,MTCNN 是多任务网络,且其网络结构为级联结构。论文中摘要中有句话特别简洁的介绍了其网络结构及其作用:

68680b6c5a9b9cb7cf49d160079a377c.png

可以看出MTCNN有三个(three stages)网络组成,或者说训练过程具有三步(“三步走”大法~),其用途(目的)则是人脸检测和人脸关键点定位,并且是一个由粗到细(还好不是人体某个 organ~)定位的过程,可以理解为使用显微镜查找细胞内细胞核的过程,先粗调找到目标,再微调以细致观察。

论文创新点:(1)通过将人脸检测和人脸关键点对齐相结合以提升检测指标;(2)对在线难样本挖掘(Online Hard Sample Mining,OHSM)算法进行改进

二、网络结构

2.1 pipline

论文中给出了一个pipiline贴图,如下图所示:

55aa23426faca327867ec23d5fe1dcd4.png
MTCNN Pipeline

看完Pipeline之后便能对MTCNN训练/测试过程有了一个初步的了解,其大致过程如下:

1)Image Pypamid : 制作图像金字塔(将尺寸从大到小的图像堆叠在一起类似金字塔形状,故名图像金字塔),对输入图像resize 到不同尺寸,为输入网络作准备。

2)Stage 1 : 将金字塔图像输入P-Net(Proposal Network),获取含人脸的Proposal boundding boxes,并通过非极大值抑制(NMS)算法(后面对NMS算法的过程进行了补充)去除冗余框,这样便初步得到一些人脸检测候选框。

3)Stage 2 : 将P-Net输出得到的人脸图像输入R-Net(Refinement Network),对人脸检测框坐标进行进一步的细化,通过NMS算法去除冗余框,此时的到的人脸检测框更加精准且冗余框更少。

4)Stage 3 : 将R-Net输出得到的人脸图像输入O-Net(Output Network),一方面对人脸检测框坐标进行进一步的细化,另一方面输出人脸5个关键点(左眼、右眼、鼻子、左嘴角、右嘴角)坐标。

疑问:为什么要做图像金字塔?

由于原始图像中,存在大小不同的脸,为了在统一尺度下检测人脸,进入网络训练之前,就要将原始图片缩放至不同尺度。以增强网络对不同尺寸大小人脸的鲁棒性。

代码:

for 

2.2 网络结构:

MTCNN 网络由P-Net 、R-Net、O-Net 串联组成,下图为三个网络的结构:

5b3453c956cd15ca0023a9f3134e9648.png

1、P-Net

P-Net包含三个卷积层,每个卷积核的大小均为3✖️3,注意到P-Net 没有全连接层。

(1)作用:判断是否含人脸,并给出人脸框和关键点的位置,为O-Net提供人脸候选框。

(2)输入:尺寸大小为 12✖️12的三通道图像

(3)输出:包含三部分:a.是否人脸的概率1✖️1✖️2向量(之所以有两个值(0和1的概率)是因为为了方便计算交叉熵);b.人脸检测框坐标(左上点和右下点)1✖️1✖️4向量;c.人脸关键点(5个关键点)坐标1✖️1✖️10向量。

代码:

class PNet(Network):
    def setup(self):
        (self.feed('data') #pylint: disable=no-value-for-parameter, no-member
             .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, padding='VALID', relu=False, name='conv3')
             .prelu(name='PReLU3')
             .conv(1, 1, 2, 1, 1, relu=False, name='conv4-1')
             .softmax(3,name='prob1'))

        (self.feed('PReLU3') #pylint: disable=no-value-for-parameter
             .conv(1, 1, 4, 1, 1, relu=False, name='conv4-2'))

代码中,激活函数为PReLU(Parametric Rectified Linear Unit),其为ReLU的改进版,即带了参数的ReLU,该参数a会随着数据变化,而当a为定值时,则变身为Leaky ReLU。

3321ba1cfcbbe77f038b4979e6391c3c.png
ReLU 和 PReLU 函数对比图

2、R-Net

P-Net网络结构与P-Net的网络结构类似,也包含三个卷积层,前两个卷积核的大小均为3✖️3,第三个卷积核的大小为2✖️2,且其相比于P-Net 多了一个全连接层。

(1)作用:对P-Net 输出可能为人脸候选框图像进一步进行判定,同时细化人脸检测目标框精度。

(2)输入:尺寸大小为 24✖️24的三通道图像

(3)输出:包含三部分:a.是否人脸的概率的1✖️1✖️2向量;b.人脸检测框坐标(左上点和右下点)1✖️1✖️4向量;c.人脸关键点坐标1✖️1✖️10向量。

代码:

class RNet(Network):
    def setup(self):
        (self.feed('data') #pylint: disable=no-value-for-parameter, no-member
             .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', relu=False, name='conv3')
             .prelu(name='prelu3')
             .fc(128, relu=False, name='conv4')
             .prelu(name='prelu4')
             .fc(2, relu=False, name='conv5-1')
             .softmax(1,name='prob1'))

        (self.feed('prelu4') #pylint: disable=no-value-for-parameter
             .fc(4, relu=False, name='conv5-2'))

3、O-Net

O-Net网络结构相比R-Net的网络结构,多了一个3✖️3卷积层,

(1)作用:对R-Net 输出可能为人脸的图像进一步进行判定,同时细化人脸检测目标框精度。

(2)输入:尺寸大小为 48✖️48的三通道图像

(3)输出:包含三部分:a.是否人脸的概率的1✖️1✖️2向量;b.人脸检测框坐标(左上点和右下点)1✖️1✖️4向量;c.人脸关键点坐标1✖️1✖️10向量。

代码:

class ONet(Network):
    def setup(self):
        (self.feed('data') #pylint: disable=no-value-for-parameter, no-member
             .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')
             .fc(2, relu=False, name='conv6-1')
             .softmax(1, name='prob1'))

        (self.feed('prelu5') #pylint: disable=no-value-for-parameter
             .fc(4, relu=False, name='conv6-2'))

        (self.feed('prelu5') #pylint: disable=no-value-for-parameter
             .fc(10, relu=False, name='conv6-3'))

估计大家也发现了一个现象,P-Net 、R-Net、O-Net ,图像的输入越来越大,每一层卷积核的个数越来越多,网络的深度也是越来越深。所以他们的运行速度也是越来越慢,然而其准确度却是越来越高。

疑问:那既然O-Net的准确度最高,为什么不直接过O-Net呢?或者是比O-Net更深的网络呢?

这是因为,假如直接过O-Net网络,运行速度会非常的慢,实际上P-Net和R-Net 这两步对含人脸检测Proposal bounding box 做了一个过滤,使得最后过耗时更大的O-Net网络 的bounding box 比较少,从而减少了整体时间。

三、网络训练

3.1 训练样本选择

  • a. 负样本数据:IoU 小于0.3
  • b. 正样本数据:IoU 大于0.65
  • c. 部分人脸数据:IoU 大于0.4 小于0.65
  • d. 关键点样本数据:样本标注了5个关键点的坐标

IoU (Intersection over Union)即bounding box 和 groud truth 的交并比,计算方法如下图所示:

c389d1ff140f7aadd635e6437abbdd3b.png
IoU 计算图

训练时,负样本和正样本用于是否人脸分类的训练;正样本和部分样本用于检测框回归训练;关键点样本则用于人脸关键点的训练。

3.2 损失函数设计

由于在MTCNN 中,每个网络都有三个输出,因此会对应有三个损失函数:

1)对于输出a:是否人脸分类,由于是分类问题,其loss 函数使用常见的交叉熵损失:

283b379003da59074f673580a0e7bd0f.png

2)对于输出b:人脸检测框定位,由于是回归问题,所以使用L2 损失函数:

c26900c30f330a2d6d8580b666c5e5a7.png

3)对于输出c:人脸关键点定位,由于也是回归问题,所以也使用L2 损失函数:

ad497f4e04256f81f79c96612d8d6fd4.png

最终将三个损失函数进行加权累加,便得到总损失函数。由上述样本选择可知,当负样本和正样本训练时,由于仅用于分类,所以其仅有分类损失,而不存在人脸检测框和人脸关键点定位损失。即并不是所有的样本都同时存在上述三种损失。通过控制不同权重,使得三个网络的关注点不一样:

5207a307b94022aaf63356b7ca2031c8.png

其中,

表示权重值,对于P-Net 和R-Net 则更关注检测框定位的准确性,而较少关注关键点定位的准确性,所以关键点定位损失权重较小
;而对于O-Net 则更关注关键点定位准确性,所以此时关键点定位损失权重较大

表示样本类型,其取值范围为
  • a. 当样本为负样本数据时,
    ;
  • b. 当样本为正样本数据时,
  • c. 当样本为部分人脸数据时,
    ;
  • d. 当样本为关键点样本数据时,

3.3 难样本挖掘

不同于传统的难样本挖掘算法,论文中提出使用在线难样本挖掘。首先将前向传递得到的loss值降序排序,然后选择前70%大的样本作为难样本,并在反向传播的过程中只计算难样本的梯度,即将对于训练网络没有增强作用的简单样本忽略不计。

补充:非极大值抑制(NMS)

论文:《Efficient Non-Maximum Suppression》

NMS 主要是用于对多个候选框去除重合率大的冗余候选框,从而保留区域内最优候选框,其过程是一个迭代遍历的过程。

78ab7292c371b1cefa53df29d1f2e865.png

算法步骤如下图所示:

3a750989e5d364b45298dcd5b9ddbcb0.png

代码:

def nms(dets, thresh):
    """Pure Python NMS baseline."""
    x1 = dets[:, 0]
    y1 = dets[:, 1]
    x2 = dets[:, 2]
    y2 = dets[:, 3]
    scores = dets[:, 4]

    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    order = scores.argsort()[::-1]

    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)
        xx1 = np.maximum(x1[i], x1[order[1:]])
        yy1 = np.maximum(y1[i], y1[order[1:]])
        xx2 = np.minimum(x2[i], x2[order[1:]])
        yy2 = np.minimum(y2[i], y2[order[1:]])

        w = np.maximum(0.0, xx2 - xx1 + 1)
        h = np.maximum(0.0, yy2 - yy1 + 1)
        inter = w * h
        ovr = inter / (areas[i] + areas[order[1:]] - inter)

        inds = np.where(ovr <= thresh)[0]
        order = order[inds + 1]

    return keep

参考文献

论文链接:Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Networks

代码:

Daniel00008/MyAI​github.com
923b3473058bc4d983a8a5526559f8dc.png


以上就是我对MTCNN算法的理解,欢迎大家一起讨论Y~

还以为写完能有时间看《妇联4》,结果已经深夜了,摔~

只好拿海报做个人脸检测Test作为封面安慰下自己~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值