干货 | 史上最全 OpenCV 活体检测教程!

2019-05-08 16:50:41 浏览数 (1)

AI 科技评论按:本文来自著名的计算机视觉教学网站「pyimagesearch」,文章作者为 Adrian Rosebrock。在本文中,Adrian 将就「如何鉴别图像/视频中的真实人脸和伪造人脸」这一问题进行深入的分析,并介绍使用基于 OpenCV 的模型进行活体检测的具体方法。雷锋网 AI 科技评论编译如下。

本教程将教授你如何使用 OpenCV 进行活性检测。通过学习,你将能够在人脸识别系统中创建一个可以发现伪造人脸并执行反人脸欺骗的活体检测器。

在过去一年中,本文作者已经写过了多篇人脸识别教程,包括:

  • 基于 OpenCV 的人脸识别(阅读地址:https://www.pyimagesearch.com/2018/09/24/opencv-face-recognition/)
  • 使用 dlib、Python 和深度学习进行人脸识别(阅读地址:https://www.pyimagesearch.com/2018/06/18/face-recognition-with-opencv-python-and-deep-learning/)
  • 基于树莓派的人脸识别(阅读地址:https://www.pyimagesearch.com/2018/06/25/raspberry-pi-face-recognition/)

然而,在我收到的一些邮件以及这些关于人脸识别的博文的评论中,我经常被问到的一个问题是:

我如何才能区分真人脸和伪造人脸?

试想一下,如果一个居心叵测的用户有目的地试图骗过你的人脸识别系统,会发生什么?

他们可能会尝试向执行人脸识别的摄像头展示一张照片或一段视频(例如在本文顶部的图像)。而这张照片和视频中的人并不是这些用户本人。

在这种情况下,人脸识别系统完全有可能把展示到它们面前的图片识别为正确的人脸,这最终会导致未授权的用户躲过人脸识别系统!

如何才能区分出这些「伪造的」与「真实的/合法的」人脸呢? 如何才能将反人脸欺骗算法应用到你的人脸识别应用中呢?

答案是使用 OpenCV 进行活体检测,这正是本文要讨论的内容。

那么,如何将基于 OpenCV 的活体检测功能结合到你自己的人脸识别系统中呢?本文接下来将给出答案。

基于 OpenCV 的活体检测

在本教程的第一部分,我们将对活体检测进行讨论,包括「活体检测是什么?」以及「为什么我们需要活体检测来改进人脸识别系统?」

在这里,我们首先回顾一下接下来将用来进行活体检测的数据集,内容包括:

  • 如何构建用于活体检测的数据集
  • 我们的真假人脸的图像示例

我们还将回顾针对活体检测器项目的项目结构。

我们将训练一个能够区分真人脸和伪造人脸的深度神经网络,来创建活体检测器。

因此,我们需要:

1. 构建图像数据集 2. 实现一个能够进行活体检测的卷积神经网络(我们称之为「LivenessNet」) 3. 训练活体检测网络 4. 创建一个能够使用我们训练好的活体检测模型并将其应用于实时视频的 Python OpenCV 的脚本

接下来,让我们进入正题吧!

什么是活体检测, 我们为什么需要它?

图 1:基于 OpenCV 的活体检测。左图是一个我的活体(真人)的视频,而在右图中可以看到我正拿着我的 iPhone(屏幕上有伪造人脸/用来欺骗人脸识别系统的人脸图片)。

人脸识别系统正变得越来越普及。从 iphone / 智能手机上的人脸识别系统,到中国用来进行大规模监控的人脸识别系统——人脸识别变得无处不在。

然而,人脸识别系统却很容易被「具有欺骗性的」和「不真实」的人脸所愚弄。

通过将一个人的照片(无论是打印出来的,或者是显示在智能手机屏幕上的,等等)展示给用于人脸识别的摄像头,可以很容易地骗过人脸识别系统。

为了使人脸识别系统变得更加安全,我们需要检测出这种伪造的/不真实的人脸——而「活体检测」就是被用来指代这种算法的术语。

活体检测的方法有很多,包括:

  • 纹理分析,包括在人脸区域上计算局部二值模式(LBP,https://www.pyimagesearch.com/2015/12/07/local-binary-patterns-with-python-opencv/)和使用支持向量机(SVM)将人脸分类为真实的或伪造的。

频率分析,如查看人脸图片的傅里叶域(对其进行傅里叶变换)。

  • 变量聚焦分析,例如查看两个连续帧之间像素值的变化。
  • 基于启发式的算法,包括眼球运动、嘴唇运动和眨眼检测(https://www.pyimagesearch.com/2017/04/24/eye-blink-detection-opencv-python-dlib/)。这类算法试图跟踪眼球运动和眨眼,以确保用户展示的并非另一个人的照片(因为照片不会眨眼或移动嘴唇)。
  • 光流算法,即查看由三维物体和二维平面产生的光流的差异和特性。
  • 三维人脸形状,类似于苹果 iPhone 所使用的人脸识别系统,使人脸识别系统能够区分真实的人脸和打印出来 / 照片中的 / 图像中的另一个人的人脸。
  • 将上述方法结合起来,使人脸识别系统工程师能够选择适合其特定应用程序的活体检测模型。

在 Chakraborty 和 Das 于 2014 年发表的论文「An Overview of Face liveness Detection」(论文阅读地址:https://arxiv.org/pdf/1405.2227.pdf)中,对活体检测算法进行了全面的回顾。

在本文的教程中,我们将活体检测作为一个二分类问题来看待。

给定一张输入图像,我们将训练一个能够识别出真假人脸的卷积神经网络。

在开始训练我们的活体检测模型前, 我们先查看一下使用的数据集。

我们的活体检测视频

图 2:一个收集到的真实人脸和伪造/欺骗性人脸的例子。左边的视频是一个我的人脸的合法录像。右边是我的笔记本电脑将左边的这段视频录下来的视频。

为了简化我们的例子,我们在本文中构建的活体检测器将重点关注区分屏幕上的真实人脸和欺骗性的人脸。

该算法可以很被容易地扩展到其他类型的欺骗性人脸中,包括打印机或高分辨率打印出来的人脸等。

为了构建活体检测数据集,我将:

1. 使用我的 iPhone,把它调成人像 / 自拍模式。 2. 录制一段大约 25 秒的我自己在办公室内走来走去的视频。 3. 将我的 iPhone 朝向我的桌面重放这个 25 秒的视频,我从桌面的角度录制了这段重放的视频; 4. 这会产生两个示例视频。一个被用作「真实」人脸,另一个被用作「伪造/欺骗性」人脸。 5. 最终,我将人脸检测技术同时应用在了这两组视频上,以提取两类人脸各自的 ROI。

我在本文的「下载」部分为大家提供了真实人脸视频和伪造人脸视频。

你可以直接使用这些视频开始构建数据集,但是我建议你收集更多的数据,从而帮助提升你的活体检测器的鲁棒性和准确率。

通过测试,我确认这个模型会偏向于检测出我自己的人脸——因为所有的模型都是使用我自己的视频训练的,因此这个模型依旧是有意义的。此外,由于我是白种人,我并不奢望该数据集被用于检测其它肤色的人脸时有同样出色的表现。

理想情况下,你可以使用包含多个种族的人脸的数据来训练一个模型。如果读者想要获得更多关于改进活体检测模型的建议,请务必参考下面的「局限性和进一步工作」章节。

在接下来的教程中,你将学习到如何利用我记录下来的数据集,并使用 OpenCV 和深度学习技术得到一个真正的活体检测器。

项目架构

在继续阅读的过程中,读者可以使用「下载」部分提供的链接获取代码、数据集以及活体检测模型,并解压存档。

当你导航到项目的目录时,你会注意到如下所示的架构:

$ tree --dirsfirst --filelimit 10 ├── dataset │ ├── fake [150 entries] │ └── real [161 entries] ├── face_detector │ ├── deploy.prototxt │ └── res10_300x300_ssd_iter_140000.caffemodel ├── pyimagesearch │ ├── __init__.py │ └── livenessnet.py ├── videos │ ├── fake.mp4 │ └── real.mov ├── gather_examples.py ├── train_liveness.py ├── liveness_demo.py ├── le.pickle ├── liveness.model └── plot.png 6 directories, 12 files

在我们的项目中有四个主要的目录:

  • 「dataset/」:我们的数据集目录包含两类图像:

(1)摄像头对着正在播放我的人脸视频的手机屏幕所拍下的伪造图像。 (2)我用手机自拍的视频中的真实图像。

  • 「face_detector/ 」:包括我们用来定位人脸 ROI 的、预训练好的 Caffe 人脸检测器。
  • 「pyimagesearch/」:该模块包含我们的 LivenessNet 类。
  • 「videos/」:我提供了两个用来训练 LivenessNet 分类器的输入视频。

现在,我们将仔细回顾三个 Python 脚本。在本文的最后,你可以在自己的数据和输入视频上运行这些脚本。按照在本教程中出现的顺序,这三个脚本依次是:

1. 「gather_examples.py」:该脚本从输入的视频文件中抓取人脸的 ROI,并帮助我们创建一个深度学习人脸活体检测数据集。

2.「train_liveness.py」: 如文件名所示,该脚本将训练我们的 LivenessNet 分类器。 我们将使用 Keras 和 TensorFlow 来训练模型。训练的过程会产生以下几个文件:

(1)le.pickle:我们的类标签编码器。 (2)liveness.model:我们用来训练人脸活体检测器的一系列 Keras 模型。 (3)plot.png:训练历史示意图显示出了模型的准确率和损失曲线,我们可以根据它评估我们的模型(即过拟合 / 欠拟合)。

3.「liveness_demo.py」:我们的演示脚本将启动你的网络摄像头拍下视频帧,来进行实时人脸活体检测。

从我们的训练(视频)数据集中检测并提取人脸的 ROI

图 3:为了建立一个活体检测数据集,首先需要检测出视频中的人脸 ROI 区域

现在我们可以回顾一下我们初始化的数据集和项目架构,让我们看看如何从输入的视频中提取真实和伪造的人脸图像。

这个脚本的最终目标是向两个目录中填充数据:

1. 「dataset/fake/」:包含「fake.mp4」文件中的人脸 ROI 区域。 2. 「dataset/real/」:包含「real.mov」文件中的人脸 ROI 区域。

基于这些帧,我们接下来将在图像上训练一个基于深度学习的活体检测器。

打开「gather_examples.py」文件并插入下列代码:

# import the necessary packages import numpy as np import argparse import cv2 import os # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-i", "--input", type=str, required=True, help="path to input video") ap.add_argument("-o", "--output", type=str, required=True, help="path to output directory of cropped faces") ap.add_argument("-d", "--detector", type=str, required=True, help="path to OpenCV's deep learning face detector") ap.add_argument("-c", "--confidence", type=float, default=0.5, help="minimum probability to filter weak detections") ap.add_argument("-s", "--skip", type=int, default=16, help="# of frames to skip before applying face detection") args = vars(ap.parse_args())

第 2-5 行引入了我们需要的安装包。除了内置的 Python 模块,该脚本仅仅需要用到 OpenCV 和 NumPy 。

第 8-19 行代码将解析我们的命令行参数(https://www.pyimagesearch.com/2018/03/12/python-argparse-command-line-arguments/):

  • 「--input」:我们的输入视频文件的路径。
  • 「--output」:储存每个裁剪出来的人脸图像的输出目录的路径。
  • 「--detector」:人脸检测器的路径。我们将使用 OpenCV 的深度学习人脸检测器(https://www.pyimagesearch.com/2018/02/26/face-detection-with-opencv-and-deep-learning/)。为了方便读者使用,Caffe 模型可在本文的「下载」部分获得。
  • 「--confidence」:过滤弱人脸检测结果的最小概率。默认情况下,该值为 50%。
  • 「--skip」:我们不需要检测并存储每个图像,因为相邻的帧是相似的。相反我们会在两次人脸检测任务之间跳过 N 个帧。你可以使用此参数修改默认的 N 值(16)。

让我们继续加载人脸检测器并初始化我们的视频流:

# load our serialized face detector from disk print("[INFO] loading face detector...") protoPath = os.path.sep.join([args["detector"], "deploy.prototxt"]) modelPath = os.path.sep.join([args["detector"], "res10_300x300_ssd_iter_140000.caffemodel"]) net = cv2.dnn.readNetFromCaffe(protoPath, modelPath) # open a pointer to the video file stream and initialize the total # number of frames read and saved thus far vs = cv2.VideoCapture(args["input"]) read = 0 saved = 0

第 23-26 行加载了 OpenCV 的深度学习人脸检测器(相关阅读:https://www.pyimagesearch.com/2018/02/26/face-detection-with-opencv-and-deep-learning/)。

之后,我们在第 30 行打开了我们的视频流。

我们还在循环执行时,为读取到的帧数以及保存下来的帧数初始化了两个变量(第 31 和第 32 行)。

接下来,我们将创建一个循环来处理这些帧:

# loop over frames from the video file stream while True: # grab the frame from the file (grabbed, frame) = vs.read() # if the frame was not grabbed, then we have reached the end # of the stream if not grabbed: break # increment the total number of frames read thus far read = 1 # check to see if we should process this frame if read % args["skip"] != 0: continue

我们在第 35 行开始「while」循环。

接着,我们抓取到了一个帧并对其进行验证(第 37-42 行)。

这时,由于我们已经读取了一个帧,我们将增加「read」计数器的计数(第 48 行)。如果需要跳过这个特定的帧,我们将不做任何操作继续进入下一轮循环(第 48 和 49 行)。

让我们继续检测人脸:

# grab the frame dimensions and construct a blob from the frame (h, w) = frame.shape[:2] blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0)) # pass the blob through the network and obtain the detections and # predictions net.setInput(blob) detections = net.forward() # ensure at least one face was found if len(detections) > 0: # we're making the assumption that each image has only ONE # face, so find the bounding box with the largest probability i = np.argmax(detections[0, 0, :, 2]) confidence = detections[0, 0, i, 2]

为了执行人脸检测,我们需要根据图像创建一个二进制大对象数据(blob)(第 53 和 54 行)。为了适应于 Caffe 人脸检测器,这个「blob」的宽、高为 300*300。稍后还需要对边界框进行放缩,因此在第 52 行中,我们会抓取到帧的维度。

第 58 和 59 行通过深度学习人脸检测器前馈传递这个「blob」。

我们的脚本假设在视频的每一帧中只出现了一张人脸(第 62-65 行),这有助于防止假正例的产生。如果你正在处理包含多个面孔的视频,我建议你相应地调整逻辑。

因此,第 65 行获取到了最高的人脸检测索引的概率。第 66 行使用该索引提取出检测的置信度(confidence)。

接下来,让我们过滤掉弱人脸检测结果并将人脸的 ROI 写入磁盘:

# ensure that the detection with the largest probability also # means our minimum probability test (thus helping filter out # weak detections) if confidence > args["confidence"]: # compute the (x, y)-coordinates of the bounding box for # the face and extract the face ROI box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (startX, startY, endX, endY) = box.astype("int") face = frame[startY:endY, startX:endX] # write the frame to disk p = os.path.sep.join([args["output"], "{}.png".format(saved)]) cv2.imwrite(p, face) saved = 1 print("[INFO] saved {} to disk".format(p)) # do a bit of cleanup vs.release() cv2.destroyAllWindows()

第 71 行保证了我们的人脸检测 ROI 能够满足减少假正例所需要的最小阈值。

在此基础上,我们提取出了人脸的 ROI 边界框「box」坐标以及相应的人脸 ROI 数值(第 74-76 行)。

我们为人脸 ROI 生成了一个「路径 文件名」,并在第 79-81 行中将其写入了磁盘。在这里,我们可以增加「saved」人的数量。

处理完成后,我们将在第 86 和 87 行中清空 OpenCV 的工作内存。

构建我们的活体检测图片数据集

图 4:我们的 OpenCV 人脸活体检测数据集。我们将使用 Keras 和 OpenCV 训练一个活体检测模型的演示样例。

请确保你使用了本教程「下载」部分的链接获取到了源代码以及输入视频的示例。

在此基础上,请打开一个终端并执行下面的命令,从而提取出我们的「伪造/欺骗性」的类别所需要的人脸图像:

$ python gather_examples.py --input videos/real.mov --output dataset/real --detector face_detector --skip 1 [INFO] loading face detector... [INFO] saved datasets/fake/0.png to disk [INFO] saved datasets/fake/1.png to disk [INFO] saved datasets/fake/2.png to disk [INFO] saved datasets/fake/3.png to disk [INFO] saved datasets/fake/4.png to disk [INFO] saved datasets/fake/5.png to disk ... [INFO] saved datasets/fake/145.png to disk [INFO] saved datasets/fake/146.png to disk [INFO] saved datasets/fake/147.png to disk [INFO] saved datasets/fake/148.png to disk [INFO] saved datasets/fake/149.png to disk

类似地,我们可以为获取「真实」类别的人脸图像进行相同的操作:

$ python gather_examples.py --input videos/fake.mov --output dataset/fake --detector face_detector --skip 4 [INFO] loading face detector... [INFO] saved datasets/real/0.png to disk [INFO] saved datasets/real/1.png to disk [INFO] saved datasets/real/2.png to disk [INFO] saved datasets/real/3.png to disk [INFO] saved datasets/real/4.png to disk ... [INFO] saved datasets/real/156.png to disk [INFO] saved datasets/real/157.png to disk [INFO] saved datasets/real/158.png to disk [INFO] saved datasets/real/159.png to disk [INFO] saved datasets/real/160.png to disk

由于「真实」的人脸视频比「伪造」的人脸视频文件要更长一些,我们将使用更长的跳帧值来平衡每一类输出的人脸 ROI 的数量。

在执行了上面的脚本后,你应该已经获得了如下所示的图像数据:

  • 伪造人脸:150 张图像
  • 真实人脸:161 张图像
  • 总计:311 张图像

实现我们的深度学习活体检测器「LivenessNet」

图 5:LivenessNet 的深度学习架构,这是一个被设计用来在图像和视频中检测出活体人脸的卷积神经网络(CNN)。

接下来,我们将实现基于深度学习技术的活体检测器「LivenessNet」。

实质上,「LivenessNet」就是一个简单的卷积神经网络。

出于以下两点原因,我们将刻意让该网络保持尽可能浅的层数和尽可能少的参数:

1. 为了降低在我们的小型数据集上发生过拟合现象的可能性。 2. 为了确保我们的活体检测器能够快速、实时运行(即使在诸如树莓派等计算资源受限的设备上)。

现在,我们将开始实现「LivenessNet」,请打开「livenessnet.py」文件并插入下列代码:

# import the necessary packages from keras.models import Sequential from keras.layers.normalization import BatchNormalization from keras.layers.convolutional import Conv2D from keras.layers.convolutional import MaxPooling2D from keras.layers.core import Activation from keras.layers.core import Flatten from keras.layers.core import Dropout from keras.layers.core import Dense from keras import backend as K class LivenessNet: @staticmethod def build(width, height, depth, classes): # initialize the model along with the input shape to be # "channels last" and the channels dimension itself model = Sequential() inputShape = (height, width, depth) chanDim = -1 # if we are using "channels first", update the input shape # and channels dimension if K.image_data_format() == "channels_first": inputShape = (depth, height, width) chanDim = 1

我们将从 Keras 包中引入所有需要的方法(第 2-10 行)。如果你想要进一步了解每一个网络层和函数,请参阅「 Deep Learning for Computer Vision with Python」(网址为:https://www.pyimagesearch.com/deep-learning-computer-vision-python-book/)。

我们在第 12 行开始定义「LivenessNet」类。它包含一个静态的构造方法「build」(第 14 行)。「build」方法将接受四个输入参数:

  • 「width」:图像/视频的宽度
  • 「height」:图像的高度
  • 「depth」:图像的通道数(由于我们将使用 RGB 图像,本例中 depth 为 3)。
  • 「classes」:检测类别的数目。我们共有两类输出人脸:「真实」人脸和「伪造」人脸。

我们在第 17 行将「model」初始化。

第 18 行定义了模型的「inputShape」,而第 23-25 行指定了通道的顺序。

下面我们将向卷积神经网络中加入一些网络层组件:

# first CONV => RELU => CONV => RELU => POOL layer set model.add(Conv2D(16, (3, 3), padding="same", input_shape=inputShape)) model.add(Activation("relu")) model.add(BatchNormalization(axis=chanDim)) model.add(Conv2D(16, (3, 3), padding="same")) model.add(Activation("relu")) model.add(BatchNormalization(axis=chanDim)) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) # second CONV => RELU => CONV => RELU => POOL layer set model.add(Conv2D(32, (3, 3), padding="same")) model.add(Activation("relu")) model.add(BatchNormalization(axis=chanDim)) model.add(Conv2D(32, (3, 3), padding="same")) model.add(Activation("relu")) model.add(BatchNormalization(axis=chanDim)) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25))

我们的卷积神经网路表现出了 VGGNet 的一些特性。它非常浅,只有少许几个学习到的卷积核。理想情况下,我们并不需要用通过一个深度网络来区分真实人脸和伪造人脸。

第 28-36 行指定了第一个「CONV => RELU => CONV => RELU => POOL」(卷积层=> RELU 激活函数层=>卷积层=>RELU 激活函数层=>池化层)网络层集合,其中也加入了批量归一化层和 dropout 层。

第 39-46 行加上了另一个「CONV => RELU => CONV => RELU => POOL」(卷积层=>RELU 激活函数层=>卷积层=>RELU 激活函数层=>池化层)网络层集合。

最后加入我们的「FC => RELU」(全连接=>RELU 激活函数层)层:

# first (and only) set of FC => RELU layers model.add(Flatten()) model.add(Dense(64)) model.add(Activation("relu")) model.add(BatchNormalization()) model.add(Dropout(0.5)) # softmax classifier model.add(Dense(classes)) model.add(Activation("softmax")) # return the constructed network architecture return model

第 49-57 行由全连接层和 ReLU 激活函数层以及一个 softmax 分类器的头组成。

在第 60 行中将该模型返回给训练脚本。

创建活体检测器的训练脚本

图 6:训练 LivenessNet 的处理流程。同时使用「真实」人脸和「欺骗性/伪造」人脸图像作为我们的数据集,我们可以使用 OpenCV、Keras 框架以及深度学习技术训练一个活体检测模型。

给定我们的真实/欺骗性人脸图像数据集以及对 LivenessNet 实现,我们现在已经做好了训练该网络的准备:

请打开「train_liveness.py」文件并插入下列代码:

# set the matplotlib backend so figures can be saved in the background import matplotlib matplotlib.use("Agg") # import the necessary packages from pyimagesearch.livenessnet import LivenessNet from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from keras.preprocessing.image import ImageDataGenerator from keras.optimizers import Adam from keras.utils import np_utils from imutils import paths import matplotlib.pyplot as plt import numpy as np import argparse import pickle import cv2 import os # construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-d", "--dataset", required=True, help="path to input dataset") ap.add_argument("-m", "--model", type=str, required=True, help="path to trained model") ap.add_argument("-l", "--le", type=str, required=True, help="path to label encoder") ap.add_argument("-p", "--plot", type=str, default="plot.png", help="path to output loss/accuracy plot") args = vars(ap.parse_args())

我们的人脸活体检测脚本引入了大量的输入(第 2-19 行)。它们分别是:

  • 「matplotlib」:用以生成训练情况示意图。在第 3 行中,我们将后端指定为「Agg」,从而使我们可以很容易地将示意图写入磁盘。

「LivenessNet」:我们在前面的章节定义过的活体检测卷积神经网络。

  • 「train_test_split」:从 scikit-learn 引入的函数,它构造了用于训练和测试的数据划分。
  • 「classification_report」:同样从 scikit-learn 引入的函数,它将生成一个描述我们模型性能的简短的统计报告。
  • 「ImageDataGenerator」:用于进行数据增强,向我们提供批量的随机突变增强的图像。
  • 「Adam」:一个非常适用于该模型的优化器(可选方案包括随机梯度下降算法(SGD)、RMSprop 等)。
  • 「paths」:从我的「imutils」包中引入,该模块可以帮助我们收集磁盘上所有图像文件的路径。
  • 「pyplot」:用来生成一个精美的训练过程示意图。
  • 「numpy」:一个用于 Python 的数字处理开发库,它也是 OpenCV 的所需要的。
  • 「argparse」:用于处理命令行参数(相关阅读:https://www.pyimagesearch.com/2018/03/12/python-argparse-command-line-arguments/)
  • 「pickle」:被用来将我们的标签编码器序列化,从而写入磁盘。
  • 「cv2」:用于绑定 OpenCV。
  • 「os」:该模块有很多功能,但在这里我们仅使用它来创建操作系统路径分隔符。

输入似乎有点多,但是在了解这些输入的目的后,检查脚本的其余部分就应该更加直观了。

该脚本接受四个命令行参数:

  • 「--dataset」:输入数据集的路径。本文前面通过「gather_examples.py」脚本创建了该数据集。
  • 「--model」:我们的脚本会生成一个输出模型文件,通过该参数指定其路径。
  • 「--le」:输出序列化标签编码器文件所需要提供的路径。
  • 「--plot」:训练脚本将生成一个训练过程示意图。如果你想重写「plot.png」的缺省值,你应该在命令行中指定该值。

下面的代码块将执行一系列初始化工作,并创建我们的数据:

# initialize the initial learning rate, batch size, and number of # epochs to train for INIT_LR = 1e-4 BS = 8 EPOCHS = 50 # grab the list of images in our dataset directory, then initialize # the list of data (i.e., images) and class images print("[INFO] loading images...") imagePaths = list(paths.list_images(args["dataset"])) data = [] labels = [] for imagePath in imagePaths: # extract the class label from the filename, load the image and # resize it to be a fixed 32x32 pixels, ignoring aspect ratio label = imagePath.split(os.path.sep)[-2] image = cv2.imread(imagePath) image = cv2.resize(image, (32, 32)) # update the data and labels lists, respectively data.append(image) labels.append(label) # convert the data into a NumPy array, then preprocess it by scaling # all pixel intensities to the range [0, 1] data = np.array(data, dtype="float") / 255.0

在第 35-37 行设置的训练参数包括学习率、批处理规模以及迭代的次数。

到这里,我们就获取到了「iamgePaths」中的路径,同时也初始化了两个用来存放「data」和类「labels」的列表(第 42-44 行)。

第 46-55 行的循环创建好了「data」和「labels」列表。「data」列表由我们加载并重新调整尺寸到 32*32 像素的图像组成。而每张图像则都有一个对应的标签被存储在「labels」列表中。

每个像素的亮度都被放缩到了 [0,1] 区间内,我们在第 59 行将列表转换成了 NumPy array 的形式。

接下来,我们将对标签进行编码并对数据进行划分:

# encode the labels (which are currently strings) as integers and then # one-hot encode them le = LabelEncoder() labels = le.fit_transform(labels) labels = np_utils.to_categorical(labels, 2) # partition the data into training and testing splits using 75% of # the data for training and the remaining 25% for testing (trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.25, random_state=42)

第 63-65 行将标签编码成了独热向量。

我们使用 scikit-learn 对数据进行划分——将 75% 的数据用来做训练,其余 25% 的数据则被留作测试之用(第 69 和 70 行)。

接下来,我们将初始化我们的数据增强对象,并编译、训练我们的人脸活体检测模型:

# construct the training image generator for data augmentation aug = ImageDataGenerator(rotation_range=20, zoom_range=0.15, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.15, horizontal_flip=True, fill_mode="nearest") # initialize the optimizer and model print("[INFO] compiling model...") opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS) model = LivenessNet.build(width=32, height=32, depth=3, classes=len(le.classes_)) model.compile(loss="binary_crossentropy", optimizer=opt, metrics=["accuracy"]) # train the network print("[INFO] training network for {} epochs...".format(EPOCHS)) H = model.fit_generator(aug.flow(trainX, trainY, batch_size=BS), validation_data=(testX, testY), steps_per_epoch=len(trainX) // BS, epochs=EPOCHS)

第 73-75 行构造了一个数据增强对象,它将通过随机的旋转,缩放、平移、剪切和翻转生成新的图像。想要了解更多关于数据增强的知识,请参阅下面这篇博文:https://www.pyimagesearch.com/2018/12/24/how-to-use-keras-fit-and-fit_generator-a-hands-on-tutorial/

我们在第 79-83 行构建并编译了「LivenessNet」模型。

接着,我们在第 87-89 行开始训练模型。由于我们使用了浅层网络和小型数据集,这个过程相对会比较快。

当模型训练完成后,我们可以评估训练结果,并生成一个训练过程示意图:

# evaluate the network print("[INFO] evaluating network...") predictions = model.predict(testX, batch_size=BS) print(classification_report(testY.argmax(axis=1), predictions.argmax(axis=1), target_names=le.classes_)) # save the network to disk print("[INFO] serializing network to '{}'...".format(args["model"])) model.save(args["model"]) # save the label encoder to disk f = open(args["le"], "wb") f.write(pickle.dumps(le)) f.close() # plot the training loss and accuracy plt.style.use("ggplot") plt.figure() plt.plot(np.arange(0, EPOCHS), H.history["loss"], label="train_loss") plt.plot(np.arange(0, EPOCHS), H.history["val_loss"], label="val_loss") plt.plot(np.arange(0, EPOCHS), H.history["acc"], label="train_acc") plt.plot(np.arange(0, EPOCHS), H.history["val_acc"], label="val_acc") plt.title("Training Loss and Accuracy on Dataset") plt.xlabel("Epoch #") plt.ylabel("Loss/Accuracy") plt.legend(loc="lower left") plt.savefig(args["plot"])

第 93 行给出了在测试集上得到的预测结果。以此为基础,生成了分类结果报告「classification_report」并显示在终端中(第 94 和 95 行)。

「LivenessNet」模型和标签编码器在第 99-104 行被序列化并写入磁盘。

剩余的第 107-117 行生成了一个训练过程示意图,以待查看。

训练我们的活体检测器

现在,我们已经为训练活体检测器做好了准备。

请确保你通过本教程「下载」部分的链接下载了源代码和数据集。接着,请执行下面你的命令:

$ python train.py --dataset dataset --model liveness.model --le le.pickle [INFO] loading images... [INFO] compiling model... [INFO] training network for 50 epochs... Epoch 1/50 29/29 [==============================] - 2s 58ms/step - loss: 1.0113 - acc: 0.5862 - val_loss: 0.4749 - val_acc: 0.7436 Epoch 2/50 29/29 [==============================] - 1s 21ms/step - loss: 0.9418 - acc: 0.6127 - val_loss: 0.4436 - val_acc: 0.7949 Epoch 3/50 29/29 [==============================] - 1s 21ms/step - loss: 0.8926 - acc: 0.6472 - val_loss: 0.3837 - val_acc: 0.8077 ... Epoch 48/50 29/29 [==============================] - 1s 21ms/step - loss: 0.2796 - acc: 0.9094 - val_loss: 0.0299 - val_acc: 1.0000 Epoch 49/50 29/29 [==============================] - 1s 21ms/step - loss: 0.3733 - acc: 0.8792 - val_loss: 0.0346 - val_acc: 0.9872 Epoch 50/50 29/29 [==============================] - 1s 21ms/step - loss: 0.2660 - acc: 0.9008 - val_loss: 0.0322 - val_acc: 0.9872 [INFO] evaluating network... precision recall f1-score support fake 0.97 1.00 0.99 35 real 1.00 0.98 0.99 43 micro avg 0.99 0.99 0.99 78 macro avg 0.99 0.99 0.99 78 weighted avg 0.99 0.99 0.99 78 [INFO] serializing network to 'liveness.model'...

图 6:使用 OpenCV、Keras 以及深度学习技术训练一个人脸活体检测模型的训练过程示意图。

如结果所示,我们在验证集上实现 99% 的活体检测准确率。

整合一下:通过 OpenCV 实现活体检测

图 7:使用 OpenCV 和深度学习技术实现人脸活体检测

最后,我们需要做的是将以上内容整合起来:

1. 连接到我们的网络摄像头/视频流 2. 将人脸检测应用到每一帧上 3. 对每一个检测到的人脸应用我们的活体检测模型

请打开「liveness_demo.py」文件并插入下列代码:

# import the necessary packages from imutils.video import VideoStream from keras.preprocessing.image import img_to_array from keras.models import load_model import numpy as np import argparse import imutils import pickle import time import cv2 import os # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-m", "--model", type=str, required=True, help="path to trained model") ap.add_argument("-l", "--le", type=str, required=True, help="path to label encoder") ap.add_argument("-d", "--detector", type=str, required=True, help="path to OpenCV's deep learning face detector") ap.add_argument("-c", "--confidence", type=float, default=0.5, help="minimum probability to filter weak detections") args = vars(ap.parse_args())

第 2-11 行输入了我们需要的安装包。 注意,我们将使用:

  • 「VideoStream」:用来连接到我们的摄像头。
  • 「img_to_array」:令我们的视频帧存储在一个兼容的数组格式中。
  • 「load_model」:加载我们的序列化 Keras 模型。
  • 「imutils」:包含一些方便使用的工具函数。
  • 「cv2」:绑定 OpenCV。

在第 14-23 行中,我们将解析命令行参数:

  • 「--model」:我们预训练好的用于活体检测的 Keras 模型的路径。
  • 「--le」:标签编码器的路径。
  • 「--detector」:用于计算出人脸 ROI 的 OpenCV 的深度学习人脸检测器的路径。
  • 「--confidence」:过滤掉较弱的检测结果的最小阈值概率。

接下来,我们将初始化人脸检测器、LivenessNet 模型 标签编码器,以及我们的视频流:

# load our serialized face detector from disk print("[INFO] loading face detector...") protoPath = os.path.sep.join([args["detector"], "deploy.prototxt"]) modelPath = os.path.sep.join([args["detector"], "res10_300x300_ssd_iter_140000.caffemodel"]) net = cv2.dnn.readNetFromCaffe(protoPath, modelPath) # load the liveness detector model and label encoder from disk print("[INFO] loading liveness detector...") model = load_model(args["model"]) le = pickle.loads(open(args["le"], "rb").read()) # initialize the video stream and allow the camera sensor to warmup print("[INFO] starting video stream...") vs = VideoStream(src=0).start() time.sleep(2.0)

在第 27-30 行加载 OpenCV 的人脸检测器。

在此基础上,我们加载了序列化、预训练的 LivenessNet 模型以及标签编码器(第 34-35 行)。

在第 39 和 40 行中,我们的「VideoStream」对象被实例化,并且允许摄像头预热 2 秒。

至此,是时候开始循环输入视频帧来检测「真实」人脸和「伪造/欺骗性」人脸了:

# loop over the frames from the video stream while True: # grab the frame from the threaded video stream and resize it # to have a maximum width of 600 pixels frame = vs.read() frame = imutils.resize(frame, width=600) # grab the frame dimensions and convert it to a blob (h, w) = frame.shape[:2] blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0)) # pass the blob through the network and obtain the detections and # predictions net.setInput(blob) detections = net.forward()

第 43 行开启了一个无限的「while」循环代码块,我们首先读取某一帧然后对其进行放缩(第46 和 47 行)。

重新定义图像尺寸后,获取帧的尺寸,以便稍后执行缩放(第 50 行)。

使用 OpenCV 的「blobFromImage」函数(https://www.pyimagesearch.com/2017/11/06/deep-learning-opencvs-blobfromimage-works/)生成一个「blob」(第 51 行和 52 行),然后让其通过人脸检测网络,完成推理过程(第 56 和 57 行)。

现在,让我们进入有趣的部分——使用 OpenCV 和深度学习进行活体检测。

# loop over the detections for i in range(0, detections.shape[2]): # extract the confidence (i.e., probability) associated with the # prediction confidence = detections[0, 0, i, 2] # filter out weak detections if confidence > args["confidence"]: # compute the (x, y)-coordinates of the bounding box for # the face and extract the face ROI box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (startX, startY, endX, endY) = box.astype("int") # ensure the detected bounding box does fall outside the # dimensions of the frame startX = max(0, startX) startY = max(0, startY) endX = min(w, endX) endY = min(h, endY) # extract the face ROI and then preproces it in the exact # same manner as our training data face = frame[startY:endY, startX:endX] face = cv2.resize(face, (32, 32)) face = face.astype("float") / 255.0 face = img_to_array(face) face = np.expand_dims(face, axis=0) # pass the face ROI through the trained liveness detector # model to determine if the face is "real" or "fake" preds = model.predict(face)[0] j = np.argmax(preds) label = le.classes_[j] # draw the label and bounding box on the frame label = "{}: {:.4f}".format(label, preds[j]) cv2.putText(frame, label, (startX, startY - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) cv2.rectangle(frame, (startX, startY), (endX, endY), (0, 0, 255), 2)

在第 60 行中,我们开始循环进行人脸检测。在循环过程中,我们做到了:

  • 过滤掉较弱的检测结果(第 63-66 行)。
  • 抽取出人脸边界「box」的坐标,并确保它们不会超出检测框的尺寸范围(第 69-77 行)。
  • 抽取出人脸 ROI,并使用训练数据所采取的同样方式对其进行预处理。(第 81-85 行)
  • 使用活体检测模型来判别输入的人脸是「真实的」还是「伪造/欺骗性」的(第 89-91 行)。
  • 在第 91 行中,你可以插入自己的代码执行人脸识别任务,但是只能在真实图像上进行。伪代码与「if label == "real": run_face_reconition()」类似,可以直接添加到第 91 行之后。
  • 最后(在这个演示样例中),我们会绘制出「label」文本和一个包围人脸的「rectangle」(第 94-98 行)。

接下来,我们将展示检测结果并清理内存:

# show the output frame and wait for a key press cv2.imshow("Frame", frame) key = cv2.waitKey(1) & 0xFF # if the `q` key was pressed, break from the loop if key == ord("q"): break # do a bit of cleanup cv2.destroyAllWindows() vs.stop()

当捕获到键盘按键时,循环过程中的每一轮迭代都会显示出输出帧(第 101-102 行)。只要用户按下「q」(quit)键,检测器就停止循环、开启指示器并关闭窗口(第 105-110 行)。

将我们的活体检测器应用到实时视频上

如果读者想跟着我们的活体检测演示完成整个执行过程,请确保你已经通过本文「下载」部分的链接下载到了源代码和预训练好的活体检测模型。

接着,请打开一个终端并执行下面的命令:

$ python liveness_demo.py --model liveness.model --le le.pickle --detector face_detector Using TensorFlow backend. [INFO] loading face detector... [INFO] loading liveness detector... [INFO] starting video stream...

在这里,你可以看到我们的活体检测器成功地将真实人脸和伪造人脸区分了开来。

我在下面的视频汇中展示了一段更长的演示样例。

视频观看地址:https://youtu.be/MPedzm6uOMA

局限性、改进和未来的工作

我们的活性检测器的主要限制实际上是数据集比较有限——总共只有 311 张图像(包含「真实」类的 161 张图像和「伪造」类的 150 张图像)。

这项工作的一项扩展,就是收集额外的训练数据,更具体地说,可以收集来自于你、我之外的人的图像/视频帧数据。

请记住,这里使用的示例数据集只包含一个人(我自己)的人脸。我是白人/白种人,而你应该收集其他种族和肤色的人脸训练数据。

我们的活体检测器只在屏幕上具有欺骗性攻击的人脸进行训练,而没有对打印出来的图像或照片进行训练。因此,我的第三个建议是,除了简单的屏幕录制回放之外,还要收集其它种类的图像/人脸资源。

最后,我想说:想实现活体检测并没有什么捷径。

最好的活体检测器应该包含多种活体检测方法(请参阅上面的「什么是活体检测, 我们为什么需要它?」一节)。

你需要花点时间来考虑和评估你自己的项目、指导方针和需求。在某些情况下,你可能只需要用到基本的眨眼检测启发方法。

而在其他情况下,你需要将基于深度学习的活体检测与其它启发式方法相结合。

不要急于进行人脸识别和活体检测。你克制一下自己,花点时间考虑自己独特的项目需求。这样的话将确保你获得更好、更准确的结果。

总结

通过学习本教程,你就可以掌握如何使用 OpenCV 进行活体检测。

现在通过使用活体检测器,你就可以检测出伪造的人脸,并在你自己的人脸识别系统中执行反人脸欺骗过程。

在活体检测器的创建过程中,我们用到了 OpenCV、深度学习技术以及 Python 语言。

首先,我们需要收集自己的「真实 vs 伪造」人脸数据集。为了完成该任务,我们要做到:

1. 用智能手机录制一段我们自己的视频(即「真实」人脸)。 2. 将我们的智能手机屏幕展示给笔记本电脑/桌面电脑的摄像头,重放在上一步中录制的同一个视频,然后使用你的网络摄像头录下视频回放(即「伪造」人脸)。 3. 将人脸检测技术同时应用到上面两个视频集合中,以构建最终的活体检测数据集。

在构建好数据集后,我们实现了「LivenessNet」——它是一个用 Keras 和深度学习技术实现的卷积神经网络。

我们特意将网络设计得很浅,这是为了确保:

1. 在我们的小型数据集上减少过拟合现象。 2. 模型能够实时运行(包括在树莓派等硬件上)。

总的来说,该活体检测器在我们的验证集上的表现,准确率高达 99% 。

为了演示完整的活体检测工作流程,我们创建了一个 Python OpenCV 的脚本,该脚本加载了我们的活体检测程序并将其应用在了实时视频流上。

正如我们的演示样例所示,我们的活体检测器能够区分真假人脸。

希望你喜欢这篇使用 OpenCV 进行活体检测的文章!

资源下载链接:https://www.getdrip.com/forms/321809846/submissions

via: https://www.pyimagesearch.com/2019/03/11/liveness-detection-with-opencv

0 人点赞