基於OpenCV的(人臉)活性檢測

通過本教程,我們將學到如何使用OpenCV進行活體檢測。我們將要創建一個活性檢測算子。在面部識別系統中發現假的臉(如靜止圖片)。

過去幾年裏,我撰寫了幾篇臉部教程,包括了:

然而,在郵箱中以及在所發表面部識別文章的評論區中,問得很多次的一個問題是:

 

如何區分真臉和假的臉?

當一個壞人故意破壞你的臉部識別系統,會有發生什麼事情。


這樣的一個用戶會拿着別人的照片。他們可能手舉着在手機中的一副照片或者視頻,對着臉部識別系統的信息採集攝像頭(例如像上面那副圖片所示的那樣)。

這些情況下,攝像頭完全可能對着手機中的圖片,識別出一張臉。最終導致一個沒授權的用戶繞過了你的臉部識別系統。

怎麼纔可以發現區分開“假的”和“真的、有效的”臉部?怎麼把活體檢測算法集成到你的面部識別程序中?

答案是:應用本文介紹的基於OpenCV的活體檢測算法。

請繼續閱讀,下文介紹如何把基於OpenCV的活體檢測算法集成到面部識別系統中。


本教程的第一部分,我們討論活體檢測,包括了介紹這算法是什麼、爲什麼使用本算法。

我們將要複習一下,爲了運用活體檢測算法,數據集的一些操作,包括了:

  • 怎麼建立活體檢測的數據集。
  • 區分真假面部圖片的例子

我們也要說明活體檢測算法的工程目錄。

爲了創建活體檢測算子,我們將訓練一個能區別真假人臉的深度神經網絡

我們因此需要:

  1. 練級圖片數據集
  2. 實現一個能運行活體檢測算子的卷積神經網絡(我們稱之爲LivenessNet)
  3. 訓練活體檢測算子網絡
  4. 創建一個Python+ OpenCV腳本。調用我們訓練好的活體檢測模型並應用到實時視頻中。

讓我們開始吧。

什麼是活體檢測,爲什麼我們需要活體檢測。

圖1. 基於OpenCV的活體檢測。左邊是一段自拍的視頻直播(真實的),右邊是你們也能看到的我拿着自己的iPhone(假的/僞造的)

臉部識別系統變得越來越流行了,從iPhone或其他智能手機到中國的羣衆監控。臉部識別系統無處不在。

然而,臉部識別系統容易被一些假的人臉欺騙。

單單地對着識別攝像頭舉着一張某人的照片(如打印的,手機上的等等),即可繞過臉部識別系統。

爲了讓臉部識別系統更加安全,我們需要能檢測這些假的/非真實的人臉。活體檢測就是這種算法的名字。


實現活體檢測,有很多種方法,包括了:

3可變聚焦分析(Variable focusing analysis)。評估兩個連續幀中的像素變化。
4啓發式算法(Heuristic-based algorithms)。包括眼睛動作,嘴脣動作,眨眼檢測。這些算法嘗試跟蹤眼睛的移動以及眨眼,保證用戶不是舉着別人的照片(因爲靜止的圖片中的人不會眨眼也不會動嘴脣)。

 

There are a number of approaches to liveness detection, including:

  • 紋理分析(Texture analysis)。計算面部區域的局部二值模式(Local Binary Patterns),使用SVM分類真臉和假臉。
  • 頻率分析(Frequency analysis)。分析臉部的頻譜。
  • 可變聚焦分析(Variable focusing analysis)。評估兩個連續幀中的像素變化。
  • 啓發式算法(Heuristic-based algorithms)。包括眼睛動作,嘴脣動作,眨眼檢測。這些算法嘗試跟蹤眼睛的移動以及眨眼,保證用戶不是舉着別人的照片(因爲靜止的圖片中的人不會眨眼也不會動嘴脣)。
  • 光流算法。審查3D物體和2D平面的光流特性變化。
  • 3D臉部形狀。和蘋果iPhone臉部識別系統類似,使得系統能區別真臉和打印出來的別人圖片。
  • 上述的不同組合。使得臉部識別工程師可以選擇適合他們應用的檢測模型。

活體檢測算法的綜述可以閱讀Chakraborty and Das在2014年的論文:《An Overview of Face liveness Detection》
今天教程的目標是,我們把活體檢測看成是一個2進制的分類問題。
對於一張輸入照片,我們訓練一個能區別真、假人臉的卷積神經網絡。
但是在我們訓練活體檢測模型之前,我們首先考察我們的數據集。

 

我們活體檢測視頻

圖2 手機真臉、假臉樣品的例子。左邊視頻展示的是一個符合規範的我的臉。右邊視頻展示的是我的手提錄的視頻
爲了讓我們的例子直接簡潔,我們在本文建立的活體檢測算子將致力於區分真臉和屏幕中的假臉。
本算法可以延伸爲其他類型的假臉,包括了沖印照片,高像素打印圖片等。
爲了建立活體檢測數據集,我:

 

  1. 拿起我的iPhone,打開自拍模式
  2. 錄製一個25秒的我在辦公室走動的視頻
  3. 重播這段25秒視頻,這次將我的iPhone對準我的臺式機,讓臺式機錄製視頻重播的景象。
  4. 這樣我們得到了一個真臉的視頻,另外一個假臉的視頻。
  5. 最後,對着兩段視頻,運用臉部檢測,提取出兩種分類下的每個臉部的ROIs(就是每張圖片都過濾了背景,只留下臉部)。

我提供了真臉、假臉的兩段視頻文件,請看本文的“下載”部分。

讀者可以使用這些視頻作爲數據集的起點,但是,我建議採集更多的數據,可以幫助建立一個更加魯棒和精準的活體檢測算子。

測試中發現,這模型稍微向我本人的臉偏離。原因是模型訓練時用的數據集都是我的。另外,我是白人,我期待同樣的數據集適用於不同膚色的人。(譯者注:作者是大概是認爲自己做實驗時候採集到的數據集太小了,圖像識別中每個分類達3000個樣品很正常)

理想情況下,你在數據集中加入多個人的臉部,同時包括不同的人種。爲了改善你的檢測模型,請參考“不足點以及將來的工作”提出的建議。

教程的其餘部分,你會學習到如何使用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 /: 我們的數據集目錄包括了兩個分類的圖片;
    • 當手機播放我的視頻時,電腦錄製到的手機屏幕而獲取得到的假臉,
    • 手機的自拍攝像頭捕捉到的我的真臉。
  • face_detector/: 包括了我們預訓練好的能定位人臉ROIs的Caffe人臉算子。
  • pyimagesearch/: 這個模塊包括了我們LivenessNet類
  • videos/:我提供的給LivenessNet分類器的兩個視頻輸入

現在,我們一起詳細分析三段Python代碼。到了本文最後,讀者們可以往這程序輸入個人的數據或者視頻。先簡單一提,這三段腳本是:

  1. gather_examples.py : 這腳本提取了輸入視頻文件中的臉部ROIs,幫助我們建立一個深度學習人臉活體數據集。
  2. train_liveness.py : 正如其名,這段代碼將要訓練LivenessNet分類器。我們將運用Keras和TensorFLow訓練模型。訓練結束後會得到一些新文件:
    • le.pickle : 我們的種類標籤編碼器。
    • liveness.model : 我們序列化的Keras模型。它用於檢測人臉活性。
    • plot.png : 這是訓練歷史的繪圖。它展示了精度和損失曲線。我們可以依據它評估我們的模型(過擬合、欠擬合等等。)(譯者注:通常是迭代次數不夠,稱爲欠擬合。迭代次數太多,稱爲過擬合)
  3. liveness_demo.py : 我們的演示腳本。這程序會打開webcam,讀取各幀圖像,實時地執行臉部活體檢測。

從我們訓練的(視頻)數據集檢測並提取臉部ROIs

圖3. 爲了建立活體檢測數據集,在視頻中檢測人臉ROIs

既然我們已經看了我們初始化後的數據集以及工程目錄,接下來我們可以通過輸入視頻,提取真的和假的臉部圖像。

這段代碼的目標是生成兩個文件夾:

  1. dataset/fake/: 包含了fake.mp4文件中的臉部ROIs
  2. dataset/real/: 包含了real.mov文件中的臉部ROIs

在這樣的框架下,我們接下來是用這些圖片訓練一個基於深度學習的活體檢測算子。

打開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行解析命令行輸入參數:

  • --input : 輸入視頻文件的路徑
  • --output : 裁剪後的臉部存放的路徑
  • --detector : 臉部識別算子的路徑。我們將要使用OpenCV的深度學習臉部檢測算子。這個Caffe模型已經包含在“下載”部分便於大家。
  • --confidence : 過濾臉部檢測結果的最小可能性閥值。缺省值是50%。
  • --skip : 我們不需要處理每一幀圖片,因爲相鄰的數幀圖片都是相似的。因此,我們將要跳過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的深度學習臉部檢測算子。

在第30行我們打開視頻流。

我們也初始化了兩個變量。記錄了我們的循環執行中的讀取圖像數目和保存圖像數目。

接下來創建一個循環處理每一幀圖像:

# 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

我們的while循環在第35行開始:

我們讀取並覈實一幀圖像(第37-42行)

在這個點上,既然我們已經讀取了一幀圖像,我們將讀取計數值遞增(第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]

爲了運行臉部檢測,我們需要創建一個圖像的bolb(第53-54行)。爲了兼容我們的Caffe臉部檢測算子,這個bolb長和高是300x300。之後縮放邊框是必需的,因此第52行,獲取圖像的尺寸。

第58-59行將bolb正向通過深度學習的臉部檢測算子。

我們的代碼假設視頻中每一幀只有一個人臉(第62-65行)。這幫助減少誤報。如果你的視頻包含了一個以上的人臉,我建議你相應地改變相關邏輯。

因此,第65行獲得了臉部檢測的最大可能性所在的位置索引。第66行用這個位置索引提取了檢測的置信度。

我們過濾掉弱的檢測(譯者注:置信度較低的檢測過濾掉),並將人臉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大於閥值,爲了降低誤報

第74-76行我們提取了臉部ROI的彈框座標以及臉部ROI本身(第74-76行)。

在第79行-第81行,我們自動爲臉部ROI生成了路徑+文件名字,並保存到硬盤中。同時,我們所保存到的臉部的數量遞增。

一旦處理完成,我們清理了一下,見第86-87行。

建立我們的活體檢測圖像數據集

 

圖4 我們OpenCV臉部活體檢測數據集。我們將使用Keras和OpenCV訓練並演示一個活體模型。

既然我們已經實現了example.py腳本,那麼就在工程中調用它。

確保你用本教程的“下載”獲取源碼和例程、輸入視頻。

打開終端,執行以下代碼提取“假的、僞造”的臉部種類。

$ python gather_examples.py --input videos/fake.mp4 --output dataset/fake \
	--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/real.mov --output dataset/real \
	--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

由於“真實的”視頻文件長於“僞造的”視頻文件,我們將跳過更多的幀數以平衡兩個種類的輸出臉部ROIs數量。

執行代碼以後,得到了以下的圖像數目:

  • 僞造的:150幅圖像
  • 真實的:161幅圖像
  • 總計:311幅圖像。

實現深度學習活體檢測算子“LivenessNet”

圖5 LivenessNet的深度學習框架是一個用於檢測圖像和視頻中的臉部活體卷積神經網絡

下一步實現了我們的深度學習活體檢測算子“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

All of our imports are from Keras (Lines 2-10). For an in-depth review of each of these layers and functions, be sure to refer to Deep Learning for Computer Vision with Python.

全部導入的模塊都輸入Keras(第2-10行)。如果想深度分析每一層的代碼以及作用,請參考Deep Learning for Computer Vision with Python。

我們的LivenessNet種類定義在第12行。它包括了一個靜態函數build()(見第14行)。build()函數接受4種參數:

  • 寬度width :圖像的寬度
  • 高度height :圖像的高度.
  • 深度depth :圖像的通道數(本場合由於是RGB圖像,有3個通道)
  • 種類classes:種類的數目。在這裏我們有兩個種類:“真實的”和“僞造的”

第17行初始化了我們的模型。

第18行定義了我們模型的輸入形狀。第23

接下來,增加我們CNN卷積神經網絡的層數:

		# 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))

我們的CNN展示了 VGGNet-esque的品質。它很淺,帶有一些少量的學習過濾器。事實上,我們不需要一個很深的網絡區別真的和假的臉部。

第一個 CONV => RELU => CONV => RELU => POOL層組在第28-36行指定了。同時增加了批標準化(BN)和dropout層。

第39-46增加了另一個CONV => RELU => CONV => RELU => POOL層組。

網絡的尾部是我們的FC=>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行,return模型到訓練程序。

創建活體檢測算子訓練腳本:

圖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 : 用於生成訓練情況繪圖。我們指定Agg後端,便於將繪圖保存到硬盤。見第3行。
  • LivenessNet : 之前提到的我們定義好了活體CNN網絡。
  • train_test_split :scikit-learn的一個函數,用於分離訓練和測試的樣品
  • classification_report :來自scikit-learn中。生成模型運行表現的簡要統計報告。
  • ImageDataGenerator :用於數據擴充。讓我們可以隨機地變異現有圖像(譯者注:增大數據集容量)。
  • Adam : 模型的優化器。(也可以選擇SGD,RMSprop等)
  • paths : imutils的模塊。幫我們獲取硬盤中圖像文件的路徑。
  • pyplot :用於生成一個好看的訓練繪圖。
  • numpy : 一個Python數字處理庫。也是OpenCV的運行需要的。
  • argparse :用於解析命令行參數
  • pickle : 用於將標籤編譯器保存到硬盤。
  • cv2 : 我們的OpenCV。
  • os : 這模塊有很多作用。但我們只用於分離操作系統路徑。

這真是囉嗦。不過如果知道上面導入的內容,那麼下面審查代碼就變得很簡單了。

本代碼支持四個參數輸入。

  • --dataset : 輸入數據集的路徑。本文已經提到用gather_example.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

訓練參數包括了初始的學習速度,批尺寸(batch size)和迭代次數,見第35-37行。

第42-44行,我們獲得了imagePaths。我們初始化了兩個列表,存放我們的數據和種類標籤。

第46-55行代碼的循環,建立了我們的數據和標籤列表。數據是我們讀取並將尺寸改爲32x32。每一張圖片都有對應的標籤存放在labels列表。

每一個像素的密集度用[0,1]表示。第59行用NumPy數組創建這個列表。

編碼我們的標籤以及劃分我們的數據。

# 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構建了數據擴充目標,生成了隨機的旋轉,縮放,漂移,裁剪,翻轉。想知道更多的數據擴充,請看 my previous blog post。

我們LivenessNet模型建立以及編譯,在第79-83行。

第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行,對測試數據進行預測。隨後生成了一個分類報告,並在終端打印(第94-95行)

第99-104行,LivenessNet模型和標籤的編碼器一同保存到硬盤。

剩餘的第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的活體檢測算法

綜合以上的各部分:基於OpenCV的活體檢測算法

圖7 用OpenCV和深度學習的臉部活體檢測

最後一步結合所有的部分:

  1. 我們讀取webcam/視頻流
  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 : OpenCV的深度學習臉部識別器的路徑,用於找到臉部ROIs
  • --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臉部識別算子。

第34-35行,我們讀取了序列化、預訓練好的模型(LivenessNet)和標籤編碼器。

第39-40行我們的視頻流對象實例化。攝像頭有兩秒開機熱機時間。

是時候開始循環處理每一幀圖像,檢測真、假臉部。

# 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行是一個死循環,裏面讀取並改變了每一幀的尺寸(第46-47行)。

隨後,每一幀的尺寸都被讀取,之後進行縮放(第50行)

使用OpenCV自帶的blobFromImage函數,我們生成一個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行)
  • 提取臉部彈框的座標,並保存不會坐落到圖像的邊緣之外(第69-77行)。
  • 提取臉部ROI,然後用處理訓練數據的方法進行預處理。
  • 使用我們的活體檢測模型判斷臉部是真實的還是僞造的。
  • 第91行,你會插入你的代碼,只對真實的圖像進行臉部識別。這段程度的僞代碼類似於 if label == "real": run_face_reconition()
  • 本次demo的最終,我們畫出標籤文字和矩陣框,以標記出臉部(第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”,我們會跳出循環,釋放 指針,關閉窗口(第105-110行)

對實時視頻部署我們的活體檢測算子

如果是要跟着我們的demo跑,請確保是從本文的“下載”部分獲取到的代碼、預訓練好的活體檢測模型。

打開終端並執行以下代碼:

$ 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...

這樣,可以看到我們活體檢測算子可以成功區分真實的僞造的人臉。

我將在下面的視頻中,展示一個更長的demo。

不足點,改善之處以及將來的工作

不足點,改善之處以及將來的工作

我們活體檢測算子最首要侷限在於我們受限的數據集,只有總數爲311張。(161是真實的種類,150是僞造的種類)。

本工作的一個工作延伸是簡單的收集額外的訓練數據,更詳細的是,圖像不能僅僅是你和我。

要記得本例子數據集只包括了一個人的臉部。同樣作者本身是白人,你可以手機更多的訓練臉部或者其他膚色的人種。

我們的活體檢測算子的訓練集中,僞造的樣品都是手機屏幕中的。並沒有使用打印出來的圖片進行訓練。因此,我的第三個建議是,不要只錄制手機屏幕,而是採取更多的方式獲得僞造人臉圖像。

最後,我想提醒大家,活體檢測算法並沒有銀色子彈(譯者注:意思是沒有完美的一種活體檢測算法)

一些最好的活體檢測算子繼承了多個活體檢測方法(請參考前文的“什麼是活體檢測,爲什麼要活體檢測”)

擠出時間,考慮並評估一下項目、指引、需求。某些情況下,你所需要的可能僅僅是基本的眨眼檢測。

另外的情況下,你可能需要結合深度學習檢測和其他試探法,

不要盲目衝進臉部識別和活體檢測,花時間想想,別衝動,想想你自身獨特的項目需求。這樣能保證你會獲得更好更準確的結果。

總結

本教程,學會了如何用OpenCV執行活體檢測

使用這個活體檢測算子,可以發現僞造的臉部,同時在自己的臉部識別系統中排除掉嘗試用臉部欺騙的人臉驗證。

使用OpenCV深度學習,Python創建了活體檢測算子

第一步是採集我們的真的vs假的數據集。爲了完成這個任務,我們:

  1. 首先用智能手機自拍模式錄製了我們的視頻
  2. 將我們的手機對着筆記本、臺式機,重播上面的視頻,使用我們的webcam錄製並回放視頻。
  3. 對兩段視頻,採用臉部識別,構造我們的活體檢測數據集

建立好數據集,我們實現了“LivenessNet”,一個Keras+深度學習的CNN網絡。

這個網絡故意做得淺,保證了:

  1. 在我們的小型數據集中,減少了過擬合的機會
  2. 模型本身可以實時運行(樹莓派也可以)

總的來說,我們活體檢測算子在我們的驗證組中能獲取99%的精度。

爲了演示這個活體檢測通道,我們創建了Python+OpenCV腳本,讀取了我們活體檢測算子並應用到實時視頻流當中。

像我們demo展示的那樣,我們的活體檢測算子能區別出真的和僞造的人臉。

我希望你們能享受今天我就基於OpenCV實現活體檢測的文章。

 

 

原文鏈接:https://www.pyimagesearch.com/2019/03/11/liveness-detection-with-opencv/

免責聲明:請大家支持原創,本文是翻譯的。只作學習用途。文中所有圖片均來自pyimagesearch。若侵權,請聯繫我。我的QQ是77028629。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章