理论分析+Python&Tensorflow&Opencv实现深度学习MTCNN人脸检测
MTCNN理论
论文地址:https://arxiv.org/ftp/arxiv/papers/1604/1604.02878.pdf (此论文有部分勘误,后期有修改,具体以代码为准)
具体实现代码地址将在文末放出链接
MTCNN和多数卷积神经网络一样具体分为两个部分即预测部分和训练部分。
1.训练-反向传播
通过论文的思路,MTCNN是一个级联的CNN神经网络结构,分别是:PNet、RNet、ONet;
如图可以看出,MTCNN三层网络的输入图像、层中卷积核、输出图像等信息。细分一下,输出也分为三部分,即人脸预测分数、边框回归、人脸的五点定位。所以在训练时候也是分开的,三个部分通过前向传播,预测出相应的三个信息,再通过与标签上的三个信息的误差,做反向传播,从而更新全面卷积核的参数,达到迭代预测,使得整个网络与正确的标签逐渐接近。
三个属性的损失函数也是不一样的,人脸的预测分数使用的是cross-entropy loss(交叉熵损失函数):
边框回归使用的损失函数是Euclidean loss(范数),即偏移量的面积ROI:
人脸的五点定位损失也是类似的Euclidean loss(范数),只不过做了某种减小操作(具体的不多做展开):
由于本篇文章重点着眼于预测,所以就不在训练上继续了。
2.网络结构介绍
注:本文的尺寸表达格式为(矩阵长,矩阵宽,矩阵通道)
2.1 PNet(Proposal Net)
PNet原理描述
PNet是将样本图像resize成(12,12,3)的尺寸作为输入,先通过三层卷积核(第一层卷积后接一个最大池化层)尺寸分别为Conv(3,3,10)+MP(2,2,10)、(3,3,16)、(3,3,32)进行矩阵的互相关操作,使得输出图像的尺寸按层顺序依次是(5,5,10)、(3,3,16)、(1,1,32)。最后再通过三层卷积核分别为(1,1,2)和(1,1,4)和(1,1,10)的尺寸输出相同尺寸的信息,如图信息内容分别包括为特征矩阵预测出是否是人脸的概率(此处激活函数是"SoftMax")以及预测出的特征在图像中的位置信息,即BoundingBox(左上的X坐标,左上的Y坐标,长,宽)和人脸的五点定位;
网络结构伪代码:
# 读取
img = imread("图像在磁盘中的的位置")
# 获取图像信息(长、宽、通道)
height, width, channel = img.shape
# 将图像resize到(12,12,3)
resized_im = resize(img , (12, 12), interpolation=cv2.INTER_LINEAR)
# 第一个卷积层,用于提取特征图
conv2d(input_shape=(12,12,3),kernel_shape(3,3,10),strides=1,name="conv1")
# 第一个池化层,用于简化特征图
max_pooling(kernel_size=(2,2,10),strides=1,name="MP1")
# 第二个卷积层,用于提取特征图
conv2d(input_shape=(5,5,10),kernel_shape(3,3,16),strides=1,name="conv2")
# 第三个卷积层,用于提取特征图
conv2d(input_shape=(3,3,16),kernel_shape(3,3,32),strides=1,name="conv3")
# 第四个卷积层,用于人脸预测分数,使用“softmax激活函数”
conv2d(input_shape=(1,1,32),kernel_shape(1,1,2),strides=1,name="conv4")
# 第五个卷积层,用于边框回归
conv2d(input_shape=(1,1,32),kernel_shape(1,1,4),name="conv5")
# 第六个卷积层,用于人脸的五点定位
conv2d(input_shape=(1,1,32),kernel_shape(1,1,10),name="conv6")
值得一提的是,PNet属于一个全卷积网络,所以它并没有像别的卷积层一样最后连接一个Dense(全连接),这么做的原因是因为之后预测的时候,每张图像的尺寸不一样,为了能适应尺寸的多样性,通过1X1卷积核保存特征矩阵的信息而非向量信息(如果使用特征向量信息会使得不同的输入尺寸得到不同的特征向量维度不同)。此处关键点在后面的预测环节详细展开。
2.2 RNet(Refinement Net)
RNet原理描述
RNet是将图像resize成(24,24,3)尺寸作为输入,先通过三层卷积核(第一、二层卷积后接一个最大池化层)尺寸分别为Conv(3,3,28)+MP(3,3,28)、Conv(3,3,48)+MP(3,3,48)、(2,2,64)进行矩阵的互相关操作,使得输出图像的尺寸按层顺序依次是(11,11,28)、(4,4,48)、(3,3,64)。最后再通过全连接层输出一个128维度的向量,如图信息内容分别包括为特征矩阵预测出是否是人脸的概率(此处激活函数是"SoftMax")以及预测出的特征在图像中的位置信息,即BoundingBox(左上的X坐标,左上的Y坐标,长,宽)和人脸的五点定位;
网络结构伪代码:
# 读取
img = imread("PNet保存的候选图")
# 获取图像信息(长、宽、通道)
height, width, channel = img.shape
# 将图像resize到(24,24,3)
resized_im = resize(img , (24, 24), interpolation=cv2.INTER_LINEAR)
# 第一个卷积层,用于提取特征图
conv2d(input_shape=(24,24,3),kernel_shape(3,3,28),strides=1,name="conv1")
# 第一个池化层,用于简化特征图
max_pooling(kernel_size=(3,3,28),strides=2,activation=“relu”,name="MP1")
# 第二个卷积层,用于提取特征图
conv2d(input_shape=(11,11,28),kernel_shape(3,3,48),strides=2,name="conv2")
# 第二个池化层,用于简化特征图
max_pooling(kernel_size=(3,3,48),strides=2,activation=“relu”,name="MP2")
# 第三个卷积层,用于提取特征图
conv2d(input_shape=(4,4,48),kernel_shape(2,2,64),strides=1,name="conv3")
# 第四层,全连接用于连接特征图
dense(input_shape=(3,3,64),output_shape=128,activation=“relu”,name="fully connect1")
# 第五层,全连接用于人脸预测分数,使用“softmax激活函数”
dense(output_shape=2,activation=“softmax”,name="fully connect2")
# 第六层,全连接用于边框回归
dense(output_shape=4,name="fully connect3")
# 第七层,全连接用于人脸的五点定位
dense(output_shape=10,name="fully connect4")
2.3 ONet(Output Net)
ONet原理描述
ONet是将图像resize成(48,48,3)尺寸作为输入,先通过四层卷积核(第一、二、三层卷积后接一个最大池化层)尺寸分别为Conv(3,3,32)+MP(3,3,32)、Conv(3,3,64)+MP(3,3,64)、(3,3,64)+MP(2,2,64)、(2,2,128)进行矩阵的互相关操作,使得输出图像的尺寸按层顺序依次是(23,23,32)、(10,10,64)、(4,4,64)、(3,3,128)。最后再通过全连接层输出一个256维度的向量,如图信息内容分别包括为特征矩阵预测出是否是人脸的概率(此处激活函数是"SoftMax")以及预测出的特征在图像中的位置信息,即BoundingBox(左上的X坐标,左上的Y坐标,长,宽)和人脸的五点定位;
# 读取
img = imread("RNet保存的候选图")
# 获取图像信息(长、宽、通道)
height, width, channel = img.shape
# 将图像resize到(48,48,3)
resized_im = resize(img , (48, 48), interpolation=cv2.INTER_LINEAR)
# 第一个卷积层,用于提取特征图
conv2d(input_shape=(48,48,3),kernel_shape(3,3,32),strides=1,name="conv1")
# 第一个池化层,用于简化特征图
max_pooling(kernel_size=(3,3,32),strides=2,activation=“relu”,name="MP1")
# 第二个卷积层,用于提取特征图
conv2d(input_shape=(23,23,32),kernel_shape(3,3,64),strides=2,activation=“relu”,name="conv2")
# 第二个池化层,用于简化特征图
max_pooling(kernel_size=(3,3,64),strides=2,name="MP2")
# 第三个卷积层,用于提取特征图
conv2d(input_shape=(10,10,64),kernel_shape(3,3,64),strides=1,name="conv3&#