乐趣区

关于人工智能:基于PythonKeras和OpenCV的实时人脸活体检测

作者 |Jordan Van Eetveldt
编译 |Flin
起源 |towardsdatascience

你在互联网上找到的大多数人脸识别算法和钻研论文都蒙受照片攻打。这些办法在检测和辨认来自网络摄像头的图像、视频和视频流中的人脸方面十分无效。然而,他们无奈辨别现实生活中的脸孔和照片上的脸孔。这种无奈辨认人脸的景象是因为这些算法在二维帧上工作。

当初让咱们设想一下咱们想要实现一个人脸识别开门器。该零碎能够很好地区分已知脸孔和未知脸孔,以便只有受权人员能力拜访。尽管如此,一个心怀不轨的人只有出示授权人的照片。这个 3D 探测器,相似于苹果的 FaceID,应运而生了。但如果咱们没有 3D 探测器呢?

本文的指标是实现一种基于眨眼检测的人脸活体检测算法,以抵制照片攻打。该算法通过网络摄像头实时工作,只有当人的名字闪动时才会显示进去。艰深地说,程序运行如下:

  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}

当初咱们晓得了每个想辨认的人的编码,咱们能够尝试通过网络摄像头辨认人脸。然而,在转到这一部分之前,咱们须要辨别一张人脸照片和一张活人的脸。

  1. 人脸活体检测

作为揭示,咱们的指标是在某个点上检测出一个睁闭的睁眼模式。我训练了一个卷积神经网络来分类眼睛是闭着的还是睁着的。所抉择的模型是 LeNet-5,它曾经在 Closed Eyes In The Wild (CEW)数据集上进行了训练。它由大概 4800 张 24×24 大小的眼睛图像组成。

Closed Eyes In The Wild (CEW)数据集地址:

  • http://parnec.nuaa.edu.cn/xta…
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 的这篇弱小的文章。

  • https://docs.opencv.org/3.4.3…
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/f…

应用眨眼检测性能阻止照片攻打的演示视频:
https://youtu.be/arQN6w0fZw8

参考文献

  • https://docs.opencv.org/3.4.3…
  • https://www.pyimagesearch.com…

原文链接:https://towardsdatascience.co…

欢送关注磐创 AI 博客站:
http://panchuang.net/

sklearn 机器学习中文官网文档:
http://sklearn123.com/

欢送关注磐创博客资源汇总站:
http://docs.panchuang.net/

退出移动版