import cv2
|
import imutils
|
import dlib
|
import numpy as np
|
import random
|
import time
|
from imutils import face_utils
|
|
|
class FaceDetector:
|
def __init__(self, cap, win_width, win_height):
|
self.cap = cap
|
self.WIN_WIDTH = win_width
|
self.WIN_HEIGHT = win_height
|
|
self.detector = None # 人脸检测器
|
self.predictor = None # 特征点检测器
|
# 闪烁阈值
|
self.EAR_THRESH = None
|
self.MOUTH_THRESH = None
|
# 总闪烁次数
|
self.eye_flash_counter = None
|
self.mouth_open_counter = None
|
self.turn_left_counter = None
|
self.turn_right_counter = None
|
# 连续帧数阈值
|
self.EAR_CONSTANT_FRAMES = None
|
self.MOUTH_CONSTANT_FRAMES = None
|
self.LEFT_CONSTANT_FRAMES = None
|
self.RIGHT_CONSTANT_FRAMES = None
|
# 连续帧计数器
|
self.eye_flash_continuous_frame = 0
|
self.mouth_open_continuous_frame = 0
|
self.turn_left_continuous_frame = 0
|
self.turn_right_continuous_frame = 0
|
|
# 计算眼长宽比例 EAR值
|
@staticmethod
|
def count_EAR(eye):
|
A = np.linalg.norm(eye[1] - eye[5])
|
B = np.linalg.norm(eye[2] - eye[4])
|
C = np.linalg.norm(eye[0] - eye[3])
|
EAR = (A + B) / (2.0 * C)
|
return EAR
|
|
# 计算嘴长宽比例 MAR值
|
@staticmethod
|
def count_MAR(mouth):
|
A = np.linalg.norm(mouth[1] - mouth[11])
|
B = np.linalg.norm(mouth[2] - mouth[10])
|
C = np.linalg.norm(mouth[3] - mouth[9])
|
D = np.linalg.norm(mouth[4] - mouth[8])
|
E = np.linalg.norm(mouth[5] - mouth[7])
|
F = np.linalg.norm(mouth[0] - mouth[6]) # 水平欧几里德距离
|
ratio = (A + B + C + D + E) / (5.0 * F)
|
return ratio
|
|
# 计算左右脸转动比例 FR值
|
@staticmethod
|
def count_FR(face):
|
rightA = np.linalg.norm(face[0] - face[27])
|
rightB = np.linalg.norm(face[2] - face[30])
|
rightC = np.linalg.norm(face[4] - face[48])
|
leftA = np.linalg.norm(face[16] - face[27])
|
leftB = np.linalg.norm(face[14] - face[30])
|
leftC = np.linalg.norm(face[12] - face[54])
|
ratioA = rightA / leftA
|
ratioB = rightB / leftB
|
ratioC = rightC / leftC
|
ratio = (ratioA + ratioB + ratioC) / 3
|
return ratio
|
|
# 本地活体检测
|
def detect_face_local(self):
|
self.detect_start_time = time.time()
|
|
# 特征点检测器首次加载比较慢,通过判断减少后面加载的速度
|
if self.detector is None:
|
self.detector = dlib.get_frontal_face_detector()
|
if self.predictor is None:
|
self.predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
|
|
# 闪烁阈值
|
self.EAR_THRESH = 0.25
|
self.MOUTH_THRESH = 0.7
|
|
# 总闪烁次数
|
self.eye_flash_counter = 0
|
self.mouth_open_counter = 0
|
self.turn_left_counter = 0
|
self.turn_right_counter = 0
|
|
# 连续帧数阈值
|
self.EAR_CONSTANT_FRAMES = 2
|
self.MOUTH_CONSTANT_FRAMES = 2
|
self.LEFT_CONSTANT_FRAMES = 4
|
self.RIGHT_CONSTANT_FRAMES = 4
|
|
# 连续帧计数器
|
self.eye_flash_continuous_frame = 0
|
self.mouth_open_continuous_frame = 0
|
self.turn_left_continuous_frame = 0
|
self.turn_right_continuous_frame = 0
|
|
print("活体检测 初始化时间:", time.time() - self.detect_start_time)
|
|
# 当前总帧数
|
total_frame_counter = 0
|
|
# 设置随机值
|
now_flag = 0
|
random_type = [0, 1, 2, 3]
|
random.shuffle(random_type)
|
|
random_eye_flash_number = random.randint(4, 6)
|
random_mouth_open_number = random.randint(2, 4)
|
print('请按照指示执行相关动作') # 简单输出提示信息
|
|
# 抓取面部特征点的索引
|
(lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
|
(rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
|
(mStart, mEnd) = face_utils.FACIAL_LANDMARKS_IDXS["mouth"]
|
|
while self.cap.isOpened():
|
ret, frame = self.cap.read()
|
total_frame_counter += 1
|
frame = imutils.resize(frame)
|
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
rects = self.detector(gray, 0)
|
|
if len(rects) == 1:
|
shape = self.predictor(gray, rects[0])
|
shape = face_utils.shape_to_np(shape)
|
|
# 提取面部坐标
|
left_eye = shape[lStart:lEnd]
|
right_eye = shape[rStart:rEnd]
|
mouth = shape[mStart:mEnd]
|
|
# 计算长宽比
|
left_EAR = self.count_EAR(left_eye)
|
right_EAR = self.count_EAR(right_eye)
|
mouth_MAR = self.count_MAR(mouth)
|
leftRight_FR = self.count_FR(shape)
|
average_EAR = (left_EAR + right_EAR) / 2.0
|
|
# 计算左眼、右眼、嘴巴的凸包
|
left_eye_hull = cv2.convexHull(left_eye)
|
right_eye_hull = cv2.convexHull(right_eye)
|
mouth_hull = cv2.convexHull(mouth)
|
|
# 可视化
|
cv2.drawContours(frame, [left_eye_hull], -1, (0, 255, 0), 1)
|
cv2.drawContours(frame, [right_eye_hull], -1, (0, 255, 0), 1)
|
cv2.drawContours(frame, [mouth_hull], -1, (0, 255, 0), 1)
|
|
if now_flag >= 4:
|
return True
|
|
if random_type[now_flag] == 0:
|
if self.turn_left_counter > 0:
|
now_flag += 1
|
else:
|
self.check_left_turn(leftRight_FR)
|
self.turn_right_counter = 0
|
self.mouth_open_counter = 0
|
self.eye_flash_counter = 0
|
|
elif random_type[now_flag] == 1:
|
if self.turn_right_counter > 0:
|
now_flag += 1
|
else:
|
self.check_right_turn(leftRight_FR)
|
self.turn_left_counter = 0
|
self.mouth_open_counter = 0
|
self.eye_flash_counter = 0
|
|
elif random_type[now_flag] == 2:
|
if self.mouth_open_counter >= random_mouth_open_number:
|
now_flag += 1
|
|
else:
|
self.check_mouth_open(mouth_MAR)
|
self.turn_right_counter = 0
|
self.turn_left_counter = 0
|
self.eye_flash_counter = 0
|
|
elif random_type[now_flag] == 3:
|
if self.eye_flash_counter >= random_eye_flash_number:
|
now_flag += 1
|
else:
|
self.check_eye_flash(average_EAR)
|
self.turn_right_counter = 0
|
self.turn_left_counter = 0
|
self.mouth_open_counter = 0
|
|
elif len(rects) == 0:
|
pass # 这里可以添加未检测到人脸的提示逻辑
|
|
elif len(rects) > 1:
|
pass # 这里可以添加检测到多张人脸的提示逻辑
|
|
show_video = cv2.cvtColor(cv2.resize(frame, (self.WIN_WIDTH, self.WIN_HEIGHT)), cv2.COLOR_BGR2RGB)
|
# 这里假设你有地方显示画面,实际需要关联到对应的界面元素
|
|
if total_frame_counter >= 1000.0:
|
return False
|
|
def check_eye_flash(self, average_EAR):
|
if average_EAR < self.EAR_THRESH:
|
self.eye_flash_continuous_frame += 1
|
else:
|
if self.eye_flash_continuous_frame >= self.EAR_CONSTANT_FRAMES:
|
self.eye_flash_counter += 1
|
self.eye_flash_continuous_frame = 0
|
|
def check_mouth_open(self, mouth_MAR):
|
if mouth_MAR > self.MOUTH_THRESH:
|
self.mouth_open_continuous_frame += 1
|
else:
|
if self.mouth_open_continuous_frame >= self.MOUTH_CONSTANT_FRAMES:
|
self.mouth_open_counter += 1
|
self.mouth_open_continuous_frame = 0
|
|
def check_right_turn(self, leftRight_FR):
|
if leftRight_FR <= 0.5:
|
self.turn_right_continuous_frame += 1
|
else:
|
if self.turn_right_continuous_frame >= self.RIGHT_CONSTANT_FRAMES:
|
self.turn_right_counter += 1
|
self.turn_right_continuous_frame = 0
|
|
def check_left_turn(self, leftRight_FR):
|
if leftRight_FR >= 2.0:
|
self.turn_left_continuous_frame += 1
|
else:
|
if self.turn_left_continuous_frame >= self.LEFT_CONSTANT_FRAMES:
|
self.turn_left_counter += 1
|
self.turn_left_continuous_frame = 0
|