史上最全 OpenCV 活體檢測教程!
雷鋒網 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
※加速研發自動駕駛卡車,戴姆勒買下Torc Robotics多數股權
TAG:雷鋒網 |