项目十 人脸检测器的使用

人脸检测一直是机器学习当中一个非常经典的话题。在深度学习还未得到广泛的 研究之前,人脸检测最为经典的方法是 Haar 特征+AdaBoost 分类器,很多人认为采 用 AdaBoost 的方式已经是人脸检测问题的最佳解决方案,但是经过实际测试发现 AdaBoost 分类器仍然存在误检和漏检的情况,例如逆光、人脸佩戴物遮挡、侧脸和黑 种人等问题。在深度学习开始得到广泛的研究后,不断的在图像上取得非常优秀的成 绩,尤其是在人脸方向,人脸检测和人脸识别的精度在不断的?升。

img1

1. 准备工作

在指定的磁盘路径创建存放当前项目的目录,linux或macos可使用mkdir命令创建文件夹目录,Windows直接使用图形化界面右键新建文件夹即可,例如我们的存放项目的目录名为project10,并创建dataset和models文件:

    (dlwork) jingyudeMacBook-Pro:~ jingyuyan$ mkdir project10

    (dlwork) jingyudeMacBook-Pro:~ jingyuyan$ cd project10

    (dlwork) jingyudeMacBook-Pro:project10 jingyuyan$ mkdir dataset

    (dlwork) jingyudeMacBook-Pro:project10 jingyuyan$ mkdir models

    (dlwork) jingyudeMacBook-Pro:project10 jingyuyan$ jupyter notebook

经过一系列操作,读者会得到这样的一个文件夹

project10/
├───demo10.ipynb
├───models/
└───dataset/

2. 测试数据集

本篇数据集是由作则自行收集整理的person1000数据集,其中包含图片1000张,每一张图片包含一张或一张以上的人脸。另外还有一个imgs文件夹存放着几张我们本次实验需要测试的图片文件。

2.1 数据下载与安放

这些数据可以在附录中给出的方法进行下载,下载后把这person1000和imgs放入dataset文件夹中,得到目录如下。

project10/
├───demo10.ipynb
├───models/
└───dataset/
    ├───imgs/
       ├──00.jpg
       ├──01.jpg
          ...
    └───person1000/
         ├──00.png
         ├──01.png
            ...

2.2 数据的读取和可视化

定义read_images_random函数,输入文件夹地址进行批量预测,默认提取出30张样本,最后返回出检测结果的一组图片。读取图片采用OpenCV读取图片,可以将图片直接以Numpy的多维数组形式存储。

import cv2
import os
import matplotlib.pyplot as plt
import random
import numpy as np
def read_images_random(dir_path, num=30):
    imgs = []
    all_images = []
    for parent, dirnames, filenames in os.walk(dir_path):
        imgs = filenames
    # 随机不重复的挑选num张照片
    test_files_name = random.sample(range(0, len(imgs) - 1), num )
    test_img = [imgs[index] for index in test_files_name]
    for test_file in test_img:
        path = os.path.join(dir_path, test_file)
        taget = cv2.imread(path)
        all_images.append(taget)
    return all_images

利用matplotlib绘制一组图片,默认30张为一组

def rbg2bgr(img):
    """由于OpenCV的颜色通道顺序为BGR,
    而matplotlib的通道循序为RGB,
    所以需要将BGR转为RGB进行输出"""
    try:
        b, g, r = cv2.split(img)
        out = cv2.merge([r, g, b])
    except BaseException:
        return img
    return out

def show_images(images, num=30):
    fig = plt.gcf()
    fig.set_size_inches(12, 14)
    if (num>len(images)):
        num = len(images)
    for i in range(0, num):
        target = images[i]
        # 利用rbg2bgr进行通道转换
        out = rbg2bgr(target)
        ax = plt.subplot(5, 6, 1 + i)
        ax.imshow(out)
        title = str(i+1)
        ax.set_title(title, fontsize=10)
        ax.set_xticks([])
        ax.set_yticks([])
    plt.show()

在ipython下使用matplotlib显示图片做比较直观,舍弃cv2.imshow()函数。由于OpenCV输出图片的顺序是BGR,我们可以自行写一个转换函数,读者也可以用其他函数库进行转换。

def imshow(img, size=(7, 7)):
    plt.figure(figsize=size)
    if len(img.shape) == 3:
        img2 = img[:,:,::-1] 
        plt.imshow(img2)
    elif len(img.shape) == 2:
        img2 = img[:,:]
        plt.imshow(img2, cmap='gray')
    else:
        print('error')

设定好数据集的地址

# 获取项目根目录
ROOT = os.getcwd()
# 获取数据集目录
DATASET_PATH = os.path.join(ROOT, 'dataset')
# 获取数据集目录
IMGS_PATH = os.path.join(DATASET_PATH, 'imgs')
# 获取数据集目录
PERSON1000_PATH = os.path.join(DATASET_PATH, 'person1000')

利用定义好的读取函数和可视化函数进行测试。先读取person1000数据集中的随机30项目数据集查看数据集。

# 读取person1000内的随机30个数据
imgs = read_images_random(PERSON1000_PATH)
show_images(imgs)

png

使用单个显示函数显示一个图片。

imshow(imgs[11])

png

3. 使用haar分类器进行人脸检测

使用基于Haar特征的级联分类器的对象检测是Paul Viola和Michael Jones在2001年发表的文章“使用简单特征的增强级联的快速对象检测”中提出的一种有效的对象检测方法。它是基于机器学习的方法,其中a级联功能是从许多正面和负面图像进行训练。然后用它来检测其他图像中的对象。OpenCV已经包含许多用于面部,眼睛,笑脸等的预先分类器。这些XML文件存储在https://github.com/opencv/opencv/tree/master/data/haarcascades 中,读者可以自行下载,当然,也可通过附录中的下载方式进行下载提取文件。

3.1 安放Haar模型文件

将下载好的haarcascades文件夹放入项目目录下的models下,读者会得到这样的一个文件夹结构:

project10/
├───demo10.ipynb
├───models/
  └───haarcascades/ <------------haarcascades放在这里
      ├──haarcascade_eye.xml
      ├──haarcascade_frontalcatface_extended.xml
      ├──haarcascade_frontalface_alt_tree.xml
      ├──haarcascade_frontalface_alt.xml
      ├──haarcascade_frontalface_alt2.xml
         ...
└───dataset/
    ├───imgs/
       ├──00.jpg
       ├──01.jpg
          ...
    └───person1000/
         ├──00.png
         ├──01.png
            ...

3.2 使用haarcascade进行人脸检测实验

本小节实验需要使用haarcascade_frontalface_alt2.xml这个文件。haarcascade_frontalface_alt2.xml是一个人脸检测器模型,速度比较快,并且使用方法相对简单方便。

# 构建models文件夹目录
MODELS_PATH = os.path.join(ROOT, 'models')
# 构建haarcascades目录
HARRCASCADES_PATH = os.path.join(MODELS_PATH, 'haarcascades')
# 构建haarcascade_frontalface_alt2文件夹目录
FRONTAFACE_ALT2_WEIGHT_PATH = os.path.join(HARRCASCADES_PATH, 'haarcascade_frontalface_alt2.xml')

使用cv2.CascadeClassifier加载HAAR级联分类器

facesDetector = cv2.CascadeClassifier(FRONTAFACE_ALT2_WEIGHT_PATH)

3.2.1 单张人脸检测实验

准备一张图片进行测试,我们首次测试,挑选imgs文件夹下的00.jpg文件进行测试。

test_img = cv2.imread(os.path.join(IMGS_PATH, '00.jpg'))

可视化图像test_img,并输出其shape。

imshow(test_img), test_img.shape
(None, (479, 400, 3))

png

我们将test_img转换成灰度图像,因为检测器使用单通道图像可以加快检测速度。转换完毕可视化结果,并输出其shape。

test_img_gray = cv2.cvtColor(test_img, cv2.COLOR_BGR2GRAY)
imshow(test_img_gray), test_img_gray.shape
(None, (479, 400))

png

使用OpenCV中的detectMultiScale函数,可以检测图片中所有的人脸,并返回各个人脸矩形的坐标和长宽位置。detectMultiScale常用的参数如下:

  • image :输入一张图片。
  • scaleFactor :扫码窗口扩大比例,例如默认为1.1,扩展比例以次为10%。
  • minNeighbors:表示相邻目标下,两个矩形框最小距离,如果小于这个最小距离,那么将会被排除。
  • minSize、maxSize:通常用来限制目标最大和最小的范围

检测过程中,我们可以计算出检测时长。

import time

current_time = time.time()
# 检测test_img_gray对象
faces = facesDetector.detectMultiScale(test_img_gray, 1.3, 5)
# 计算检测用时
use_time = time.time() - current_time

我们将检测结果faces打印输出

print("检测到的人脸数量: ", len(faces))
print("检测结果的形状: ",faces.shape)
print("人脸矩形框: ", faces)
print("检测用时: ", str(round(use_time, 5))+'秒')
检测到的人脸数量:  1
检测结果的形状:  (1, 4)
人脸矩形框:  [[124  82 188 188]]
检测用时:  0.09075

可以发现,我们检测到一张人脸,并返回一组矩形框,如何判断检测是否正确?我们可以将检测到的人脸框在图片上绘制出来,查看结果。(x1, y1)和(x2, y2)分别表示人脸框对角两个点。第二点需要通过第一个点加上延伸的宽度w和高度h进行相加得出。

# 矩形框四个属性分别对应起始点x、起始点y、延伸宽度w和延伸高度h
x1, y1 , w, h = faces[0]
x2 = x1 + w
y2 = y1 + h

将矩形框绘制到原图的复制品上,输入两个坐标的参数,两对坐标分别是矩形两个对角的点,绘制出一个像素值为(B:200, G:200, R:0)的、宽度为3像素的边框。

# 复制一张原图进行绘制
test_img_draw = test_img.copy()
# 使用cv2.rectangle进行绘制矩形框
cv2.rectangle(test_img_draw, (x1, y1), (x2, y2), (200, 200,0), 3)
# 可视化结果
imshow(test_img_draw)
```![png](http://q65n4x23a.bkt.clouddn.com/10_http://q65n4x23a.bkt.clouddn.com/10_


![png](http://q65n4x23a.bkt.clouddn.com/10_http://q65n4x23a.bkt.clouddn.com/10_output_51_0.png)


一张人脸的图片效果还算不错,用时仅0.05646秒,且也能找对正确的人脸位置。

#### 3.2.1 多张人脸检测实验

我们在imgs中准备了多张图片,其中人脸数量由少到多,我们尝试从少的人脸图片开始测试。先读取、处理和显示01.jpg图片


```python
test_img_muli = cv2.imread(os.path.join(IMGS_PATH, '01.jpg'))
imshow(test_img_muli)

png

可以发现01.jpg图中有四个非常帅气的男生,我们尝试是否能把他们几位都检测出来。

# 灰度化
test_img_muli_gray = cv2.cvtColor(test_img_muli, cv2.COLOR_BGR2GRAY)
imshow(test_img_muli_gray)

png

开始检测图片

current_time = time.time()
# 检测test_img_gray对象
faces = facesDetector.detectMultiScale(test_img_muli_gray, 1.3, 3)
# 计算检测用时
use_time = time.time() - current_time

查看输出检测结果

print("检测到的人脸数量: ", len(faces))
print("检测结果的形状: ",faces.shape)
print("人脸矩形框: ", faces)
print("检测用时: ", str(round(use_time, 5))+'秒')
检测到的人脸数量:  3
检测结果的形状:  (3, 4)
人脸矩形框:  [[123  83 114 114]
 [434 176 132 132]
 [162 311 129 129]]
检测用时:  0.07301

可以发现显示检测到了3个人脸,我们尝试绘制出所有的人脸框位置。

test_img_muli_draw = test_img_muli.copy()
for (x1, y1, w, h) in faces:
    x2 = x1 + w
    y2 = y1 + h
    cv2.rectangle(test_img_muli_draw, (x1, y1), (x2, y2), (200, 200,0), 3)
imshow(test_img_muli_draw)

png

可以发现,漏了一个人脸,是这么回事呢?我们可以尝试调整我们已经输入的参数,重新进行检测,例如我们设置的minSize为3,我们把他调小点,试试是否能成功检测到漏检的人脸。

current_time = time.time()
# 检测test_img_gray对象
faces = facesDetector.detectMultiScale(test_img_muli_gray, 1.3, 2)
# 计算检测用时
use_time = time.time() - current_time

print("检测到的人脸数量: ", len(faces))
print("检测结果的形状: ",faces.shape)
print("人脸矩形框: ", faces)
print("检测用时: ", str(round(use_time, 5))+'秒')

test_img_muli_draw = test_img_muli.copy()
for (x1, y1, w, h) in faces:
    x2 = x1 + w
    y2 = y1 + h
    cv2.rectangle(test_img_muli_draw, (x1, y1), (x2, y2), (200, 200,0), 3)
imshow(test_img_muli_draw)
检测到的人脸数量:  4
检测结果的形状:  (4, 4)
人脸矩形框:  [[325  50  97  97]
 [123  83 114 114]
 [434 176 132 132]
 [162 311 129 129]]
检测用时:  0.10057

png

可以发现,检测出4个人脸了,但是之前未检测到的那位人脸头部稍微侧头,偏移了一些角度,检测器框出的部分就不太准确,所以可以发现该检测器虽然速度快,但是只能应对一些头部姿态较为正的人脸。我们构建一个检测+绘制的函数faces_detection_cv2,接下去的测试会比较方便。

def faces_detection_cv2(facesDetector, target, scaleFactor=1.3, minSize=3, showSize=(12, 12)):
    # 图片灰度化
    target_gray = cv2.cvtColor(target, cv2.COLOR_BGR2GRAY)
    # 记录当前时间
    current_time = time.time()
    # 检测test_img_gray对象
    faces = facesDetector.detectMultiScale(target_gray, scaleFactor, minSize)
    # 计算检测用时
    use_time = time.time() - current_time
    # 复制一张原图用于绘制
    target_draw = target.copy()
    if (len(faces) != 0):

        print("检测到的人脸数量: ", len(faces))
        print("检测结果的形状: ",faces.shape)
        print("人脸矩形框: ", faces)
        print("检测用时: ", str(round(use_time, 5))+'秒')

        for (x1, y1, w, h) in faces:
            x2 = x1 + w
            y2 = y1 + h
            cv2.rectangle(target_draw, (x1, y1), (x2, y2), (200, 200,0), 2)
    else:
        print("未检测到人脸")

    imshow(target_draw, size=showSize)

我们继续对下一张人脸较多的02.jpg图片进行检测,查看效果如何。

test_img_muli_2 = cv2.imread(os.path.join(IMGS_PATH, '02.jpg'))
imshow(test_img_muli_2, size=(12, 12))

png

直接使用faces_detection_cv2检测图片中的人脸

faces_detection_cv2(facesDetector, test_img_muli_2, scaleFactor=1.1, minSize=1)
检测到的人脸数量:  8
检测结果的形状:  (8, 4)
人脸矩形框:  [[302 203  28  28]
 [224 207  32  32]
 [401 216  28  28]
 [488 218  28  28]
 [263 326  32  32]
 [151 319  38  38]
 [545 322  37  37]
 [438 321  39  39]]
检测用时:  0.21675

png

可以发现,本次检测漏检了一个人,图上可以发现,此人捂嘴后便不容易被检测器检测出来。继续尝试场景较为复杂的05.jpg图片,查看效果如何。

test_img_muli_3 = cv2.imread(os.path.join(IMGS_PATH, '05.jpg'))
imshow(test_img_muli_3, size=(12, 12))

png

faces_detection_cv2(facesDetector, test_img_muli_3, showSize=(13, 13), minSize=1, scaleFactor=1.2)
检测到的人脸数量:  16
检测结果的形状:  (16, 4)
人脸矩形框:  [[738 318  26  26]
 [578 332  27  27]
 [842 322  31  31]
 [644 375  28  28]
 [763 378  40  40]
 [851 378  22  22]
 [707 358  36  36]
 [883 357  39  39]
 [242 375  49  49]
 [313 382  48  48]
 [390 403  46  46]
 [822 432  41  41]
 [470 408  44  44]
 [198 410  55  55]
 [681 437  52  52]
 [767 552  61  61]]
检测用时:  0.28707

png

可以发现,图片中有非常多的人脸,但是仅可检测出16个人脸。

3.2.2 找出使用haarcascades存在的问题和局限性

我们在检测05.jpg的实验上,得到了比较差的结果,仅仅输出少部分人脸。现在我们对这些被检测到人脸进行剪切并分析,为什么这些人脸能被检测出来,他们和其他未被检测到的人脸有何不同。

# 读取和显示图片
test_img_muli_3 = cv2.imread(os.path.join(IMGS_PATH, '05.jpg'))
faces_detection_cv2(facesDetector, test_img_muli_3, showSize=(13, 13), minSize=1, scaleFactor=1.2)
检测到的人脸数量:  16
检测结果的形状:  (16, 4)
人脸矩形框:  [[738 318  26  26]
 [842 322  31  31]
 [578 332  27  27]
 [851 378  22  22]
 [644 375  28  28]
 [707 358  36  36]
 [763 378  40  40]
 [883 357  39  39]
 [390 403  46  46]
 [470 408  44  44]
 [242 375  49  49]
 [313 382  48  48]
 [198 410  55  55]
 [822 432  41  41]
 [681 437  52  52]
 [767 552  61  61]]
检测用时:  0.41683

png

我们创建crop_faces_imgs列表,收集以多维数组切片的形式剪切出人脸框的像素区域,利用前面小节定义的show_images函数显示出已切出的人脸

# 检测人脸,剪切并收集检测到的人脸集合
test_img_muli_3_gray = cv2.cvtColor(test_img_muli_3, cv2.COLOR_BGR2GRAY)
# 传递与检测函数一样的参数
faces = facesDetector.detectMultiScale(test_img_muli_3_gray, 1.2, 1)
crop_faces_imgs = []
for (x1, y1, w, h) in faces:
    x2 = x1 + w
    y2 = y1 + h
    # 以多维数组切片的形式剪切出人脸框的像素区域
    crop_faces_imgs.append(test_img_muli_3[int(y1):int(y2), int(x1):int(x2)])

# 显示结果
show_images(crop_faces_imgs)

png

可以发现,被检测出的人脸,都是头部姿态相对比较正,没有过多的偏移并且也没有过多的遮挡。接下来我们读取04.jpg尝试光照环境测试。

# 读取和显示图片
test_img_muli_4 = cv2.imread(os.path.join(IMGS_PATH, '04.jpg'))
faces_detection_cv2(facesDetector, test_img_muli_4, showSize=(5, 5),minSize=1)
未检测到人脸

png

可以发现,图中是一张比较昏暗的人像,检测器并没有检测出任何人脸。我们将图片的亮度和对比度进行调整。定义set_contrast函数调整图片的亮度和对比度。

def set_contrast(c, b):
    rows, cols, chunnel = test_img_muli_4.shape
    blank = np.zeros([rows, cols, chunnel], test_img_muli_4.dtype) 
    dst = cv2.addWeighted(test_img_muli_4, c, blank, 1-c, b)
    return dst

test_img_muli_4_fix = set_contrast(5, 12)
imshow(test_img_muli_4_fix, size=(6, 6))

png

修复好图片后,可以很轻易发现图片的中人脸。我们进行进行预测。

faces_detection_cv2(facesDetector, test_img_muli_4_fix, showSize=(6, 6))
检测到的人脸数量:  1
检测结果的形状:  (1, 4)
人脸矩形框:  [[ 74 108  83  83]]
检测用时:  0.02614

png

由上面的实验可以得知,在比较极端的光照环境下,也会影响到检测器的判断。所以,使用OpenCV的haarcascades进行人脸检测虽然速度相对比较快,使用用方便,但是却在人脸头部姿态和一些复杂环境下,例如光照、遮挡有着比较不稳定的效果。

4. 使用MTCNN进行人脸检测

MTCNN(Multi-task Cascaded Convolutional Networks)出自《Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Networks》这篇论文,有效的解决了目标检测中人脸检测算法存在的一些性能功耗上的问题。

4.1 MTCNN简单介绍

MTCNN被提出的论文中心思想是,提出多任务级联卷积的方式,主要包括PRO三个子网络——P-Net、R-Net、O-Net,三个stage采用由浅到深的方式对图像进行处理。可以将PRO三个子网络理解为三个由低到高的三个网络。先由P-Net大致的做分类,再由精度更高的R-Net做人脸选择框的定位,再由精度更加细致的O-Net做人脸关键点位置的回归。MTCNN输出层中,分别对应人脸分类(face classification)、候选框(bounding box regression)、人脸关键点(facial landmark localization)三个类别,如图3.5所示,人脸分类是一个二分类,人脸候选框则输出矩形的对角两坐标,输出4个数分别对应两组坐标:(x0,y0)和(x1,y1),而人脸关键点的输出分别对应着眼、鼻、嘴角五个关键点,输出10个数对应5组坐标。img2.png

4.2 MTCNN人脸检测器下载与安装

读者可以翻阅附录,获取下载mtcnnplug文件夹的方法。下载好文件夹后,将文件夹放置在项目根目录下的models目录下。如下结构所示:

project10/
├───demo10.ipynb
├───models/
  ├───mtcnn.mlz  <------------mtcnn.mlz放在这里
  └───haarcascades/ 
      ├──haarcascade_eye.xml
      ├──haarcascade_frontalcatface_extended.xml
      ├──haarcascade_frontalface_alt_tree.xml
      ├──haarcascade_frontalface_alt.xml
      ├──haarcascade_frontalface_alt2.xml
         ...
├───mtcnnplug/  <------------mtcnnplug放在这里
      ├──__init__.py
      ├──native.py
      └──core/
         ├──libface_python_ext.dll
         └──libface_python_ext.so
└───dataset/
    ├───imgs/
      ├──00.jpg
      ├──01.jpg
         ...
    └───person1000/
        ├──00.png
        ├──01.png
           ...

安放完毕后即可使用MTCNN的人脸检测器进行预测。

4.3 使用MTCNN人脸检测器进行实验

MTCNN的检测我们从多张人脸的实验开始。需要注意的是,我们文件夹中有两个动态链接库libface_python_ext.dll和libface_python_ext.so,其中libface_python_ext.dll适用于Windows用户使用,而libface_python_ext.so适用于Linux用户或MacOS用户使用,请读者根据自己的操作系统自行选择所需使用的动态链接库。

# 构建mtcnnplug目录
MTCNN_PLUG_PATH = os.path.join(ROOT, 'mtcnnplug')
# 构建mtcnn中的core目录
MTCNN_CORE_PATH = os.path.join(MTCNN_PLUG_PATH, 'core')
# 构建mtcnn中的model路径
MTCNN_MODEL = os.path.join(MODELS_PATH, 'mtcnn.mlz')
# 构建core目录下的动态链接库路径,根据系统选择不一样的动态链接库
MTCNN_LIBFACE = os.path.join(MTCNN_CORE_PATH, 'libface_python_ext.so')
# MTCNN_LIBFACE = os.path.join(MTCNN_CORE_PATH, 'libface_python_ext.dll')

构建目录地址和文件路径地址完成后,我们尝试读取和载入mtcnn人脸检测器模型。

from mtcnnplug.native import MtcnnFaceDetector

传入参数,构造一个MTCNN人脸检测器对象

mtcnnFaceDetector = MtcnnFaceDetector(MTCNN_LIBFACE, MTCNN_MODEL)

4.3.1 多张人脸进行预测

我们直接使用MTCNN人脸检测器尝试多个人脸的检测,依旧使用我们的02.jpg文件进行测试。

test_img_muli_2 = cv2.imread(os.path.join(IMGS_PATH, '02.jpg'))
imshow(test_img_muli_2, size=(12, 12))

png

使用我们构造好的检测器对test_img_muli_2进行检测,最小人脸尺寸为10,值得注意的是,这次不需要将图像灰度处理

current_time = time.time()
# 使用simpleDetection简单的识别一张图片,并返回出结果,最小人脸尺寸为10
bboxes = mtcnnFaceDetector.simpleDetection(test_img_muli_2, minSizeImage=10)
# 计算检测用时
use_time = time.time() - current_time
print("检测到的人脸数量: ", len(bboxes))
print("检测结果的形状: ", bboxes.shape)
print("检测用时: ", str(round(use_time, 5)) + '秒')
检测到的人脸数量:  9
检测结果的形状:  (9, 15)
检测用时:  0.41989

可以发现,使用mtcnn返回出来的结果的形状和之前的haarcascades的检测结果是不一样的。我们将其中第一个形状输出。

bboxes[0], bboxes[0].shape
(array([  1.     , 404.     , 217.     , 427.     , 244.     , 410.0276 ,
        228.12885, 420.4683 , 227.67918, 414.61334, 232.27853, 410.79736,
        238.83467, 419.70242, 238.54521], dtype=float32), (15,))

可以发现,输出的结果是15个浮点数,其中索引为0的第一个数为检测结果的置信度,通常我们会设置一个阈值(threshold)对该数值进行一个约束,通俗的讲,通过该数可以判断检测器对这项结果是否是一个人脸的自我评分,而阈值便是给定的评分标准,阈值在0到1之间浮动。而索引1到4这个4个数分别代表矩形框的(x1, y1)和(x2, y2)两个坐标点。剩下的5到15这10个数分别代表mtcnn的5个关键点位置的x和y坐标数。我们使用可视化函数,将其结果绘制出来。

# 复制一个对象
test_img_muli_2_draw = test_img_muli_2.copy()
# 绘制
for bbox in bboxes:
    x1, y1, x2, y2 = bbox[1:5]
    # 绘制人脸矩形框
    cv2.rectangle(test_img_muli_2_draw, (x1, y1), (x2, y2), (200, 200,0), 2)
    # 绘制人脸五个关键点
    for i in range(5, 15, 2):
        cv2.circle(test_img_muli_2_draw, (int(bbox[i + 0]), int(bbox[i + 1])), 2, (200, 200,0))

imshow(test_img_muli_2_draw, size=(12, 12))

png

可以看见检测器可以将图片中9个人的人脸位置均检测出来,并将所有人脸的位置和关键点准确的标定好,用时也不过1秒钟。我们将本次实验所使用的函数构造成函数faces_detection_mtcnn函数,方便下面的实验实验。

def faces_detection_mtcnn(mtcnnFaceDetector, target, minSize=10, bdsize=2):
    # 记录当前时间
    current_time = time.time()
    # 检测test_img_gray对象
    bboxes = mtcnnFaceDetector.simpleDetection(target, minSizeImage=10)
    # 计算检测用时
    use_time = time.time() - current_time
    # 复制一张原图用于绘制
    target_draw = target.copy()
    if (len(bboxes) != 0):
        print("检测到的人脸数量: ", len(bboxes))
        print("检测结果的形状: ",bboxes.shape)
        print("检测用时: ", str(round(use_time, 5))+'秒')

        # 绘制
        for bbox in bboxes:
            x1, y1, x2, y2 = bbox[1:5]
            # 绘制人脸矩形框
            cv2.rectangle(target_draw, (x1, y1), (x2, y2), (200, 200,0), bdsize)
            # 绘制人脸五个关键点
            for i in range(5, 15, 2):
                cv2.circle(target_draw, (int(bbox[i + 0]), int(bbox[i + 1])), bdsize, (200, 200,0), -1)
    else:
        print("未检测到人脸")

    return target_draw

4.3.2 复杂场景检测

我们使用上小节使用过的05.jpg文件,对相对复杂的场景进行人脸检测。

test_img_muli_3 = cv2.imread(os.path.join(IMGS_PATH, '05.jpg'))
imshow(test_img_muli_3, size=(12, 12))

png

使用mtcnn对test_img_muli_3进行预测,并显示结果

res_3 = faces_detection_mtcnn(mtcnnFaceDetector, test_img_muli_3)
imshow(res_3, size=(12, 12))
检测到的人脸数量:  65
检测结果的形状:  (65, 15)
检测用时:  1.33573

png

可以看到,用时1.3秒左右,共检测出人脸65张,相比之前的haarcascades人脸检测器,结果优秀了太多,在复杂环境下的大转角头部姿态的人脸提取效果有明显的提升。

4.3.3 昏暗场景检测

我们选择较为昏暗的04.jpg图片进行昏暗测试,我们在不对照片进行任何手段的增强情况下例如曝光调整、对比度和亮度调整等手段使用mtcnn人脸检测器进行检测。

# 读取和显示图片
test_img_muli_4 = cv2.imread(os.path.join(IMGS_PATH, '04.jpg'))
imshow(test_img_muli_4)

png

使用MTCNN进行检测

res_4 = faces_detection_mtcnn(mtcnnFaceDetector, test_img_muli_4)
imshow(res_4, size=(9, 9))
检测到的人脸数量:  1
检测结果的形状:  (1, 15)
检测用时:  0.10095

png

可以发现,使用mtcnn用时不到0.1秒便可在昏暗的环境下检测出人脸的位置和精确的标出关键点位置。

4.3.4 大型合照测试

我们读取03.jpg,这是imgs文件夹中,单张人脸数量最多的一张图片,我们拿它来进行测试,看看mtcnn的检测结果范围可覆盖原图中多少人脸。

# 读取和显示图片
test_img_muli_5 = cv2.imread(os.path.join(IMGS_PATH, '03.jpg'))
imshow(test_img_muli_5, size=(25, 25))

png

使用MTCNN进行检测

res_5 = faces_detection_mtcnn(mtcnnFaceDetector, test_img_muli_5, minSize=10)
imshow(res_5, size=(25, 25))
检测到的人脸数量:  258
检测结果的形状:  (258, 15)
检测用时:  3.89879

png

用时3秒多,虽然时间有点长,但是可以检测出258张人脸,这可能是haarcascades的人脸检测无法达到的成绩。

4.3.5 损坏图像/遮挡检测

有时候,在一些特殊的场景,可能会因为图像上的人脸被遮挡或者人脸区域图像损坏,未能被检测器检测出来,所以我们用mtcnn人脸检测器对受损图像的人脸进行检测。首先我们需要导入图像06.jpg,并使用opencv对图像进行处理,创建遮挡区域,将人脸进行遮挡后,对被破坏的人脸图层进行检测,观察检测器是否能依旧完成任务。

test_image_oringin = cv2.imread(os.path.join(IMGS_PATH, '06.jpg'))
test_image_pollution = test_image_oringin.copy()
# 建立遮挡区
p_w = 30
p_h = 80
s_y = int(test_image_pollution.shape[0]/2) - 150
s_x = int(test_image_pollution.shape[1]/2) - 30
pollution = np.zeros((p_w, p_h, 3), np.uint8)
test_image_pollution[s_x : s_x + p_w, s_y : s_y + p_h] = pollution

显示原图和背遮挡的图像

# 间隔条
def border_axis_1(img, size=5):
    border = np.zeros((img.shape[0], size, 3), np.uint8)
    return border

origin_and_pollution = np.concatenate([test_image_oringin, border_axis_1(test_image_oringin), test_image_pollution], axis=1)
imshow(origin_and_pollution, size=(10, 10))

png

对已遮挡图像test_image_pollution进行人脸检测后,显示所有图像的对比图。

res_p = faces_detection_mtcnn(mtcnnFaceDetector, test_image_pollution, bdsize=3)
res_concat = np.concatenate([origin_and_pollution, border_axis_1(test_image_oringin), res_p], axis=1)
imshow(res_concat, size=(13, 13))
检测到的人脸数量:  1
检测结果的形状:  (1, 15)
检测用时:  0.14965

png

可以发现,就算是被人脸图像被破坏,mtcnn依旧能找出人脸的位置。

4.3.6 对person1000进行随机检测

构建预测函数,默认随机筛选30张照片进行检测,并将人脸全部剪切出来,并排列绘制出结果。

def pre_images(dir_path, num=30, minsize=30):
    """输入文件夹地址进行批量预测,默认提取出30张人脸,
    最后返回出检测结果的一组Face类的列表"""
    imgs = []
    all_face = []
    for parent, dirnames, filenames in os.walk(dir_path):
        imgs = filenames
    # 随机不重复的挑选num张照片
    test_files_name = random.sample(range(0, len(imgs)), num - 1)
    test_img = [imgs[index] for index in test_files_name]
    for test_file in test_img:
        path = os.path.join(dir_path, test_file)
        target = cv2.imread(path)
        # 使用mtcnn进行人脸检测和关键点检测
        bboxes = mtcnnFaceDetector.simpleDetection(target, minSizeImage=minsize)
        # 绘制
        for bbox in bboxes:
            x1, y1, x2, y2 = bbox[1:5]
            bdsize = int(target[int(y1):int(y2), int(x1):int(x2)].shape[0] / 40)
            # 绘制人脸矩形框
            cv2.rectangle(target, (x1, y1), (x2, y2), (200, 200,0), bdsize)
            # 绘制人脸五个关键点
            for i in range(5, 15, 2):
                cv2.circle(target, (int(bbox[i + 0]), int(bbox[i + 1])), bdsize, (200, 200,0), -1)

            face = target[int(y1):int(y2), int(x1):int(x2)]
            all_face.append(face)

    return all_face

检测并绘制出裁剪结果。

res = pre_images(PERSON1000_PATH)
show_images(res)

png

结论

本章详细的阐述了两种常用的人脸检测器的使用,两种检测器在速度和性能方面各有千秋,请读者根据自身需求选择使用。

版权声明:如无特殊说明,文章均为本站原创,转载请注明出处

本文链接:http://tunm.top/article/learning_10/