Python 3 利用 Dlib 实现摄像头实时人脸识别

0. 引言

利用 Python 开发,对于输入的摄像头视频流,借助 Dlib 提供的检测识别模型来进行人脸识别;

 

首先,从摄像头中录入(裁剪)人脸图片存储到本地,然后提取特征,构建预设人脸特征;

对于 person_x,根据录入的多张人脸图片提取 128D 特征值,然后计算该人的 128D 特征均值;

然后和摄像头中实时获取到的人脸提取出的特征值,计算欧氏距离,判定是否为同一张人脸;  

 

识别模型:基于 Dlib 的 ResNet 预训练模型(dlib_face_recognition_resnet_model_v1.dat)

识别算法:ResNet 神经网络(This model is a ResNet network with 29 conv layers. It's essentially a version of the ResNet-34 network from the paper Deep Residual Learning for Image Recognition by He, Zhang, Ren, and Sun with a few layers removed and the number of filters per layer reduced by half)

 

 

 

 

调用的模型以及大概的耗时:

 

博客中代码以 GitHub 为准,博客中可能没有及时更新;

  # Blog :    http://www.cnblogs.com/AdaminXie
  # GitHub :   https://github.com/coneypo/Dlib_face_recognition_from_camera

支持的功能:

  • 支持人脸数据采集,自行建立人脸数据库
  • 支持录入人脸的时候设置姓名
  • 调用摄像头实时人脸检测和识别 
  • 支持同时识别多张人脸
  • 支持中文姓名显示
  • 利用 OT 来加速识别

 

人脸识别 / Face Recognition 的说明:

Wikipedia 上关于人脸识别系统 / Face Recognition System 的描述:they work by comparing selected facial features from given image with faces within a database.

本项目中就是比较 预设的人脸的特征 摄像头实时获取到的人脸的特征 

核心就是 提取 128D 人脸特征,然后计算 摄像头人脸特征 和 预设的特征脸的欧式距离,进行比对;

 

效果如下:

   

图 1 多人脸实时识别效果 

1. 总体流程

先说下 人脸检测 ( Face detection ) 人脸识别 ( Face Recognition ) ,前者是达到检测出场景中人脸的目的就可以了,而后者不仅需要检测出人脸,还要和已有人脸数据进行比对,识别出是否在数据库中,或者进行身份标注之类处理,人脸检测和人脸识别两者有时候可能会被理解混淆;

利用“dlib_face_recognition_resnet_model_v1.dat” 这个训练好的 resnet-34 模型,提取 人脸图像的 128D 特征,然后比对不同人脸图片的 128D 特征的欧式距离,设定一个 阈值 来判断是否为同一张脸;

1 # face recognition model, the object maps human faces into 128D vectors
2 facerec = dlib.face_recognition_model_v1("dlib_face_recognition_resnet_model_v1.dat")
3 
4 shape = predictor(img, dets[0])
5 face_descriptor = facerec.compute_face_descriptor(img, shape)

 

图 2 总体设计流程

 

2. 源码介绍

有三块代码,分别是人脸录入,特征提取和人脸识别:

  • get_faces_from_camera.py 
  • features_extraction_to_csv.py
  • face_reco_from_camera.py

2.1 get_faces_from_camera.py / 人脸注册录入

人脸识别需要将 提取到的图像数据 和 已有图像数据 进行比对分析,所以这部分代码实现的功能就是 人脸录入

程序会生成一个窗口,显示调用的摄像头实时获取的图像;

(关于摄像头的调用方式可以参考这里: Python 3 利用 Dlib 19.7 实现摄像头人脸检测特征点标定);

 

2.1.1 GUI with OpenCV

利用 OpenCV 生成的窗口界面,用户通过键盘输入进行人脸捕获:

  • “N” 新录入人脸,新建文件夹 person_X/  用来存储某人的人脸图像
  •   "S" 开始捕获人脸,将捕获到的人脸放到 person_X/ 路径下
  • “Q” 退出窗口

摄像头的调用是利用 opencv 库的 cv2.VideoCapture(0), 此处参数为 0 代表调用的是笔记本的默认摄像头,你也可以让它调用传入已有视频文件;

可以参考 https://github.com/coneypo/Dlib_face_recognition_from_camera/blob/master/how_to_use_camera.py 如何通过 OpenCV 调用摄像头; 

图 3  get_face_from_camera.py 的界面

   

“N”+“S”之后捕获到的一组人脸示例;

图 4 捕获到的一组人脸

 

get_faces_from_camera.py 源码:

  1 # Copyright (C) 2020 coneypo
  2 # SPDX-License-Identifier: MIT
  3 
  4 # Author:   coneypo
  5 # Blog:     http://www.cnblogs.com/AdaminXie
  6 # GitHub:   https://github.com/coneypo/Dlib_face_recognition_from_camera
  7 # Mail:     coneypo@foxmail.com
  8 
  9 # 进行人脸录入 / Face register
 10 
 11 import dlib
 12 import numpy as np
 13 import cv2
 14 import os
 15 import shutil
 16 import time
 17 
 18 # Dlib 正向人脸检测器 / Use frontal face detector of Dlib
 19 detector = dlib.get_frontal_face_detector()
 20 
 21 
 22 class Face_Register:
 23     def __init__(self):
 24         self.path_photos_from_camera = "data/data_faces_from_camera/"
 25         self.font = cv2.FONT_ITALIC
 26 
 27         self.existing_faces_cnt = 0         # 已录入的人脸计数器 / cnt for counting saved faces
 28         self.ss_cnt = 0                     # 录入 personX 人脸时图片计数器 / cnt for screen shots
 29         self.current_frame_faces_cnt = 0    # 录入人脸计数器 / cnt for counting faces in current frame
 30 
 31         self.save_flag = 1                  # 之后用来控制是否保存图像的 flag / The flag to control if save
 32         self.press_n_flag = 0               # 之后用来检查是否先按 'n' 再按 's' / The flag to check if press 'n' before 's'
 33 
 34         # FPS
 35         self.frame_time = 0
 36         self.frame_start_time = 0
 37         self.fps = 0
 38 
 39     # 新建保存人脸图像文件和数据CSV文件夹 / Make dir for saving photos and csv
 40     def pre_work_mkdir(self):
 41         # 新建文件夹 / Create folders to save faces images and csv
 42         if os.path.isdir(self.path_photos_from_camera):
 43             pass
 44         else:
 45             os.mkdir(self.path_photos_from_camera)
 46 
 47     # 删除之前存的人脸数据文件夹 / Delete the old data of faces
 48     def pre_work_del_old_face_folders(self):
 49         # 删除之前存的人脸数据文件夹, 删除 "/data_faces_from_camera/person_x/"...
 50         folders_rd = os.listdir(self.path_photos_from_camera)
 51         for i in range(len(folders_rd)):
 52             shutil.rmtree(self.path_photos_from_camera+folders_rd[i])
 53         if os.path.isfile("data/features_all.csv"):
 54             os.remove("data/features_all.csv")
 55 
 56     # 如果有之前录入的人脸, 在之前 person_x 的序号按照 person_x+1 开始录入 / Start from person_x+1
 57     def check_existing_faces_cnt(self):
 58         if os.listdir("data/data_faces_from_camera/"):
 59             # 获取已录入的最后一个人脸序号 / Get the order of latest person
 60             person_list = os.listdir("data/data_faces_from_camera/")
 61             person_num_list = []
 62             for person in person_list:
 63                 person_num_list.append(int(person.split('_')[-1]))
 64             self.existing_faces_cnt = max(person_num_list)
 65 
 66         # 如果第一次存储或者没有之前录入的人脸, 按照 person_1 开始录入 / Start from person_1
 67         else:
 68             self.existing_faces_cnt = 0
 69 
 70     # 获取处理之后 stream 的帧数 / Update FPS of video stream
 71     def update_fps(self):
 72         now = time.time()
 73         self.frame_time = now - self.frame_start_time
 74         self.fps = 1.0 / self.frame_time
 75         self.frame_start_time = now
 76 
 77     # 生成的 cv2 window 上面添加说明文字 / PutText on cv2 window
 78     def draw_note(self, img_rd):
 79         # 添加说明 / Add some notes
 80         cv2.putText(img_rd, "Face Register", (20, 40), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA)
 81         cv2.putText(img_rd, "FPS:   " + str(self.fps.__round__(2)), (20, 100), self.font, 0.8, (0, 255, 0), 1,
 82                     cv2.LINE_AA)
 83         cv2.putText(img_rd, "Faces: " + str(self.current_frame_faces_cnt), (20, 140), self.font, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
 84         cv2.putText(img_rd, "N: Create face folder", (20, 350), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
 85         cv2.putText(img_rd, "S: Save current face", (20, 400), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
 86         cv2.putText(img_rd, "Q: Quit", (20, 450), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
 87 
 88     # 获取人脸 / Main process of face detection and saving
 89     def process(self, stream):
 90         # 1. 新建储存人脸图像文件目录 / Create folders to save photos
 91         self.pre_work_mkdir()
 92 
 93         # 2. 删除 "/data/data_faces_from_camera" 中已有人脸图像文件 / Uncomment if want to delete the saved faces and start from person_1
 94         if os.path.isdir(self.path_photos_from_camera):
 95             self.pre_work_del_old_face_folders()
 96 
 97         # 3. 检查 "/data/data_faces_from_camera" 中已有人脸文件
 98         self.check_existing_faces_cnt()
 99 
100         while stream.isOpened():
101             flag, img_rd = stream.read()        # Get camera video stream
102             kk = cv2.waitKey(1)
103             faces = detector(img_rd, 0)         # Use Dlib face detector
104 
105             # 4. 按下 'n' 新建存储人脸的文件夹 / Press 'n' to create the folders for saving faces
106             if kk == ord('n'):
107                 self.existing_faces_cnt += 1
108                 current_face_dir = self.path_photos_from_camera + "person_" + str(self.existing_faces_cnt)
109                 os.makedirs(current_face_dir)
110                 print('\n')
111                 print("新建的人脸文件夹 / Create folders: ", current_face_dir)
112 
113                 self.ss_cnt = 0                 # 将人脸计数器清零 / Clear the cnt of screen shots
114                 self.press_n_flag = 1           # 已经按下 'n' / Pressed 'n' already
115 
116             # 5. 检测到人脸 / Face detected
117             if len(faces) != 0:
118                 # 矩形框 / Show the ROI of faces
119                 for k, d in enumerate(faces):
120                     # 计算矩形框大小 / Compute the size of rectangle box
121                     height = (d.bottom() - d.top())
122                     width = (d.right() - d.left())
123                     hh = int(height/2)
124                     ww = int(width/2)
125 
126                     # 6. 判断人脸矩形框是否超出 480x640 / If the size of ROI > 480x640
127                     if (d.right()+ww) > 640 or (d.bottom()+hh > 480) or (d.left()-ww < 0) or (d.top()-hh < 0):
128                         cv2.putText(img_rd, "OUT OF RANGE", (20, 300), self.font, 0.8, (0, 0, 255), 1, cv2.LINE_AA)
129                         color_rectangle = (0, 0, 255)
130                         save_flag = 0
131                         if kk == ord('s'):
132                             print("请调整位置 / Please adjust your position")
133                     else:
134                         color_rectangle = (255, 255, 255)
135                         save_flag = 1
136 
137                     cv2.rectangle(img_rd,
138                                   tuple([d.left() - ww, d.top() - hh]),
139                                   tuple([d.right() + ww, d.bottom() + hh]),
140                                   color_rectangle, 2)
141 
142                     # 7. 根据人脸大小生成空的图像 / Create blank image according to the size of face detected
143                     img_blank = np.zeros((int(height*2), width*2, 3), np.uint8)
144 
145                     if save_flag:
146                         # 8. 按下 's' 保存摄像头中的人脸到本地 / Press 's' to save faces into local images
147                         if kk == ord('s'):
148                             # 检查有没有先按'n'新建文件夹 / Check if you have pressed 'n'
149                             if self.press_n_flag:
150                                 self.ss_cnt += 1
151                                 for ii in range(height*2):
152                                     for jj in range(width*2):
153                                         img_blank[ii][jj] = img_rd[d.top()-hh + ii][d.left()-ww + jj]
154                                 cv2.imwrite(current_face_dir + "/img_face_" + str(self.ss_cnt) + ".jpg", img_blank)
155                                 print("写入本地 / Save into:", str(current_face_dir) + "/img_face_" + str(self.ss_cnt) + ".jpg")
156                             else:
157                                 print("请先按 'N' 来建文件夹, 按 'S' / Please press 'N' and press 'S'")
158 
159             self.current_frame_faces_cnt = len(faces)
160 
161             # 9. 生成的窗口添加说明文字 / Add note on cv2 window
162             self.draw_note(img_rd)
163 
164             # 10. 按下 'q' 键退出 / Press 'q' to exit
165             if kk == ord('q'):
166                 break
167 
168             # 11. Update FPS
169             self.update_fps()
170 
171             cv2.namedWindow("camera", 1)
172             cv2.imshow("camera", img_rd)
173 
174     def run(self):
175         cap = cv2.VideoCapture(0)
176         self.process(cap)
177 
178         cap.release()
179         cv2.destroyAllWindows()
180 
181 
182 def main():
183     Face_Register_con = Face_Register()
184     Face_Register_con.run()
185 
186 
187 if __name__ == '__main__':
188     main()

 

考虑到有可能需要保存的矩形框超出摄像头范围,对于这种异常,如果矩形框超出范围,矩形框会从白变红,然后提示 "OUT OF RANGE",这时候不允许用户保存人脸图片;

图 5 人脸录入异常(Out of range)处理

 

get_face_from_camera.py 的输出 log:

新建的人脸文件夹 / Create folders:  data/data_faces_from_camera/person_1
写入本地 / Save into: data/data_faces_from_camera/person_1/img_face_1.jpg
写入本地 / Save into: data/data_faces_from_camera/person_1/img_face_2.jpg
写入本地 / Save into: data/data_faces_from_camera/person_1/img_face_3.jpg
写入本地 / Save into: data/data_faces_from_camera/person_1/img_face_4.jpg


新建的人脸文件夹 / Create folders:  data/data_faces_from_camera/person_2
写入本地 / Save into: data/data_faces_from_camera/person_2/img_face_1.jpg
写入本地 / Save into: data/data_faces_from_camera/person_2/img_face_2.jpg


新建的人脸文件夹 / Create folders:  data/data_faces_from_camera/person_3
写入本地 / Save into: data/data_faces_from_camera/person_3/img_face_1.jpg
写入本地 / Save into: data/data_faces_from_camera/person_3/img_face_2.jpg

 

2.1.2 GUI with Tkinter

利用 Tkinter 生成的 GUI,用户界面更加丰富,也可以在该界面,录入人脸的时候设置姓名;

  • 如果需要,点击 “Clear” 来删除 /data/data_faces_from_camera/" 下的所有图片和 "features_all.csv"
  • 直接点击 "Input",会在人脸文件夹,相应的生成 "person_1/" ...
  • 输入姓名,比如“Tom”,然后再点击 'Input",会生成 "person_1_Tom/" ..
  • 如果 "/data/data_faces_from_camera/" 目录下已有 person_x,会自动从 person_x+1 开始新建文件夹;

 

点击 Step 3 中的按钮“Save current face”,将当前帧中检测到的人脸保存下来;

 

2.2 features_extraction_to_csv.py

这部分代码实现的功能是将之前捕获到的人脸图像文件,提取出 128D 特征,然后计算出某人人脸数据的特征均值存入 CSV 中,方便之后识别时候进行比对;

利用 numpy.mean() 计算特征均值,生成一个存储所有录入人脸数据 database 的 "features_all.csv";

features_extraction_to_csv.py 源码:

  1 # Copyright (C) 2018-2021 coneypo
  2 # SPDX-License-Identifier: MIT
  3 
  4 # Author:   coneypo
  5 # Blog:     http://www.cnblogs.com/AdaminXie
  6 # GitHub:   https://github.com/coneypo/Dlib_face_recognition_from_camera
  7 # Mail:     coneypo@foxmail.com
  8 
  9 # 从人脸图像文件中提取人脸特征存入 "features_all.csv" / Extract features from images and save into "features_all.csv"
 10 
 11 import os
 12 import dlib
 13 import csv
 14 import numpy as np
 15 import logging
 16 import cv2
 17 
 18 # 要读取人脸图像文件的路径 / Path of cropped faces
 19 path_images_from_camera = "data/data_faces_from_camera/"
 20 
 21 # Dlib 正向人脸检测器 / Use frontal face detector of Dlib
 22 detector = dlib.get_frontal_face_detector()
 23 
 24 # Dlib 人脸 landmark 特征点检测器 / Get face landmarks
 25 predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
 26 
 27 # Dlib Resnet 人脸识别模型,提取 128D 的特征矢量 / Use Dlib resnet50 model to get 128D face descriptor
 28 face_reco_model = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")
 29 
 30 
 31 # 返回单张图像的 128D 特征 / Return 128D features for single image
 32 # Input:    path_img           <class 'str'>
 33 # Output:   face_descriptor    <class 'dlib.vector'>
 34 def return_128d_features(path_img):
 35     img_rd = cv2.imread(path_img)
 36     faces = detector(img_rd, 1)
 37 
 38     logging.info("%-40s %-20s", "检测到人脸的图像 / Image with faces detected:", path_img)
 39 
 40     # 因为有可能截下来的人脸再去检测,检测不出来人脸了, 所以要确保是 检测到人脸的人脸图像拿去算特征
 41     # For photos of faces saved, we need to make sure that we can detect faces from the cropped images
 42     if len(faces) != 0:
 43         shape = predictor(img_rd, faces[0])
 44         face_descriptor = face_reco_model.compute_face_descriptor(img_rd, shape)
 45     else:
 46         face_descriptor = 0
 47         logging.warning("no face")
 48     return face_descriptor
 49 
 50 
 51 # 返回 personX 的 128D 特征均值 / Return the mean value of 128D face descriptor for person X
 52 # Input:    path_face_personX        <class 'str'>
 53 # Output:   features_mean_personX    <class 'numpy.ndarray'>
 54 def return_features_mean_personX(path_face_personX):
 55     features_list_personX = []
 56     photos_list = os.listdir(path_face_personX)
 57     if photos_list:
 58         for i in range(len(photos_list)):
 59             # 调用 return_128d_features() 得到 128D 特征 / Get 128D features for single image of personX
 60             logging.info("%-40s %-20s", "正在读的人脸图像 / Reading image:", path_face_personX + "/" + photos_list[i])
 61             features_128d = return_128d_features(path_face_personX + "/" + photos_list[i])
 62             # 遇到没有检测出人脸的图片跳过 / Jump if no face detected from image
 63             if features_128d == 0:
 64                 i += 1
 65             else:
 66                 features_list_personX.append(features_128d)
 67     else:
 68         logging.warning("文件夹内图像文件为空 / Warning: No images in%s/", path_face_personX)
 69 
 70     # 计算 128D 特征的均值 / Compute the mean
 71     # personX 的 N 张图像 x 128D -> 1 x 128D
 72     if features_list_personX:
 73         features_mean_personX = np.array(features_list_personX, dtype=object).mean(axis=0)
 74     else:
 75         features_mean_personX = np.zeros(128, dtype=int, order='C')
 76     return features_mean_personX
 77 
 78 
 79 def main():
 80     logging.basicConfig(level=logging.INFO)
 81     # 获取已录入的最后一个人脸序号 / Get the order of latest person
 82     person_list = os.listdir("data/data_faces_from_camera/")
 83     person_list.sort()
 84 
 85     with open("data/features_all.csv", "w", newline="") as csvfile:
 86         writer = csv.writer(csvfile)
 87         for person in person_list:
 88             # Get the mean/average features of face/personX, it will be a list with a length of 128D
 89             logging.info("%sperson_%s", path_images_from_camera, person)
 90             features_mean_personX = return_features_mean_personX(path_images_from_camera + person)
 91 
 92             if len(person.split('_', 2)) == 2:
 93                 # "person_x"
 94                 person_name = person
 95             else:
 96                 # "person_x_tom"
 97                 person_name = person.split('_', 2)[-1]
 98             features_mean_personX = np.insert(features_mean_personX, 0, person_name, axis=0)
 99             # features_mean_personX will be 129D, person name + 128 features
100             writer.writerow(features_mean_personX)
101             logging.info('\n')
102         logging.info("所有录入人脸数据存入 / Save all the features of faces registered into: data/features_all.csv")
103 
104 
105 if __name__ == '__main__':
106     main()

 

我们可以看下对于某张图片,face_descriptor 这个 128D vectors 的输出结果:

绿色框内是我们的返回 128D 特征的函数;

在红色框内调用该函数来计算 img_face_13.jpg;

可以看到黄色框中的输出为 128D 的向量

图 6 返回单张图像的 128D 特征的计算结果

 

之后就需要人脸图像进行批量化操作,提取出 128D 的特征,然后计算特征均值,存入 features_all.csv;

features_all.csv 是一个 n 行 129 列的 CSV, n 是录入的人脸数,129 列是某人的名字加上 128D 特征(如果没有设置名字,那么就是 person_1, person_2 之类);

这存储的就是 录入的人脸特征数据,之后 摄像头捕获的人脸 将要拿过来和 这些特征值 进行比对,如果欧式距离比较近的话,就可以认为是同一张人脸

 

 get_features_into_CSV.py 的输出 log:

##### person_1 #####
data/data_csvs_from_camera/person_1.csv
正在读的人脸图像 / image to read:                data/data_faces_from_camera/person_1/img_face_1.jpg
检测到人脸的图像 / image with faces detected:    data/data_faces_from_camera/person_1/img_face_1.jpg 

正在读的人脸图像 / image to read:                data/data_faces_from_camera/person_1/img_face_2.jpg
检测到人脸的图像 / image with faces detected:    data/data_faces_from_camera/person_1/img_face_2.jpg 

正在读的人脸图像 / image to read:                data/data_faces_from_camera/person_1/img_face_3.jpg
检测到人脸的图像 / image with faces detected:    data/data_faces_from_camera/person_1/img_face_3.jpg 

正在读的人脸图像 / image to read:                data/data_faces_from_camera/person_1/img_face_4.jpg
检测到人脸的图像 / image with faces detected:    data/data_faces_from_camera/person_1/img_face_4.jpg 

##### person_2 #####
data/data_csvs_from_camera/person_2.csv
正在读的人脸图像 / image to read:                data/data_faces_from_camera/person_2/img_face_1.jpg
检测到人脸的图像 / image with faces detected:    data/data_faces_from_camera/person_2/img_face_1.jpg 

正在读的人脸图像 / image to read:                data/data_faces_from_camera/person_2/img_face_2.jpg
检测到人脸的图像 / image with faces detected:    data/data_faces_from_camera/person_2/img_face_2.jpg 

##### person_3 #####
data/data_csvs_from_camera/person_3.csv
正在读的人脸图像 / image to read:                data/data_faces_from_camera/person_3/img_face_1.jpg
检测到人脸的图像 / image with faces detected:    data/data_faces_from_camera/person_3/img_face_1.jpg 

正在读的人脸图像 / image to read:                data/data_faces_from_camera/person_3/img_face_2.jpg
检测到人脸的图像 / image with faces detected:    data/data_faces_from_camera/person_3/img_face_2.jpg 


...

 

2.3 face_reco_from_camera.py / 实时人脸识别对比分析

这部分源码实现的功能:调用摄像头,捕获摄像头中的人脸,然后如果检测到人脸,将 摄像头中的人脸提取出 128D 的特征,然后和 之前录入人脸的 128D 特征 进行计算欧式距离,如果比较小,可以判定为一个人,否则不是一个人;

图 7 face_reco_from_camera.py 实现逻辑

 

所以设计的伪代码如下:

# 人脸检测器/预测器/识别模型
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
facerec = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")

faces = detector(img_gray, 0)

# 1. 如果检测到人脸
if len(faces) != 0:
    # 遍历所有检测到的人脸
    for i in range(len(faces)):
        # 2. 提取当前帧人脸的特征描述子
        shape = predictor(img_rd, faces[i])
        facerec.compute_face_descriptor(img_rd, shape)
        # 3. 将当前帧人脸特征描述子和数据库的特征描述子进行对比
        for i in range(len(self.features_known_list)):
            e_distance_tmp = self.return_euclidean_distance(self.features_camera_list[k], self.features_known_list[i])                                  

 

关于 https://github.com/coneypo/Dlib_face_recognition_from_camera/blob/master/face_reco_from_camera.py 里面变量的定义:

变量 说明
self.feature_known_list 存储所有录入人脸特征的数组 / Save the features of faces in the database
self.name_known_list 存储已录入人脸的名字 / Save the names of faces in the database
self.current_frame_face_cnt 存储当前摄像头中捕获到的人脸数 / Counter for faces in current frame
self.current_frame_feature_list 存储当前摄像头中捕获到的人脸特征 / Features of faces in current frame
self.current_frame_name_position_list  存储当前摄像头中捕获到的所有人脸的名字坐标 / Positions of faces in current frame
self.current_frame_name_list     存储当前摄像头中捕获到的所有人脸的名字 / Names of faces in current frame

 

 

 关于用到的 dlib 检测器,预测器,识别器:

1. dlib.get_frontal_face_detector

Link:http://dlib.net/python/index.html#dlib.get_frontal_face_detector

介绍:返回默认的人脸检测器,为下面的 fhog_object_detectorm / Returns the default face detector

 

2. class dlib.fhog_object_detector

Link:http://dlib.net/python/index.html#dlib.fhog_object_detector

介绍 :基于滑动窗的HOG进行目标检测, This object represents a sliding window histogram-of-oriented-gradients based object detector.

参数:

__call__(self: dlib.fhog_object_detector, image: array, upsample_num_times: int=0L) → dlib.rectangles

 

3. class dlib.shape_predictor

Link:

http://dlib.net/python/index.html#dlib.shape_predictor

简介:

人脸图像作为输入, 输出面部特征点;

This object is a tool that takes in an image region containing some object and outputs a set of point locations that define the pose of the object. 

The classic example of this is human face pose prediction, where you take an image of a human face as input and are expected to identify

the locations of important facial landmarks such as the corners of the mouth and eyes, tip of the nose, and so forth.

参数 / parameters:

__call__(self: dlib.shape_predictor, image: array, box: dlib.rectangle) → dlib.full_object_detection

输入: dlib.rectangle 输出: dlib.full_object_detection

 

4. class dlib.face_recognition_model_v1

Link:

http://dlib.net/python/index.html#dlib.face_recognition_model_v1

简介 / intro:

将人脸转换为128D特征向量, 这样的话相似人脸会比较相近, 不相像的会比较远;

This object maps human faces into 128D vectors where pictures of the same person are mapped near to each other and pictures of different people are mapped far apart.

The constructor loads the face recognition model from a file. The model file is available here: http://dlib.net/files/dlib_face_recognition_resnet_model_v1.dat.bz2

参数 / parameters:

compute_face_descriptor(self: dlib.face_recognition_model_v1, img: numpy.ndarray[(rows,cols,3),uint8], face: dlib.full_object_detection, num_jitters: int=0L, padding: float=0.25) -> dlib.vector

 

通过 print(type()) 可以更清楚的看到 dlib 对象的传递:

# 人脸检测器/预测器/识别模型
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
facerec = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")

faces = detector(img_gray, 0)

# 如果检测到人脸
if len(faces) != 0:
    print(type(faces)                                                # <class 'dlib.dlib.rectangles'>
    # 遍历所有检测到的人脸
    for i in range(len(faces)):
        # 进行人脸比对
        shape = predictor(img_rd, faces[i])
        print(type(shape))                                            # <class 'dlib.dlib.full_object_detection'>
        facerec.compute_face_descriptor(img_rd, shape)
        print(type(facerec.compute_face_descriptor(img_rd, shape))    # <class 'dlib.dlib.vector'>

 

这样一个对象传递过程:

faces = detector(img_gray, 0) -> <class 'dlib.dlib.rectangles'> -> 
shape = predictor(img_rd, faces[i]) -> <class 'dlib.dlib.full_object_detection'> -> 
facerec.compute_face_descriptor(img_rd, shape) -> <class 'dlib.dlib.vector'>

 

欧氏距离对比的阈值设定,是在 return_euclidean_distance 函数的 dist  变量;

我这里程序里面指定的 欧氏距离判断阈值是 0.4,具体阈值可以根据实际情况或者测得结果进行修改;

  

这边做了一个,让人名跟随显示在头像下方,如果想要在人脸矩形框下方显示人名,首先需要知道 Dlib 生成的矩形框的尺寸怎么读取;

Dlib 返回的 dets 变量是一系列人脸的数据,此处对单张人脸处理,所以取 dets[0] 的参数;

可以通过 dets[0].top()dets[0].bottom()dets[0].left() 和 dets[0].right() 来确定要显示的人名的坐标;

图 8 dets[0].top() 等参数说明 

  

得到矩形框的坐标,就可以获取人名的相对位置;

这是我这边取的坐标:

 pos_text_1 = tuple([dets[0].left(), int(dets[0].bottom()+(dets[0].bottom()-dets[0].top())/4)]) 

 

 

图 9 face_reco_from_camera.py 生成的人脸识别窗口界面

 

   如果想定制输出显示的名字而不是“Person_1”,"Person_2"...;

 


图 9 定制显示名字

 

face_reco_from_camera.py 源码:

  1 # Copyright (C) 2020 coneypo
  2 # SPDX-License-Identifier: MIT
  3 
  4 # Author:   coneypo
  5 # Blog:     http://www.cnblogs.com/AdaminXie
  6 # GitHub:   https://github.com/coneypo/Dlib_face_recognition_from_camera
  7 # Mail:     coneypo@foxmail.com
  8 
  9 # 摄像头实时人脸识别 / Real-time face detection and recognition
 10 
 11 import dlib
 12 import numpy as np
 13 import cv2
 14 import pandas as pd
 15 import os
 16 import time
 17 from PIL import Image, ImageDraw, ImageFont
 18 
 19 # Dlib 正向人脸检测器 / Use frontal face detector of Dlib
 20 detector = dlib.get_frontal_face_detector()
 21 
 22 # Dlib 人脸 landmark 特征点检测器 / Get face landmarks
 23 predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
 24 
 25 # Dlib Resnet 人脸识别模型,提取 128D 的特征矢量 / Use Dlib resnet50 model to get 128D face descriptor
 26 face_reco_model = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")
 27 
 28 
 29 class Face_Recognizer:
 30     def __init__(self):
 31         self.feature_known_list = []                # 用来存放所有录入人脸特征的数组 / Save the features of faces in the database
 32         self.name_known_list = []                   # 存储录入人脸名字 / Save the name of faces in the database
 33 
 34         self.current_frame_face_cnt = 0             # 存储当前摄像头中捕获到的人脸数 / Counter for faces in current frame
 35         self.current_frame_feature_list = []        # 存储当前摄像头中捕获到的人脸特征 / Features of faces in current frame
 36         self.current_frame_name_position_list = []  # 存储当前摄像头中捕获到的所有人脸的名字坐标 / Positions of faces in current frame
 37         self.current_frame_name_list = []           # 存储当前摄像头中捕获到的所有人脸的名字 / Names of faces in current frame
 38 
 39         # Update FPS
 40         self.fps = 0
 41         self.frame_start_time = 0
 42 
 43     # 从 "features_all.csv" 读取录入人脸特征 / Get known faces from "features_all.csv"
 44     def get_face_database(self):
 45         if os.path.exists("data/features_all.csv"):
 46             path_features_known_csv = "data/features_all.csv"
 47             csv_rd = pd.read_csv(path_features_known_csv, header=None)
 48             for i in range(csv_rd.shape[0]):
 49                 features_someone_arr = []
 50                 for j in range(0, 128):
 51                     if csv_rd.iloc[i][j] == '':
 52                         features_someone_arr.append('0')
 53                     else:
 54                         features_someone_arr.append(csv_rd.iloc[i][j])
 55                 self.feature_known_list.append(features_someone_arr)
 56                 self.name_known_list.append("Person_"+str(i+1))
 57             print("Faces in Database:", len(self.feature_known_list))
 58             return 1
 59         else:
 60             print('##### Warning #####', '\n')
 61             print("'features_all.csv' not found!")
 62             print(
 63                 "Please run 'get_faces_from_camera.py' and 'features_extraction_to_csv.py' before 'face_reco_from_camera.py'",
 64                 '\n')
 65             print('##### End Warning #####')
 66             return 0
 67 
 68     # 计算两个128D向量间的欧式距离 / Compute the e-distance between two 128D features
 69     @staticmethod
 70     def return_euclidean_distance(feature_1, feature_2):
 71         feature_1 = np.array(feature_1)
 72         feature_2 = np.array(feature_2)
 73         dist = np.sqrt(np.sum(np.square(feature_1 - feature_2)))
 74         return dist
 75 
 76     # 更新 FPS / Update FPS of Video stream
 77     def update_fps(self):
 78         now = time.time()
 79         self.frame_time = now - self.frame_start_time
 80         self.fps = 1.0 / self.frame_time
 81         self.frame_start_time = now
 82 
 83     def draw_note(self, img_rd):
 84         font = cv2.FONT_ITALIC
 85 
 86         cv2.putText(img_rd, "Face Recognizer", (20, 40), font, 1, (255, 255, 255), 1, cv2.LINE_AA)
 87         cv2.putText(img_rd, "FPS:   " + str(self.fps.__round__(2)), (20, 100), font, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
 88         cv2.putText(img_rd, "Faces: " + str(self.current_frame_face_cnt), (20, 140), font, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
 89         cv2.putText(img_rd, "Q: Quit", (20, 450), font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
 90 
 91     def draw_name(self, img_rd):
 92         # 在人脸框下面写人脸名字 / Write names under rectangle
 93         font = ImageFont.truetype("simsun.ttc", 30)
 94         img = Image.fromarray(cv2.cvtColor(img_rd, cv2.COLOR_BGR2RGB))
 95         draw = ImageDraw.Draw(img)
 96         for i in range(self.current_frame_face_cnt):
 97             # cv2.putText(img_rd, self.current_frame_name_list[i], self.current_frame_name_position_list[i], font, 0.8, (0, 255, 255), 1, cv2.LINE_AA)
 98             draw.text(xy=self.current_frame_name_position_list[i], text=self.current_frame_name_list[i], font=font)
 99             img_with_name = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
100         return img_with_name
101 
102     # 修改显示人名 / Show names in chinese
103     def show_chinese_name(self):
104         # Default known name: person_1, person_2, person_3
105         if self.current_frame_face_cnt >= 1:
106             self.name_known_list[0] ='张三'.encode('utf-8').decode()
107         # self.name_known_list[1] ='李四'.encode('utf-8').decode()
108         # self.name_known_list[2] ='xx'.encode('utf-8').decode()
109         # self.name_known_list[3] ='xx'.encode('utf-8').decode()
110         # self.name_known_list[4] ='xx'.encode('utf-8').decode()
111 
112     # 处理获取的视频流,进行人脸识别 / Face detection and recognition from input video stream
113     def process(self, stream):
114         # 1. 读取存放所有人脸特征的 csv / Get faces known from "features.all.csv"
115         if self.get_face_database():
116             while stream.isOpened():
117                 print(">>> Frame start")
118                 flag, img_rd = stream.read()
119                 faces = detector(img_rd, 0)
120                 kk = cv2.waitKey(1)
121                 # 按下 q 键退出 / Press 'q' to quit
122                 if kk == ord('q'):
123                     break
124                 else:
125                     self.draw_note(img_rd)
126                     self.current_frame_feature_list = []
127                     self.current_frame_face_cnt = 0
128                     self.current_frame_name_position_list = []
129                     self.current_frame_name_list = []
130 
131                     # 2. 检测到人脸 / Face detected in current frame
132                     if len(faces) != 0:
133                         # 3. 获取当前捕获到的图像的所有人脸的特征 / Compute the face descriptors for faces in current frame
134                         for i in range(len(faces)):
135                             shape = predictor(img_rd, faces[i])
136                             self.current_frame_feature_list.append(face_reco_model.compute_face_descriptor(img_rd, shape))
137                         # 4. 遍历捕获到的图像中所有的人脸 / Traversal all the faces in the database
138                         for k in range(len(faces)):
139                             print(">>>>>> For face", k+1, " in camera")
140                             # 先默认所有人不认识,是 unknown / Set the default names of faces with "unknown"
141                             self.current_frame_name_list.append("unknown")
142 
143                             # 每个捕获人脸的名字坐标 / Positions of faces captured
144                             self.current_frame_name_position_list.append(tuple(
145                                 [faces[k].left(), int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)]))
146 
147                             # 5. 对于某张人脸,遍历所有存储的人脸特征
148                             # For every faces detected, compare the faces in the database
149                             current_frame_e_distance_list = []
150                             for i in range(len(self.feature_known_list)):
151                                 # 如果 person_X 数据不为空
152                                 if str(self.feature_known_list[i][0]) != '0.0':
153                                     print("   >>> With person", str(i + 1), ", the e distance: ", end='')
154                                     e_distance_tmp = self.return_euclidean_distance(self.current_frame_feature_list[k],
155                                                                                     self.feature_known_list[i])
156                                     print(e_distance_tmp)
157                                     current_frame_e_distance_list.append(e_distance_tmp)
158                                 else:
159                                     # 空数据 person_X
160                                     current_frame_e_distance_list.append(999999999)
161                             # 6. 寻找出最小的欧式距离匹配 / Find the one with minimum e distance
162                             similar_person_num = current_frame_e_distance_list.index(min(current_frame_e_distance_list))
163                             print("   >>> Minimum e distance with ", self.name_known_list[similar_person_num], ": ", min(current_frame_e_distance_list))
164 
165                             if min(current_frame_e_distance_list) < 0.4:
166                                 self.current_frame_name_list[k] = self.name_known_list[similar_person_num]
167                                 print("   >>> Face recognition result:  " + str(self.name_known_list[similar_person_num]))
168                             else:
169                                 print("   >>> Face recognition result: Unknown person")
170 
171                             # 矩形框 / Draw rectangle
172                             for kk, d in enumerate(faces):
173                                 # 绘制矩形框
174                                 cv2.rectangle(img_rd, tuple([d.left(), d.top()]), tuple([d.right(), d.bottom()]),
175                                               (0, 255, 255), 2)
176 
177                         self.current_frame_face_cnt = len(faces)
178 
179                         # 7. 在这里更改显示的人名 / Modify name if needed
180                         # self.show_chinese_name()
181 
182                         # 8. 写名字 / Draw name
183                         img_with_name = self.draw_name(img_rd)
184 
185                     else:
186                         img_with_name = img_rd
187 
188                 print(">>>>>> Faces in camera now:", self.current_frame_name_list)
189 
190                 cv2.imshow("camera", img_with_name)
191 
192                 # 9. 更新 FPS / Update stream FPS
193                 self.update_fps()
194                 print(">>> Frame ends\n\n")
195 
196     # OpenCV 调用摄像头并进行 process
197     def run(self):
198         cap = cv2.VideoCapture(0)
199         # cap = cv2.VideoCapture("video.mp4")
200         cap.set(3, 480)     # 640x480
201         self.process(cap)
202 
203         cap.release()
204         cv2.destroyAllWindows()
205 
206 
207 def main():
208     Face_Recognizer_con = Face_Recognizer()
209     Face_Recognizer_con.run()
210 
211 
212 if __name__ == '__main__':
213     main()

 

face_reco_from_camera.py 输出 log:

Faces in Database: 3
>>> Frame start
>>>>>> For face 1  in camera
   >>> With person 2 , the e distance: 0.24747225595381367
   >>> With person 3 , the e distance: 0.22821104803792178
   >>> Minimum e distance with  Person_3 :  0.22821104803792178
   >>> Face recognition result:  Person_3
>>>>>> Faces in camera now: ['Person_3']
>>> Frame ends


>>> Frame start
>>>>>> For face 1  in camera
   >>> With person 2 , the e distance: 0.2490812900317618
   >>> With person 3 , the e distance: 0.22549497689337802
   >>> Minimum e distance with  Person_3 :  0.22549497689337802
   >>> Face recognition result:  Person_3
>>>>>> Faces in camera now: ['Person_3']
>>> Frame ends


>>> Frame start
>>>>>> For face 1  in camera
   >>> With person 2 , the e distance: 0.24569769385882426
   >>> With person 3 , the e distance: 0.2262102554355137
   >>> Minimum e distance with  Person_3 :  0.2262102554355137
   >>> Face recognition result:  Person_3
>>>>>> Faces in camera now: ['Person_3']
>>> Frame ends


>>> Frame start
>>>>>> For face 1  in camera
   >>> With person 2 , the e distance: 0.24387949251367172
   >>> With person 3 , the e distance: 0.22636200199905795
   >>> Minimum e distance with  Person_3 :  0.22636200199905795
   >>> Face recognition result:  Person_3
>>>>>> Faces in camera now: ['Person_3']
>>> Frame ends


>>> Frame start
>>>>>> For face 1  in camera
   >>> With person 2 , the e distance: 0.2473446948271673
   >>> With person 3 , the e distance: 0.22534075942468246
   >>> Minimum e distance with  Person_3 :  0.22534075942468246
   >>> Face recognition result:  Person_3
>>>>>> Faces in camera now: ['Person_3']
>>> Frame ends


>>> Frame start
>>>>>> For face 1  in camera
   >>> With person 2 , the e distance: 0.24465000646050672
   >>> With person 3 , the e distance: 0.2238005841538998
   >>> Minimum e distance with  Person_3 :  0.2238005841538998
   >>> Face recognition result:  Person_3
>>>>>> Faces in camera now: ['Person_3']
>>> Frame ends

 

如果对单个人脸,进行实时对比输出:

图 10 实时输出的欧氏距离结果

 

  通过实时的输出结果,看的比较明显;

  输出绿色部分:当是我自己时,计算出来的欧式距离基本都在 0.2 左右

  输出红色部分:而换一张图片上去比如特朗普,明显看到欧式距离计算结果 达到了 0.8,此时就可以判定,后来这张人脸不是一张人脸;

  所以之前提到的欧式距离计算对比的阈值可以由此设定,本项目中取的是 dist=0.4;

   dist 的确切取值自己权衡,http://dlib.net/face_recognition.py.html 的说明:

 

#   When using a distance threshold of 0.6, the dlib model obtains an accuracy
#   of 99.38% on the standard LFW face recognition benchmark, which is
#   comparable to other state-of-the-art methods for face recognition as of
#   February 2017. This accuracy means that, when presented with a pair of face
#   images, the tool will correctly identify if the pair belongs to the same
#   person or is from different people 99.38% of the time.

可以通过 logging.basicConfig(level=logging.DEBUG) 来设置代码的打印;

3. 总结

核心就是 提取人脸特征,然后计算欧式距离和预设的特征脸进行比对;

不过这个实时获取摄像头人脸进行比对,要实时的进行计算摄像头脸的特征值,然后还要计算欧氏距离,所以计算量比较大,可能摄像头视频流会出现卡顿;

此项目仅个人学习爱好研究,开源供大家一起学习;

 

# 请尊重他人劳动成果,转载或者使用源码请注明出处:http://www.cnblogs.com/AdaminXie

# 代码已上传到了我的 GitHub,如果对您有帮助欢迎 Star 支持我下:https://github.com/coneypo/Dlib_face_recognition_from_camera

# 如有问题请留言或者联系邮箱: coneypo@foxmail.com

 

posted @ 2018-05-11 12:33  coneypo  阅读(72633)  评论(85编辑  收藏  举报