基於Python+Keras+OpenCV實現實時人臉活體檢測 | 文末送書

基於Python+Keras+OpenCV實現實時人臉活體檢測 | 文末送書

你在互聯網上找到的大多數人臉識別算法和研究論文都會遭受照片***。這些方法在檢測和識別來自網絡攝像頭的圖像、視頻和視頻流中的人臉方面是非常有效,但是他們無法區分現實生活中的面孔和照片上的面孔。這種無法區別現實人臉的現象是由於這些算法是在二維幀上工作的。
現在讓我們去試想一下,我們實現一個人臉識別系統,該系統可以很好地區分已知面孔和未知面孔,以便只有授權人員才能訪問,儘管如此,一個心懷不軌的人只要出示授權人的照片也能訪問。至此一個3D人臉的識別系統,類似於蘋果的FaceID,應運而生了,但如果我們沒有3D探測器該怎麼辦呢?
基於Python+Keras+OpenCV實現實時人臉活體檢測 | 文末送書
本文的目標是實現一種基於眨眼檢測的人臉活體檢測算法,以抵抗照片***。該算法通過網絡攝像頭實時工作,通過檢測眨眼來區分現實生活中的面孔和照片上的面孔。通俗地說,程序運行如下:


  • 在網絡攝像頭生成的每個幀中檢測人臉。
  • 對於每個檢測到的臉,檢測眼睛。
  • 對於每個檢測到的眼睛,檢測眼睛是否睜開或關閉。
  • 如果在某個時候檢測到眼睛是睜開的,然後是閉着的,然後是睜開的,我們就斷定此人已經眨了眼睛,並且程序顯示他的名字(如果是人臉識別開門器,我們將授權此人進入)。
    對於人臉的檢測和識別,你需要安裝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}

現在我們知道了每個想識別的人的編碼,我們可以嘗試通過網絡攝像頭識別人臉,然而,在轉到這一部分之前,我們需要區分一張人臉照片和一張活人的臉。
**2.人臉活體檢測**
我們的目標是在某個點上檢測出一個睜閉的睜眼模式。我訓練了一個卷積神經網絡來分類眼睛是閉着的還是睜着的,所選擇的模型是LeNet-5,它已經在Closed Eyes In The Wild (CEW)數據集上進行了訓練,它由大約4800張24x24大小的眼睛圖像組成。
Closed Eyes In The Wild (CEW)數據集地址:
* http://parnec.nuaa.edu.cn/xtan/data/ClosedEyeDatabases.html

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








**3.活體的人臉識別**
我們幾乎擁有了建立“真實”人臉識別算法的所有要素,我們只需要一種實時檢測人臉和眼睛的方法。我使用openCV預先訓練的Haar級聯分類器來完成這些任務。有關Haar cascade人臉和眼睛檢測的更多信息,我強烈建議你閱讀openCV的這篇強大的文章。
* https://docs.opencv.org/3.4.3/d7/d8b/tutorial_py_face_detection.html
* 

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帳戶上找到。
* 我的github帳戶
* https://github.com/Guarouba/face_rec
使用眨眼檢測功能阻止照片***的演示視頻:https://youtu.be/arQN6w0fZw8
**參考文獻**
* https://docs.opencv.org/3.4.3/d7/d8b/tutorial_py_face_detection.html
* https://www.pyimagesearch.com/2018/06/18/face-recognition-with-opencv-python-and-deep-learning/
原文鏈接:https://towardsdatascience.com/real-time-face-liveness-detection-with-python-keras-and-opencv-c35dc70dafd3
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章