目录
一、准备工作
1.1 需要的库
1.2准备需要的文件和图片
1.3测试程序
二、人脸识别开发
2.1 录入自己的人脸信息
2.2 提取录入的人脸特征
2.3 实时捕获人脸并进行识别
三、活体检测
3.1 眨眼检测
3.2 张嘴检测
3.3 摇头检测
下面这些是我突发奇想想做来玩玩,就在github上下载了人脸识别的代码(网址下面有附上),用了之后突然想试试照片的识别效果,发现照片也会被识别成我,就查阅了相关资料,一般都是通过活体检测(比如眨眼、转头之类的),想通过算法实现实时视频检测,区分真人和照片。
一、准备工作
开发环境:windows10+pycharm.1.3+python+3.9
1.1 需要的库
opencv-python==4.5.5.64
numpy==1.23.3
dlib==19.24.0
pandas=1.5.0
枕头=9.2.0
dlib库安装方法:
进入虚拟环境后输入:
anaconda search -t conda dlib
conda install -c /conda-forge dlib
1.2准备需要的文件和图片
进入网址:/files/
下载文件:shape_predictor_68_face_landmarks.dat.bz2,解压后存在data文件夹,该模型可以识别人脸68个关键点。网址中还含有5关键点模型文件。
准备人脸照片,存于img文件夹。
1.3测试程序
# -*- coding = utf-8 -*-# @Time : /9/20 9:11# @Author : 李昊芸# @file : dlib_t.py# @Software : PyCharmimport cv2import dlibpath = "img/lhy_1.jpeg"img = cv2.imread(path)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#人脸分类器detector = dlib.get_frontal_face_detector()# 获取人脸检测器predictor = dlib.shape_predictor(".\\data\\shape_predictor_68_face_landmarks.dat")dets = detector(gray, 1)for face in dets:shape = predictor(img, face) # 寻找人脸的68个标定点# 遍历所有点,打印出其坐标,并圈出来for pt in shape.parts():pt_pos = (pt.x, pt.y)cv2.circle(img, pt_pos, 2, (0, 255, 0), 1)cv2.imshow("image", img)cv2.waitKey(0)cv2.destroyAllWindows()
68关键点分类器识别结果:
多人识别效果:
二、人脸识别开发
原github地址:/coneypo/Dlib_face_recognition_from_camera
2.1 录入自己的人脸信息
get_faces_from_camera.py
# Copyright (C) - coneypo# SPDX-License-Identifier: MIT# Author: coneypo# Blog:/AdaminXie# GitHub: /coneypo/Dlib_face_recognition_from_camera# Mail:coneypo@# 进行人脸录入 / Face registerimport dlibimport numpy as npimport cv2import osimport shutilimport timeimport logging# Dlib 正向人脸检测器 / Use frontal face detector of Dlibdetector = dlib.get_frontal_face_detector()class Face_Register:def __init__(self):self.path_photos_from_camera = "data/data_faces_from_camera/"self.font = cv2.FONT_ITALICself.existing_faces_cnt = 0 # 已录入的人脸计数器 / cnt for counting saved facesself.ss_cnt = 0 # 录入 personX 人脸时图片计数器 / cnt for screen shotsself.current_frame_faces_cnt = 0 # 录入人脸计数器 / cnt for counting faces in current frameself.save_flag = 1 # 之后用来控制是否保存图像的 flag / The flag to control if saveself.press_n_flag = 0# 之后用来检查是否先按 'n' 再按 's' / The flag to check if press 'n' before 's'# FPSself.frame_time = 0self.frame_start_time = 0self.fps = 0self.fps_show = 0self.start_time = time.time()# 新建保存人脸图像文件和数据 CSV 文件夹 / Mkdir for saving photos and csvdef pre_work_mkdir(self):# 新建文件夹 / Create folders to save face images and csvif os.path.isdir(self.path_photos_from_camera):passelse:os.mkdir(self.path_photos_from_camera)# 删除之前存的人脸数据文件夹 / Delete old face foldersdef pre_work_del_old_face_folders(self):# 删除之前存的人脸数据文件夹, 删除 "/data_faces_from_camera/person_x/"...folders_rd = os.listdir(self.path_photos_from_camera)for i in range(len(folders_rd)):shutil.rmtree(self.path_photos_from_camera+folders_rd[i])if os.path.isfile("data/features_all.csv"):os.remove("data/features_all.csv")# 如果有之前录入的人脸, 在之前 person_x 的序号按照 person_x+1 开始录入 / Start from person_x+1def check_existing_faces_cnt(self):if os.listdir("data/data_faces_from_camera/"):# 获取已录入的最后一个人脸序号 / Get the order of latest personperson_list = os.listdir("data/data_faces_from_camera/")person_num_list = []for person in person_list:person_num_list.append(int(person.split('_')[-1]))self.existing_faces_cnt = max(person_num_list)# 如果第一次存储或者没有之前录入的人脸, 按照 person_1 开始录入 / Start from person_1else:self.existing_faces_cnt = 0# 更新 FPS / Update FPS of Video streamdef update_fps(self):now = time.time()# 每秒刷新 fps / Refresh fps per secondif str(self.start_time).split(".")[0] != str(now).split(".")[0]:self.fps_show = self.fpsself.start_time = nowself.frame_time = now - self.frame_start_timeself.fps = 1.0 / self.frame_timeself.frame_start_time = now# 生成的 cv2 window 上面添加说明文字 / PutText on cv2 windowdef draw_note(self, img_rd):# 添加说明 / Add some notescv2.putText(img_rd, "Face Register", (20, 40), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(img_rd, "FPS: " + str(self.fps_show.__round__(2)), (20, 100), self.font, 0.8, (0, 255, 0), 1,cv2.LINE_AA)cv2.putText(img_rd, "Faces: " + str(self.current_frame_faces_cnt), (20, 140), self.font, 0.8, (0, 255, 0), 1, cv2.LINE_AA)cv2.putText(img_rd, "N: Create face folder", (20, 350), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(img_rd, "S: Save current face", (20, 400), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(img_rd, "Q: Quit", (20, 450), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)# 获取人脸 / Main process of face detection and savingdef process(self, stream):# 1. 新建储存人脸图像文件目录 / Create folders to save photosself.pre_work_mkdir()# 2. 删除 "/data/data_faces_from_camera" 中已有人脸图像文件# / Uncomment if want to delete the saved faces and start from person_1# if os.path.isdir(self.path_photos_from_camera):#self.pre_work_del_old_face_folders()# 3. 检查 "/data/data_faces_from_camera" 中已有人脸文件self.check_existing_faces_cnt()while stream.isOpened():flag, img_rd = stream.read() # Get camera video streamkk = cv2.waitKey(1)faces = detector(img_rd, 0) # Use Dlib face detector# 4. 按下 'n' 新建存储人脸的文件夹 / Press 'n' to create the folders for saving facesif kk == ord('n'):self.existing_faces_cnt += 1current_face_dir = self.path_photos_from_camera + "person_" + str(self.existing_faces_cnt)os.makedirs(current_face_dir)logging.info("\n%-40s %s", "新建的人脸文件夹 / Create folders:", current_face_dir)self.ss_cnt = 0 # 将人脸计数器清零 / Clear the cnt of screen shotsself.press_n_flag = 1 # 已经按下 'n' / Pressed 'n' already# 5. 检测到人脸 / Face detectedif len(faces) != 0:# 矩形框 / Show the ROI of facesfor k, d in enumerate(faces):# 计算矩形框大小 / Compute the size of rectangle boxheight = (d.bottom() - d.top())width = (d.right() - d.left())hh = int(height/2)ww = int(width/2)# 6. 判断人脸矩形框是否超出 480x640 / If the size of ROI > 480x640if (d.right()+ww) > 640 or (d.bottom()+hh > 480) or (d.left()-ww < 0) or (d.top()-hh < 0):cv2.putText(img_rd, "OUT OF RANGE", (20, 300), self.font, 0.8, (0, 0, 255), 1, cv2.LINE_AA)color_rectangle = (0, 0, 255)save_flag = 0if kk == ord('s'):logging.warning("请调整位置 / Please adjust your position")else:color_rectangle = (255, 255, 255)save_flag = 1cv2.rectangle(img_rd,tuple([d.left() - ww, d.top() - hh]),tuple([d.right() + ww, d.bottom() + hh]),color_rectangle, 2)# 7. 根据人脸大小生成空的图像 / Create blank image according to the size of face detectedimg_blank = np.zeros((int(height*2), width*2, 3), np.uint8)if save_flag:# 8. 按下 's' 保存摄像头中的人脸到本地 / Press 's' to save faces into local imagesif kk == ord('s'):# 检查有没有先按'n'新建文件夹 / Check if you have pressed 'n'if self.press_n_flag:self.ss_cnt += 1for ii in range(height*2):for jj in range(width*2):img_blank[ii][jj] = img_rd[d.top()-hh + ii][d.left()-ww + jj]cv2.imwrite(current_face_dir + "/img_face_" + str(self.ss_cnt) + ".jpg", img_blank)logging.info("%-40s %s/img_face_%s.jpg", "写入本地 / Save into:",str(current_face_dir), str(self.ss_cnt))else:logging.warning("请先按 'N' 来建文件夹, 按 'S' / Please press 'N' and press 'S'")self.current_frame_faces_cnt = len(faces)# 9. 生成的窗口添加说明文字 / Add note on cv2 windowself.draw_note(img_rd)# 10. 按下 'q' 键退出 / Press 'q' to exitif kk == ord('q'):break# 11. Update FPSself.update_fps()cv2.namedWindow("camera", 1)cv2.imshow("camera", img_rd)def run(self):# cap = cv2.VideoCapture("video.mp4") # Get video stream from video filecap = cv2.VideoCapture(0)# Get video stream from cameraself.process(cap)cap.release()cv2.destroyAllWindows()def main():logging.basicConfig(level=logging.INFO)Face_Register_con = Face_Register()Face_Register_con.run()if __name__ == '__main__':main()
2.2 提取录入的人脸特征
features_extraction_to_csv.py
# Copyright (C) - coneypo# SPDX-License-Identifier: MIT# Author: coneypo# Blog:/AdaminXie# GitHub: /coneypo/Dlib_face_recognition_from_camera# Mail:coneypo@# 从人脸图像文件中提取人脸特征存入 "features_all.csv" / Extract features from images and save into "features_all.csv"import osimport dlibimport csvimport numpy as npimport loggingimport cv2# 要读取人脸图像文件的路径 / Path of cropped facespath_images_from_camera = "data/data_faces_from_camera/"# Dlib 正向人脸检测器 / Use frontal face detector of Dlibdetector = dlib.get_frontal_face_detector()# Dlib 人脸 landmark 特征点检测器 / Get face landmarkspredictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')# Dlib Resnet 人脸识别模型,提取 128D 的特征矢量 / Use Dlib resnet50 model to get 128D face descriptorface_reco_model = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")# 返回单张图像的 128D 特征 / Return 128D features for single image# Input: path_img <class 'str'># Output: face_descriptor <class 'dlib.vector'>def return_128d_features(path_img):img_rd = cv2.imread(path_img)faces = detector(img_rd, 1)logging.info("%-40s %-20s", "检测到人脸的图像 / Image with faces detected:", path_img)# 因为有可能截下来的人脸再去检测,检测不出来人脸了, 所以要确保是 检测到人脸的人脸图像拿去算特征# For photos of faces saved, we need to make sure that we can detect faces from the cropped imagesif len(faces) != 0:shape = predictor(img_rd, faces[0])face_descriptor = pute_face_descriptor(img_rd, shape)else:face_descriptor = 0logging.warning("no face")return face_descriptor# 返回 personX 的 128D 特征均值 / Return the mean value of 128D face descriptor for person X# Input: path_face_personX <class 'str'># Output: features_mean_personX <class 'numpy.ndarray'>def return_features_mean_personX(path_face_personX):features_list_personX = []photos_list = os.listdir(path_face_personX)if photos_list:for i in range(len(photos_list)):# 调用 return_128d_features() 得到 128D 特征 / Get 128D features for single image of personXlogging.info("%-40s %-20s", "正在读的人脸图像 / Reading image:", path_face_personX + "/" + photos_list[i])features_128d = return_128d_features(path_face_personX + "/" + photos_list[i])# 遇到没有检测出人脸的图片跳过 / Jump if no face detected from imageif features_128d == 0:i += 1else:features_list_personX.append(features_128d)else:logging.warning("文件夹内图像文件为空 / Warning: No images in%s/", path_face_personX)# 计算 128D 特征的均值 / Compute the mean# personX 的 N 张图像 x 128D -> 1 x 128Dif features_list_personX:features_mean_personX = np.array(features_list_personX, dtype=object).mean(axis=0)else:features_mean_personX = np.zeros(128, dtype=object, order='C')return features_mean_personXdef main():logging.basicConfig(level=logging.INFO)# 获取已录入的最后一个人脸序号 / Get the order of latest personperson_list = os.listdir("data/data_faces_from_camera/")person_list.sort()with open("data/features_all.csv", "w", newline="") as csvfile:writer = csv.writer(csvfile)for person in person_list:# Get the mean/average features of face/personX, it will be a list with a length of 128Dlogging.info("%sperson_%s", path_images_from_camera, person)features_mean_personX = return_features_mean_personX(path_images_from_camera + person)if len(person.split('_', 2)) == 2:# "person_x"person_name = personelse:# "person_x_tom"person_name = person.split('_', 2)[-1]features_mean_personX = np.insert(features_mean_personX, 0, person_name, axis=0)# features_mean_personX will be 129D, person name + 128 featureswriter.writerow(features_mean_personX)logging.info('\n')logging.info("所有录入人脸数据存入 / Save all the features of faces registered into: data/features_all.csv")if __name__ == '__main__':main()
2.3 实时捕获人脸并进行识别
face_reco_from_camera.py
# Copyright (C) - coneypo# SPDX-License-Identifier: MIT# Author: coneypo# Blog:/AdaminXie# GitHub: /coneypo/Dlib_face_recognition_from_camera# Mail:coneypo@# 摄像头实时人脸识别 / Real-time face detection and recognitionimport dlibimport numpy as npimport cv2import pandas as pdimport osimport timeimport loggingfrom PIL import Image, ImageDraw, ImageFont# Dlib 正向人脸检测器 / Use frontal face detector of Dlibdetector = dlib.get_frontal_face_detector()# Dlib 人脸 landmark 特征点检测器 / Get face landmarkspredictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')# Dlib Resnet 人脸识别模型,提取 128D 的特征矢量 / Use Dlib resnet50 model to get 128D face descriptorface_reco_model = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")class Face_Recognizer:def __init__(self):self.face_feature_known_list = []# 用来存放所有录入人脸特征的数组 / Save the features of faces in databaseself.face_name_known_list = [] # 存储录入人脸名字 / Save the name of faces in databaseself.current_frame_face_cnt = 0 # 存储当前摄像头中捕获到的人脸数 / Counter for faces in current frameself.current_frame_face_feature_list = [] # 存储当前摄像头中捕获到的人脸特征 / Features of faces in current frameself.current_frame_face_name_list = [] # 存储当前摄像头中捕获到的所有人脸的名字 / Names of faces in current frameself.current_frame_face_name_position_list = []# 存储当前摄像头中捕获到的所有人脸的名字坐标 / Positions of faces in current frame# Update FPSself.fps = 0# FPS of current frameself.fps_show = 0# FPS per secondself.frame_start_time = 0self.frame_cnt = 0self.start_time = time.time()self.font = cv2.FONT_ITALICself.font_chinese = ImageFont.truetype("simsun.ttc", 30)# 从 "features_all.csv" 读取录入人脸特征 / Read known faces from "features_all.csv"def get_face_database(self):if os.path.exists("data/features_all.csv"):path_features_known_csv = "data/features_all.csv"csv_rd = pd.read_csv(path_features_known_csv, header=None)for i in range(csv_rd.shape[0]):features_someone_arr = []self.face_name_known_list.append(csv_rd.iloc[i][0])for j in range(1, 129):if csv_rd.iloc[i][j] == '':features_someone_arr.append('0')else:features_someone_arr.append(csv_rd.iloc[i][j])self.face_feature_known_list.append(features_someone_arr)logging.info("Faces in Database:%d", len(self.face_feature_known_list))return 1else:logging.warning("'features_all.csv' not found!")logging.warning("Please run 'get_faces_from_camera.py' ""and 'features_extraction_to_csv.py' before 'face_reco_from_camera.py'")return 0# 计算两个128D向量间的欧式距离 / Compute the e-distance between two 128D features@staticmethoddef return_euclidean_distance(feature_1, feature_2):feature_1 = np.array(feature_1)feature_2 = np.array(feature_2)dist = np.sqrt(np.sum(np.square(feature_1 - feature_2)))return dist# 更新 FPS / Update FPS of Video streamdef update_fps(self):now = time.time()# 每秒刷新 fps / Refresh fps per secondif str(self.start_time).split(".")[0] != str(now).split(".")[0]:self.fps_show = self.fpsself.start_time = nowself.frame_time = now - self.frame_start_timeself.fps = 1.0 / self.frame_timeself.frame_start_time = now# 生成的 cv2 window 上面添加说明文字 / PutText on cv2 windowdef draw_note(self, img_rd):cv2.putText(img_rd, "Face Recognizer", (20, 40), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(img_rd, "Frame: " + str(self.frame_cnt), (20, 100), self.font, 0.8, (0, 255, 0), 1,cv2.LINE_AA)cv2.putText(img_rd, "FPS: " + str(self.fps_show.__round__(2)), (20, 130), self.font, 0.8, (0, 255, 0), 1,cv2.LINE_AA)cv2.putText(img_rd, "Faces: " + str(self.current_frame_face_cnt), (20, 160), self.font, 0.8, (0, 255, 0), 1,cv2.LINE_AA)cv2.putText(img_rd, "Q: Quit", (20, 450), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)def draw_name(self, img_rd):# 在人脸框下面写人脸名字 / Write names under rectangleimg = Image.fromarray(cv2.cvtColor(img_rd, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(img)for i in range(self.current_frame_face_cnt):# cv2.putText(img_rd, self.current_frame_face_name_list[i], self.current_frame_face_name_position_list[i], self.font, 0.8, (0, 255, 255), 1, cv2.LINE_AA)draw.text(xy=self.current_frame_face_name_position_list[i], text=self.current_frame_face_name_list[i], font=self.font_chinese,fill=(255, 255, 0))img_rd = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)return img_rd# 修改显示人名 / Show names in chinesedef show_chinese_name(self):# Default known name: person_1, person_2, person_3if self.current_frame_face_cnt >= 1:# 修改录入的人脸姓名 / Modify names in face_name_known_list to chinese nameself.face_name_known_list[0] = '张三'.encode('utf-8').decode()# self.face_name_known_list[1] = '张四'.encode('utf-8').decode()# 处理获取的视频流,进行人脸识别 / Face detection and recognition from input video streamdef process(self, stream):# 1. 读取存放所有人脸特征的 csv / Read known faces from "features.all.csv"if self.get_face_database():while stream.isOpened():self.frame_cnt += 1logging.debug("Frame %d starts", self.frame_cnt)flag, img_rd = stream.read()faces = detector(img_rd, 0)kk = cv2.waitKey(1)# 按下 q 键退出 / Press 'q' to quitif kk == ord('q'):breakelse:self.draw_note(img_rd)self.current_frame_face_feature_list = []self.current_frame_face_cnt = 0self.current_frame_face_name_position_list = []self.current_frame_face_name_list = []# 2. 检测到人脸 / Face detected in current frameif len(faces) != 0:# 3. 获取当前捕获到的图像的所有人脸的特征 / Compute the face descriptors for faces in current framefor i in range(len(faces)):shape = predictor(img_rd, faces[i])self.current_frame_face_feature_list.append(pute_face_descriptor(img_rd, shape))# 4. 遍历捕获到的图像中所有的人脸 / Traversal all the faces in the databasefor k in range(len(faces)):logging.debug("For face %d in camera:", k+1)# 先默认所有人不认识,是 unknown / Set the default names of faces with "unknown"self.current_frame_face_name_list.append("unknown")# 每个捕获人脸的名字坐标 / Positions of faces capturedself.current_frame_face_name_position_list.append(tuple([faces[k].left(), int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)]))# 5. 对于某张人脸,遍历所有存储的人脸特征# For every faces detected, compare the faces in the databasecurrent_frame_e_distance_list = []for i in range(len(self.face_feature_known_list)):# 如果 person_X 数据不为空if str(self.face_feature_known_list[i][0]) != '0.0':e_distance_tmp = self.return_euclidean_distance(self.current_frame_face_feature_list[k],self.face_feature_known_list[i])logging.debug(" With person %s, the e-distance is %f", str(i + 1), e_distance_tmp)current_frame_e_distance_list.append(e_distance_tmp)else:# 空数据 person_Xcurrent_frame_e_distance_list.append(999999999)# 6. 寻找出最小的欧式距离匹配 / Find the one with minimum e-distancesimilar_person_num = current_frame_e_distance_list.index(min(current_frame_e_distance_list))logging.debug("Minimum e-distance with %s: %f", self.face_name_known_list[similar_person_num], min(current_frame_e_distance_list))if min(current_frame_e_distance_list) < 0.4:self.current_frame_face_name_list[k] = self.face_name_known_list[similar_person_num]logging.debug("Face recognition result: %s", self.face_name_known_list[similar_person_num])else:logging.debug("Face recognition result: Unknown person")logging.debug("\n")# 矩形框 / Draw rectanglefor kk, d in enumerate(faces):# 绘制矩形框cv2.rectangle(img_rd, tuple([d.left(), d.top()]), tuple([d.right(), d.bottom()]),(255, 255, 255), 2)self.current_frame_face_cnt = len(faces)# 7. 在这里更改显示的人名 / Modify name if needed# self.show_chinese_name()# 8. 写名字 / Draw nameimg_with_name = self.draw_name(img_rd)else:img_with_name = img_rdlogging.debug("Faces in camera now: %s", self.current_frame_face_name_list)cv2.imshow("camera", img_with_name)# 9. 更新 FPS / Update stream FPSself.update_fps()logging.debug("Frame ends\n\n")# OpenCV 调用摄像头并进行 processdef run(self):# cap = cv2.VideoCapture("video.mp4") # Get video stream from video filecap = cv2.VideoCapture(0) # Get video stream from cameracap.set(3, 480) # 640x480self.process(cap)cap.release()cv2.destroyAllWindows()def main():# logging.basicConfig(level=logging.DEBUG) # Set log level to 'logging.DEBUG' to print debug info of every framelogging.basicConfig(level=logging.INFO)Face_Recognizer_con = Face_Recognizer()Face_Recognizer_con.run()if __name__ == '__main__':main()
识别结果(我和我的无美颜照片):
(自己的脸,马赛克一下)
可以看出, 识别效果还是很不错的。
三、活体检测
3.1 眨眼检测
经过查询资料,得到广为人使用的一个指标:眼睛纵横比(EAR)
分别得到两只眼睛的纵横比并取平均值,作为眨眼的指标,经过多次测试后,选取0.3作为阈值。
在连续检测到两次EAR小于阈值,即眼睛一睁一闭时,我们将记录为一次眨眼。
代码如下,实验结果如图:
from scipy.spatial import distance as distfrom imutils.video import VideoStreamfrom imutils import face_utilsimport imutilsimport timeimport dlibimport cv2def EAR(eye):# 计算眼睛的两组垂直关键点之间的欧式距离A = dist.euclidean(eye[1], eye[5])# 1,5是一组垂直关键点B = dist.euclidean(eye[2], eye[4])# 2,4是一组# 计算眼睛的一组水平关键点之间的欧式距离C = dist.euclidean(eye[0], eye[3])# 0,3是一组水平关键点return (A + B) / (2.0 * C)def main():EAR_THRESH = 0.3# 眨眼阈值EYE_close = 2# 闭眼次数阈值# 初始化眨眼帧计数器和总眨眼次数count_eye = 0total = 0detector = dlib.get_frontal_face_detector()predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')# 左右眼的索引(lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"](rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]vs = VideoStream(src=0).start()time.sleep(1.0)while True:frame = vs.read()frame = imutils.resize(frame, width=600)gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 在灰度框中检测人脸rects = detector(gray, 0)# 进入循环for rect in rects:shape = predictor(gray, rect)shape = face_utils.shape_to_np(shape)# 提取左眼和右眼坐标,然后使用该坐标计算两只眼睛的眼睛纵横比leftEye = shape[lStart:lEnd]rightEye = shape[rStart:rEnd]ear = EAR(leftEye) + EAR(rightEye) / 2.0# 判断眼睛纵横比是否低于眨眼阈值if ear < EAR_THRESH:count_eye += 1else:# 检测到一次闭眼if count_eye >= EYE_close:total += 1count_eye = 0# 画出画框上眨眼的总次数以及计算出的帧的眼睛纵横比cv2.putText(frame, "Blinks: {}".format(total), (10, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)cv2.putText(frame, "EAR: {:.2f}".format(ear), (300, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)cv2.imshow("Frame", frame)key = cv2.waitKey(1) & 0xFFif key == ord("q"):breakcv2.destroyAllWindows()vs.stop()if __name__ == '__main__':main()
3.2 张嘴检测
同理,可以做张嘴检测:
from imutils.video import VideoStreamfrom imutils import face_utilsimport imutilsimport timeimport dlibimport cv2import numpy as npdef MAR(mouth):# 默认二范数:求特征值,然后求最大特征值得算术平方根A = np.linalg.norm(mouth[2] - mouth[9]) # 51, 59(人脸68个关键点)B = np.linalg.norm(mouth[4] - mouth[7]) # 53, 57C = np.linalg.norm(mouth[0] - mouth[6]) # 49, 55return (A + B) / (2.0 * C)def main():MAR_THRESH = 0.5# 张嘴阈值# 初始化COUNTER_MOUTH = 0TOTAL_MOUTH = 0detector = dlib.get_frontal_face_detector()predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')# 嘴的索引(mStart, mEnd) = face_utils.FACIAL_LANDMARKS_IDXS["mouth"]vs = VideoStream(src=0).start()time.sleep(1.0)while True:frame = vs.read()frame = imutils.resize(frame, width=600)gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 在灰度框中检测人脸rects = detector(gray, 0)# 进入循环for rect in rects:shape = predictor(gray, rect)shape = face_utils.shape_to_np(shape)# 提取嘴唇坐标,然后使用该坐标计算嘴唇纵横比Mouth = shape[mStart:mEnd]mar = MAR(Mouth)# 判断嘴唇纵横比是否高于张嘴阈值,如果是,则增加张嘴帧计数器if mar > MAR_THRESH:COUNTER_MOUTH += 1else:# 如果张嘴帧计数器不等于0,则增加张嘴的总次数if COUNTER_MOUTH >= 2:TOTAL_MOUTH += 1COUNTER_MOUTH = 0cv2.putText(frame, "Mouth is open: {}".format(TOTAL_MOUTH), (10, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)cv2.putText(frame, "MAR: {:.2f}".format(mar), (300, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)cv2.imshow("Frame", frame)key = cv2.waitKey(1) & 0xFFif key == ord("q"):breakcv2.destroyAllWindows()vs.stop()if __name__ == '__main__':main()
3.3 摇头检测
在手机很多App如支付宝中,摇头也属于非常常见的活体检测手段,此项采用鼻子到左右脸的欧氏距离变化,判断是否摇头。
代码如下:
from scipy.spatial import distance as distfrom imutils.video import VideoStreamfrom imutils import face_utilsimport imutilsimport timeimport dlibimport cv2def nose_jaw_distance(nose, jaw):# 计算鼻子上一点"27"到左右脸边界的欧式距离face_left1 = dist.euclidean(nose[0], jaw[0])# 27, 0face_right1 = dist.euclidean(nose[0], jaw[16])# 27, 16# 计算鼻子上一点"30"到左右脸边界的欧式距离face_left2 = dist.euclidean(nose[3], jaw[2]) # 30, 2face_right2 = dist.euclidean(nose[3], jaw[14]) # 30, 14# 创建元组,用以保存4个欧式距离值face_distance = (face_left1, face_right1, face_left2, face_right2)return face_distancedef main():# 初始化眨眼帧计数器和总眨眼次数distance_left = 0distance_right = 0TOTAL_FACE = 0detector = dlib.get_frontal_face_detector()predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')(nStart, nEnd) = face_utils.FACIAL_LANDMARKS_IDXS["nose"](jStart, jEnd) = face_utils.FACIAL_LANDMARKS_IDXS['jaw']vs = VideoStream(src=0).start()time.sleep(1.0)while True:frame = vs.read()frame = imutils.resize(frame, width=600)gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 在灰度框中检测人脸rects = detector(gray, 0)# 进入循环for rect in rects:shape = predictor(gray, rect)shape = face_utils.shape_to_np(shape)# 提取鼻子和下巴的坐标,然后使用该坐标计算鼻子到左右脸边界的欧式距离nose = shape[nStart:nEnd]jaw = shape[jStart:jEnd]NOSE_JAW_Distance = nose_jaw_distance(nose, jaw)# 移植鼻子到左右脸边界的欧式距离face_left1 = NOSE_JAW_Distance[0]face_right1 = NOSE_JAW_Distance[1]face_left2 = NOSE_JAW_Distance[2]face_right2 = NOSE_JAW_Distance[3]# 根据鼻子到左右脸边界的欧式距离,判断是否摇头# 左脸大于右脸if face_left1 >= face_right1 + 2 and face_left2 >= face_right2 + 2:distance_left += 1# 右脸大于左脸if face_right1 >= face_left1 + 2 and face_right2 >= face_left2 + 2:distance_right += 1# 左脸大于右脸,并且右脸大于左脸,判定摇头if distance_left != 0 and distance_right != 0:TOTAL_FACE += 1distance_right = 0distance_left = 0# 画出摇头次数cv2.putText(frame, "shake one's head: {}".format(TOTAL_FACE), (10, 90),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)cv2.imshow("Frame", frame)key = cv2.waitKey(1) & 0xFFif key == ord("q"):breakcv2.destroyAllWindows()vs.stop()if __name__ == '__main__':main()