基於Python、Keras和OpenCV的實時人臉活體檢測

做者|Jordan Van Eetveldt
編譯|Flin
來源|towardsdatasciencehtml

你在互聯網上找到的大多數人臉識別算法和研究論文都遭受照片攻擊。這些方法在檢測和識別來自網絡攝像頭的圖像、視頻和視頻流中的人臉方面很是有效。然而,他們沒法區分現實生活中的面孔和照片上的面孔。這種沒法識別人臉的現象是因爲這些算法在二維幀上工做。python

如今讓咱們想象一下咱們想要實現一我的臉識別開門器。該系統能夠很好地區分已知面孔和未知面孔,以便只有受權人員才能訪問。儘管如此,一個心懷不軌的人只要出示受權人的照片。這個3D探測器,相似於蘋果的FaceID,應運而生了。但若是咱們沒有3D探測器呢?git

本文的目標是實現一種基於眨眼檢測的人臉活體檢測算法,以抵抗照片攻擊。該算法經過網絡攝像頭實時工做,只有當人的名字閃爍時纔會顯示出來。通俗地說,程序運行以下:github

  1. 在網絡攝像頭生成的每一個幀中檢測人臉。
  2. 對於每一個檢測到的臉,檢測眼睛。
  3. 對於每一個檢測到的眼睛,檢測眼睛是否睜開或關閉。
  4. 若是在某個時候檢測到眼睛是睜開的,而後是閉着的,而後是睜開的,咱們就判定此人已經眨了眼睛,而且程序顯示了他的名字(若是是人臉識別開門器,咱們將受權此人進入)。

對於人臉的檢測和識別,你須要安裝face_recognition庫,它提供了很是有用的深度學習方法來查找和識別圖像中的人臉。特別是,face_locations、face_encodings和compare_faces函數是最有用的3個函數。人臉定位方法能夠用兩種方法來檢測人臉:方向梯度直方圖(HoG)和卷積神經網絡(CNN)。因爲時間限制,選擇了HoG方法。算法

face_encodings函數是一個預先訓練的卷積神經網絡,可以將圖像編碼成128個特徵向量。這個嵌入向量應該表示足夠的信息來區分兩個不一樣的人。最後,compare_faces計算兩個嵌入向量之間的距離。它將容許算法識別從攝像頭幀中提取的人臉,並將其嵌入向量與咱們數據集中全部編碼的人臉進行比較。最近的向量應該對應於同一我的。數據庫

1. 已知人臉數據集編碼

在個人例子中,算法可以識別我和奧巴馬。我爲每一個人挑選了大約10張照片。下面是處理和編碼已知人臉數據庫的代碼。網絡

def process_and_encode(images):
    known_encodings = []
    known_names = []
    print("[LOG] Encoding dataset ...")

    for image_path in tqdm(images):
        # 加載圖片
        image = cv2.imread(image_path)
        # 將其從BGR轉換爲RGB
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
     
        # 檢測圖像中的臉並獲取其位置(方框座標)
        boxes = face_recognition.face_locations(image, model='hog')

        # 將人臉編碼爲128維嵌入向量
        encoding = face_recognition.face_encodings(image, boxes)

        # 人物名稱是圖像來源文件夾的名稱
        name = image_path.split(os.path.sep)[-2]

        if len(encoding) > 0 : 
            known_encodings.append(encoding[0])
            known_names.append(name)

    return {"encodings": known_encodings, "names": known_names}

如今咱們知道了每一個想識別的人的編碼,咱們能夠嘗試經過網絡攝像頭識別人臉。然而,在轉到這一部分以前,咱們須要區分一張人臉照片和一張活人的臉。app

  1. 人臉活體檢測

做爲提醒,咱們的目標是在某個點上檢測出一個睜閉的睜眼模式。我訓練了一個卷積神經網絡來分類眼睛是閉着的仍是睜着的。所選擇的模型是LeNet-5,它已經在Closed Eyes In The Wild (CEW)數據集上進行了訓練。它由大約4800張24x24大小的眼睛圖像組成。框架

Closed Eyes In The Wild (CEW)數據集地址:機器學習

from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import AveragePooling2D
from keras.layers import Flatten
from keras.layers import Dense
from keras.preprocessing.image import ImageDataGenerator

IMG_SIZE = 24
def train(train_generator, val_generator):
	STEP_SIZE_TRAIN=train_generator.n//train_generator.batch_size
	STEP_SIZE_VALID=val_generator.n//val_generator.batch_size
	
	model = Sequential()

	model.add(Conv2D(filters=6, kernel_size=(3, 3), activation='relu', input_shape=(IMG_SIZE,IMG_SIZE,1)))
	model.add(AveragePooling2D())

	model.add(Conv2D(filters=16, kernel_size=(3, 3), activation='relu'))
	model.add(AveragePooling2D())

	model.add(Flatten())

	model.add(Dense(units=120, activation='relu'))

	model.add(Dense(units=84, activation='relu'))

	model.add(Dense(units=1, activation = 'sigmoid'))


	model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
  
  	print('[LOG] Training CNN')
  
	model.fit_generator(generator=train_generator,
	                    steps_per_epoch=STEP_SIZE_TRAIN,
	                    validation_data=val_generator,
	                    validation_steps=STEP_SIZE_VALID,
	                    epochs=20
	)
  return model

在評估模型時,我達到了94%的準確率。

每次咱們檢測到一隻眼睛,咱們就用咱們的模型來預測它的狀態,並跟蹤每一個人的眼睛狀態。所以,檢測眨眼變得很是容易,它試圖在眼睛狀態歷史中找到一個閉眼-睜眼-閉眼模式。

def isBlinking(history, maxFrames):
    """ @history: A string containing the history of eyes status 
         where a '1' means that the eyes were closed and '0' open.
        @maxFrames: The maximal number of successive frames where an eye is closed """
    for i in range(maxFrames):
        pattern = '1' + '0'*(i+1) + '1'
        if pattern in history:
            return True
    return False
  1. 活體的人臉識別

咱們幾乎擁有創建「真實」人臉識別算法的全部要素。咱們只須要一種實時檢測人臉和眼睛的方法。我使用openCV預先訓練的Haar級聯分類器來完成這些任務。有關Haar cascade人臉和眼睛檢測的更多信息,我強烈建議你閱讀openCV的這篇強大的文章。

def detect_and_display(model, video_capture, face_detector, open_eyes_detector, left_eye_detector, right_eye_detector, data, eyes_detected):
        frame = video_capture.read()
        # 調整框架大小
        frame = cv2.resize(frame, (0, 0), fx=0.6, fy=0.6)

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        
        # 檢測人臉
        faces = face_detector.detectMultiScale(
            gray,
            scaleFactor=1.2,
            minNeighbors=5,
            minSize=(50, 50),
            flags=cv2.CASCADE_SCALE_IMAGE
        )

        # 對於每一個檢測到的臉
        for (x,y,w,h) in faces:
            # 將人臉編碼爲128維嵌入向量
            encoding = face_recognition.face_encodings(rgb, [(y, x+w, y+h, x)])[0]

            # 將向量與全部已知的人臉編碼進行比較
            matches = face_recognition.compare_faces(data["encodings"], encoding)

            # 目前咱們不知道該人的名字
            name = "Unknown"

            # 若是至少有一次匹配:
            if True in matches:
                matchedIdxs = [i for (i, b) in enumerate(matches) if b]
                counts = {}
                for i in matchedIdxs:
                    name = data["names"][i]
                    counts[name] = counts.get(name, 0) + 1

                # 匹配次數最多的已知編碼對應於檢測到的人臉名稱
                name = max(counts, key=counts.get)

            face = frame[y:y+h,x:x+w]
            gray_face = gray[y:y+h,x:x+w]

            eyes = []
            
            # 眼睛檢測
            # 首先檢查眼睛是否睜開(考慮到眼鏡)
            open_eyes_glasses = open_eyes_detector.detectMultiScale(
                gray_face,
                scaleFactor=1.1,
                minNeighbors=5,
                minSize=(30, 30),
                flags = cv2.CASCADE_SCALE_IMAGE
            )
            # 若是open_eyes_glasses檢測到眼睛,則眼睛睜開 
            if len(open_eyes_glasses) == 2:
                eyes_detected[name]+='1'
                for (ex,ey,ew,eh) in open_eyes_glasses:
                    cv2.rectangle(face,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
            
            # 不然嘗試使用left和right_eye_detector檢測眼睛
            # 以檢測到睜開和閉合的眼睛                
            else:
                # 將臉分紅左右兩邊
                left_face = frame[y:y+h, x+int(w/2):x+w]
                left_face_gray = gray[y:y+h, x+int(w/2):x+w]

                right_face = frame[y:y+h, x:x+int(w/2)]
                right_face_gray = gray[y:y+h, x:x+int(w/2)]

                # 檢測左眼
                left_eye = left_eye_detector.detectMultiScale(
                    left_face_gray,
                    scaleFactor=1.1,
                    minNeighbors=5,
                    minSize=(30, 30),
                    flags = cv2.CASCADE_SCALE_IMAGE
                )

                # 檢測右眼
                right_eye = right_eye_detector.detectMultiScale(
                    right_face_gray,
                    scaleFactor=1.1,
                    minNeighbors=5,
                    minSize=(30, 30),
                    flags = cv2.CASCADE_SCALE_IMAGE
                )

                eye_status = '1' # we suppose the eyes are open

                # 檢查每隻眼睛是否閉合。
                # 若是有人閉着眼睛,咱們得出結論是閉着眼睛
                for (ex,ey,ew,eh) in right_eye:
                    color = (0,255,0)
                    pred = predict(right_face[ey:ey+eh,ex:ex+ew],model)
                    if pred == 'closed':
                        eye_status='0'
                        color = (0,0,255)
                    cv2.rectangle(right_face,(ex,ey),(ex+ew,ey+eh),color,2)
                for (ex,ey,ew,eh) in left_eye:
                    color = (0,255,0)
                    pred = predict(left_face[ey:ey+eh,ex:ex+ew],model)
                    if pred == 'closed':
                        eye_status='0'
                        color = (0,0,255)
                    cv2.rectangle(left_face,(ex,ey),(ex+ew,ey+eh),color,2)
                eyes_detected[name] += eye_status

            # 每次,咱們都會檢查該人是否眨眼
            # 若是是,咱們顯示其名字
            if isBlinking(eyes_detected[name],3):
                cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
                # 顯示名字
                y = y - 15 if y - 15 > 15 else y + 15
                cv2.putText(frame, name, (x, y), cv2.FONT_HERSHEY_SIMPLEX,0.75, (0, 255, 0), 2)

        return frame

上面的功能是用於檢測和識別真實人臉的代碼。它接受如下參數:

  • model:咱們的睜眼/閉眼分類器

  • video_capture:流視頻

  • face_detector:Haar級聯的人臉分類器。我使用了haarcascade_frontalface_alt.xml

  • open_eyes_detector:Haar級聯睜眼分類器。我使用了haarcascade_eye_tree_eyeglasses.xml

  • left_eye_detector:Haar級聯的左眼分類器。我使用了haarcascade_lefteye_2splits.xml,它能夠檢測睜眼或閉眼。

  • right_eye_detector:Haar級聯的右眼分類器。我使用了haarcascade_righteye_2splits.xml,它能夠檢測睜眼或閉眼。

  • data:已知編碼和已知名稱的字典

  • eyes_detected:包含每一個名稱的眼睛狀態歷史記錄的字典。

第2-4行,咱們從網絡攝像頭流中獲取一個幀,而後調整其大小以加快計算速度。

第10行,咱們從幀中檢測人臉,而後在第21行,咱們將其編碼爲128-d矢量。

第23-38行,咱們將這個向量與已知的人臉編碼進行比較,並經過計算匹配的次數來肯定此人的姓名。選擇匹配次數最多的一個。

第45行開始,咱們試着探測眼睛進入人臉框。

首先,咱們嘗試用睜眼檢測器來檢測睜眼。若是探測器探測成功,則在第54行,將「1」添加到眼睛狀態歷史記錄中,這意味着眼睛是睜開的,由於睜開的眼睛探測器沒法檢測到閉着的眼睛。不然,若是第一個分類器失敗(多是由於眼睛是閉着的,或者僅僅是由於它不能識別眼睛),則使用左眼和右眼檢測器。人臉被分爲左右兩側,以便對各個探測器進行分類。

第92行開始,提取眼睛部分,訓練後的模型預測眼睛是否閉合。若是檢測到一隻眼睛閉着,則兩眼都將被預測爲閉着,並將「0」添加到眼睛狀態歷史記錄中。不然就能夠判定眼睛是睜開的。

最後,在第110行,is blinking()函數用於檢測眨眼,若是該人眨眼,則顯示姓名。整個代碼均可以在個人github賬戶上找到。

使用眨眼檢測功能阻止照片攻擊的演示視頻:
https://youtu.be/arQN6w0fZw8

參考文獻

原文連接:https://towardsdatascience.com/real-time-face-liveness-detection-with-python-keras-and-opencv-c35dc70dafd3

歡迎關注磐創AI博客站:
http://panchuang.net/

sklearn機器學習中文官方文檔:
http://sklearn123.com/

歡迎關注磐創博客資源彙總站:
http://docs.panchuang.net/

相關文章
相關標籤/搜索