MTCNN人脸检测算法实现(python)

在这里插入图片描述


前言

MTCNN 是多任务级联 CNN 的人脸检测深度学习模型,该模型不仅考虑了人脸检测概率,还综合训练了人脸边框回归和面部关键点检测,多任务同时建立 loss function 并训练,因此为 MTCNN。级联 CNN 主要由三个子网络组成:P-Net、R-Net 和 O-Net。博主通过PyQt5开发一个可视化的MTCNN人脸检测器,深度学习框架采用PyTorch。


一、MTCNN网络结构

1.1 P-Net 的结构

在这里插入图片描述

从网络结构上看,P-Net 接受大小为 (12,12,3) 的图片的输入,输出三种特征图,大小为 (1,1,C),也就是说最终得到的特征图每一点都对应着一个大小为 12×12 的感受野。三种输出如下:

  • cls:图像是否包含人脸,输出向量大小为 (1,1,2),也就是两个值,即图像不是人脸的概率和图像是人脸的概率。这两个值加起来严格等于 1,之所以使用两个值来表示,是为了方便定义交叉熵损失函数;
  • bounding_box:当前框位置相对完美的人脸框位置的偏移。这个偏移大小为 (1,1,4),即表示框左上角和右下角的坐标的偏移量。网络结构中的输出叫做 bounding_boxes,如果按代码来说应该是 offsets
  • landmark:5 个关键点相对于人脸框的偏移量。分别对应着左眼的位置、右眼的位置、鼻子的位置、左嘴巴的位置、右嘴巴的位置。每个关键点需要两维来表示,因此输出是向量大小为 (1,1,10)。

建议:

  • (12,12,3) 的输入大小指的是人脸框的大小,并不是真正图片的大小。在测试的时候大家会发现我们会输入各种缩放比例的图片,为什么可以这样?这是因为 P-Net 的输出是 特征图,没有 全连接层,这意味着网络对输出图片大小没有限制;
  • 训练时是 (12,12,3) 的输入,(1,1,32) 的输出,但是这里的 (12,12) 输入只是一个示意,实际测试的时候由于 P-Net 的输出特征图的感受野是 (12, 12),输入任意尺寸的图片矩阵经过 P-Net 后可以看做经历了一个完整的卷积(kernel = 12,stride = 2),输出是 (H’,W’,32)。例如如果输入是 (48,48,3) 的图片矩阵,经过 P-Net 后输出为 (19,19,32) 了,并且 (19,19) 中每个二维点对应到原图中都是一个 (12,12) 的视野区域,可以理解为对原图进行了卷积的滑动并分别计算每个 (12,12) 窗口的人脸概率以及框回归;
  • P-Net 实际上对输出特征图的 每一个像素格子 都进行人脸概率、边框、地标预测,因此开始时的预测框数量非常多,要根据人脸概率的阈值先进行初步筛选,在进行边界框的 非极大值抑制。那这里就有疑问了,为什么可以对每一个像素方格进行预测?这里可以在后面的 图像金字塔 中再做解释;
  • 在实际测试中,P-Net 的输出中不包括 landmark

1.2 R-Net 的结构

由于 P-Net 是对输出特征图的每一个像素进行预测,因此结果十分冗杂,所以接下来使用 R-Net 进一步优化。R-Net 和 P-Net 类似,不过这一步的输入是前面 P-Net 生成的边界框,不管实际边界框的大小,在输入 R-Net 之前,都需要缩放到 (24,24,3)。网络的输出和 P-Net 是一样的。这一步的目的主要是为了去除大量的非人脸框。

在这里插入图片描述

1.3 O-Net 的结构

进一步将 R-Net 的所得到的区域缩放到 (48,48,3),输入到最后的 O-Net,O-Net 的结构与 P-Net 类似,只不过在测试输出的时候多了关键点位置的输出。输入大小为 (48,48,3) 的图像,输出包含 n 个人脸概率、边界框的偏移量和关键点的偏移量。三个子网络流程如下:
在这里插入图片描述
进一步将 R-Net 的所得到的区域缩放到 (48,48,3),输入到最后的 O-Net,O-Net 的结构与 P-Net 类似,只不过在测试输出的时候多了关键点位置的输出。输入大小为 (48,48,3) 的图像,输出包含 n 个人脸概率、边界框的偏移量和关键点的偏移量。三个子网络流程如下:
在这里插入图片描述

1.4 图像金字塔

  MTCNN基于卷积神经网络,通常只适用于检测一定尺寸范围内的人脸,比如其中的 P-Net,用于判断 12 × 12 大小范围内是否含有人脸,但是输入图像中人脸的尺寸未知,需要构建图像金字塔获得不同尺寸的图像,缩放图像是为了将图像中的人脸缩放到网络能检测的适宜尺寸,只要某个人脸被放缩到12×12左右,就可以被检测出来,下图为MTCNN人脸检测流程。
在这里插入图片描述

在人脸检测中,通常要设置要原图中要检测的最小人脸尺寸,原图中小于这个尺寸的人脸不必关心,MTCNN 代码中为 minsize = 20,MTCNN P-Net 用于检测 12 × 12 大小的人脸,这需要我们将不同的人脸大小都要缩放到 12 × 12。在 P-Net 中我们为什么可以对输出特征图中的每一个像素方格进行预测,正是因为原图中的人脸都被缩放到 12 × 12,而且输出特征图的感受野正是 12 × 12。

建议:
人脸检测中的图像金字塔构建,涉及如下数据:

  • 输入图像尺寸:(h, w)
  • 最小人脸尺寸:min_face_size
  • 最大人脸尺寸:max_face_size,如果不设置,为图像高宽中较短的那个;
  • 网络/方法能检测的人脸尺寸:net_face_size
  • 金字塔层间缩放比率:factor

缩放图像是为了将图像中的人脸缩放到网络能检测的适宜尺寸,图像金字塔中:

  • 最大缩放尺度(最小缩小比例):max_scale = net_face_size / min_face_size
  • 最小缩放尺度(最大缩小比例):min_scale = net_face_size / max_face_size
  • 中间的尺度:scale_n = max_scale * (factor ^ n)
  • 对应的图像尺寸为:(h_n, w_n) = (h * scale_n, w_n * scale_n)
  • 保证 min(h_n, w_n) >net_face_size

注: 缩小比例为缩放尺寸的倒数。

  在 MTCNN 的实际测试中,如果输入图像为 (100,120),其中人脸最小为 (20,20),最大为 (20,20)——对应图像较短边长,为了将人脸放缩到 (12,12),同时保证相邻层间缩放比率 factor = 0.709,依据上述公式则最大缩放尺度为 12 / 20,最小缩放尺度为 12 / 20,金字塔中图像尺寸依次为 (60,72)、(52,61)、(36,43)、(26,31)、(18,22)、(13,16),其中 (60,72) 对应把 (20,20) 的人脸缩放到 (12,12),(13,16)对应把 (100,100) 的人脸缩放到 (12,12),在保证缩放比率一致的情况下近似。

  综上,构建图像金字塔有两个步骤:

  1. 给定输入图像,根据设置的最小人脸尺寸以及网络能检测的人脸尺寸,确定最大缩放图像和最小缩放图像;

  2. 根据设置的金字塔层间缩放比率,确定每层图像的尺寸。

二、代码实现

如果新同学不知道如何配置环境,可以参考博主写的【Anaconda3与PyCharm安装配置保姆教程

此外,cv2无法读取中文路径图像的解决方案,具体可以参考博主写的文章【opencv-python[cv2]读取中文路径图像

2.1 静态图像人脸检测

代码如下(示例):

import numpy as np

from mtcnn import FaceDetector
from PIL import Image


import cv2
# 人脸检测对象。优先使用GPU进行计算(会自动判断GPU是否可用)
# 你也可以通过设置 FaceDetector("cpu") 或者 FaceDetector("cuda") 手动指定计算设备
detector = FaceDetector()

image = Image.open("./images/image.jpg")

# 检测人脸,返回人脸位置坐标
# 其中bboxes是一个n*5的列表、landmarks是一个n*10的列表,n表示检测出来的人脸个数,数据详细情况如下:
# bbox:[左上角x坐标, 左上角y坐标, 右下角x坐标, 右下角y坐标, 检测评分]
# landmark:[右眼x, 左眼x, 鼻子x, 右嘴角x, 左嘴角x, 右眼y, 左眼y, 鼻子y, 右嘴角y, 左嘴角y]
bboxes, landmarks = detector.detect(image)

# 绘制并保存标注图
drawed_image = detector.draw_bboxes(image)

img_cv= cv2.cvtColor(np.asarray(drawed_image),cv2.COLOR_RGB2BGR)
cv2.imshow('face', np.asarray(img_cv))
cv2.waitKey(0)


2.2 摄像头动态人脸检测

代码如下(示例):

import cv2
from mtcnn import FaceDetector
from PIL import Image
import numpy

detector = FaceDetector()


def camera_detect():
    video = cv2.VideoCapture(0)
    while True:
        ret, frame = video.read()

        # 将 OpenCV 格式的图片转换为 PIL.Image
        pil_im = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
        # 绘制带人脸框的标注图
        drawed_pil_im = detector.draw_bboxes(pil_im)
        # 再转回 OpenCV 格式用于视频显示
        frame = cv2.cvtColor(numpy.asarray(drawed_pil_im), cv2.COLOR_RGB2BGR)

        cv2.imshow("Face Detection", frame)
        # 输入 q 的时候结束循环(退出检测程序)
        if cv2.waitKey(1) & 0xFF == ord("q"):
            break

    video.release()
    cv2.destroyAllWindows()


if __name__ == "__main__":
    camera_detect()

2.3.可视化界面PyQt5实现人脸检测

2.3.1 人脸检测界面设计

在这里插入图片描述
在这里插入图片描述

2.3.2 视频人脸检测效果

在这里插入图片描述


结束语

由于博主能力有限,本篇文章中提及的方法,也难免会有疏漏之处,希望您能热心指出其中的错误,以便下次修改时能以一个更完美更严谨的样子,呈现在大家面前。

猜你喜欢

转载自blog.csdn.net/weixin_40280870/article/details/131375324