简介
利用 Python 开发,借助 Dlib 库捕获摄像头中的人脸,提取人脸特征,通过计算特征值之间的欧氏距离,来和预存的人脸特征进行对比,判断是否匹配,达到人脸识别的目的;
最终的的追踪识别效果如图(Face_1 和 Face_2 是检测出来的人脸,Person_1 和 Person_2 是通过目标追踪得到的标识 ID):
原始人脸实时检测识别逻辑
在之前的博客里面介绍到如何利用 Dlib 进行实时的人脸识别(https://www.cnblogs.com/AdaminXie/p/9010298.html),但是会遇到 FPS 很低 (FPS 差不多在 5 左右)的问题;
对于视频流中的某一帧 N,需要进行以下步骤来进行识别:
- 对于当前帧进行人脸检测(~0.03s);
- 对于检测出的人脸,提取特征描述子(~0.158s);
- 对于当前帧中的所有人脸,都要和已知人脸数据库进行遍历比对(~0.003s);
- 对于当前帧中的人脸 X,如果比对结果出来发现和已知人脸 Y 的特征描述子的欧氏距离小于 0.4,则认为当前帧中的人脸 X 就是我们认识的 Y;
从耗时可以看出来,步骤一中的人脸检测和步骤三四中的比对都不会太影响到程序性能,但是如果要实时计算特征描述子,就会很吃资源,需要差不多 0.16s 的时间来计算某一张人脸的特征描述子;
与之对比进行检测(只需要 0.03s)和数据库比对(只需要 0.003s);
在这个程序(https://github.com/coneypo/Dlib_face_recognition_from_camera/blob/master/face_descriptor_from_camera.py)里面,对捕获到的图像进行实时的特征描述子计算,发现最终出来的界面的 FPS 输出在 5 FPS 左右,会有明显的卡顿;
原程序中进行人脸识别的伪代码如下:
while stream.isOpened(): flag, img_rd = stream.read() # 1. 对于当前帧进行人脸检测 faces = detector(img_rd, 0) # 2. 如果当前帧中出现人脸 if len(faces) !=0: # 3. 对于这些人脸,提取特征描述子,存入一个 list for i in range(len(faces)): features_camera_list.append(face_reco_model.compute_face_decriptor(img_rd, shape) # 4. 对于这些人脸,遍历已知人脸数据库 for i in range(len(faces)): # 4.1 比如对于 person_X,和所有已知的人脸进行欧式距离对比 for j in range(len(features_known_list)): return_e_distance(当前帧中的 person_X 的特征描述子,已知人脸的特征描述子[j]) # 4.2 对于 person_x,得到一个和已知人脸比较的最小欧式距离,如果 <0.4 则认为 person_x 就是这个已知人脸 if e_distance_min < 0.4: 当前帧中的 person_X 的名字 = 欧式距离 <0.4 的已知人脸的名字
比如在第 N 帧中有两个人,经过我们的遍历对比,我们知道是 Jack 和 Lucky;
那么对于视频流中的后续帧(N+1,N+2 帧等等),如果还是有两个人,我们就可以大概率判定他们还是 Jack 和 Lucky,只是在帧中的位置发生了变化(这也是目标追踪能够适用的前提);
那我们只需要确定帧 N 的两个人,和帧 N+1 中这两个人的对应关系,而不需要对于帧 N+1 再进行一次特征描述子提取,然后再进行遍历比对(因为这样很占用资源);
这就是目标追踪要做的事情,具体 OT 的介绍可以参考我之前的博文(https://www.cnblogs.com/AdaminXie/p/13560758.html);
通过目标跟踪来提高 FPS
OT 的实现逻辑
所以我们希望能避免掉这个 耗时 0.16s 的特征子提取 的处理工作,事实上通过目标追踪,我们确实不需要再对每一帧都要做做检测+识别;
只需要对于视频流第一帧/初始帧进行检测+识别,识别出该帧的人脸名字;
对于后续帧,首先是判断当前帧和上一帧的目标数变化,如果目标数不变,比如上一帧有两个人,那么这一帧也有两个人,我们就可以判定这两个人就是上一帧出现的两个人,所以不需要再进行特征描述子的提取和识别工作;
取而代之的是需要判断当前帧这两个目标,和上一帧两个目标的关系(通过 https://www.cnblogs.com/AdaminXie/p/13560758.html 介绍的 质心追踪算法 来确定);
原始方式:
- 初始帧:检测+识别
- 后续帧:检测+识别
引入 OT:
- 初始帧:检测+识别
- 后续帧:检测+质心追踪
目标人脸数不超过一张
考虑最简单的情况,窗口中出现的人脸至多一张,所以目标数 = 1 或者 0;
对于这种特殊的情况,如果人脸数发生改变:
- 0->1,没有人脸到出现人脸,对于这一帧中新出现的人脸进行识别
- 1->0,人脸消失了,注销人脸,清空储存人脸的 list
如果人脸数不发生改变,那么初始帧认出来他是 person_X,他就一直是 person_X,后续帧只需要做检测就好了(这里其实质心比对都不需要做了);
人脸数 <=1 的情况下的实现可以参考我这里的代码:https://github.com/coneypo/Dlib_face_recognition_from_camera/blob/master/face_reco_from_camera_ot_single_person.py
可以看到 FPS 可以达到 28.31,比之前的 FPS 提高了很多(因为只对人脸出现的第一帧进行检测+识别,后续帧只做了检测);
目标人脸数多于一张
出现的人脸数一旦要大于1,就要利用质心追踪算法来判断前后帧的目标对应关系:
- last_frame_centroid_list 存储的是上一帧的质心坐标;
- curret_frame_centroid_list 存储的是当前帧的质心坐标;
- e_distance_current_frame_face_x_list:对于当前帧中检测出的 face_X,和上一帧中的 face_1, face_2 .. face_n 计算质心欧式距离,得到一个长度为 n 的列表;
比如对于第 65 帧,已知:
上一帧中(第 64 帧):
- last_frame_centroid_list = [[566.5, 163.5], [129.0, 186.5]];
- last_frame_face_names_list = ['person_1', 'person_2'];
当前帧中(第 65 帧):
- current_frame_centroid_list = [[566.5, 163.5], [117.0, 184.0]];
想得到当前帧(第 65 帧):
- current_frame_face_names_list = ?
所以对于当前帧中出现的所有人脸(face_1 和 face_2),计算质心得到 current_frame_centroid_list = [[566.5, 163.5], [117.0, 184.0]],
对于当前帧中的 face_1,和上一帧中的 last_frame_centroid_list[0] = [566.5, 163.5] / last_frame_centroid_list[1] = [129.0, 186.5] 计算欧式距离;
经过计算得知 face_1 和 last_frame_centroid_list[0] 的欧式距离更小,得到 last_frame_num = 0;
也就是说:
- self.current_frame_face_names_list[0] = self.last_frame_face_names_list[1],即 face_1 是 person_1
- self.current_frame_face_names_list[1] = self.last_frame_face_names_list[0],即 face_2 是 person_2
得到当前帧的人脸名称 current_frame_names_list = ["person_1", "person_2"];
def centroid_tracker(self): for i in range(len(self.current_frame_centroid_list)): e_distance_current_frame_face_x_list = [] # for face 1 in current_frame, compute e-distance with face 1/2/3/4/... in last frame for j in range(len(self.last_frame_centroid_list)): self.last_current_frame_centroid_e_distance = self.return_euclidean_distance( self.current_frame_centroid_list[i], self.last_frame_centroid_list[j]) e_distance_current_frame_person_x_list.append( self.last_current_frame_centroid_e_distance) last_frame_num = e_distance_current_frame_person_x_list.index( min(e_distance_current_frame_face_x_list)) self.current_frame_face_names_list[i] = self.last_frame_face_names_list[last_frame_num]
考虑如下情况,分别是第 57/58 帧:
第 57 帧中,检测出 face_1 和 face_2,face_1 在右边,face_2 在左边,face_1 是 person_1, face_2 是 person_2;
current_frame_centroid_list = [[597.0, 155.0], [169.0, 169.0]];
current_frame_face_names_list = ['person_1', 'person_2'];
即右边的 [597.0, 155.0] 的 face_1 是 person_1;
左边的 [169.0, 169.0] 的 face_2 是 person_2;
在第 58 帧的时候,先进行人脸检测,检测出来两个人脸 face_1 和 face_2,不过 face_1 在左边,face_2 在右边;
(第 57 帧)last_frame_centroid_list = [[597.0, 155.0], [169.0, 169.0]];
(第 58 帧)current_frame_centroid_list = [[169.0, 169.0], [589.5, 163.5]];
这时候对于当前帧的 face_1 ([169.0, 169.0]),和上一帧的两个人脸进行质心的欧氏距离对比,得到和 last_frame_centroid_list[1] 更近一点,也就是说:
- self.current_frame_face_names_list[0] = self.last_frame_face_names_list[1],即 face_1 是 person_2
- self.current_frame_face_names_list[1] = self.last_frame_face_names_list[0],即 face_2 是 person_1
得到当前帧的人脸名单:
current_frame_face_names_list = ['person_1', 'person_2'];
所以最终的结果可以看到识别的结果,FPS 在 26 左右,比之前的方法提高了很多:
# 请尊重他人劳动成果,转载或者使用源码请注明出处:http://www.cnblogs.com/AdaminXie
# 代码已上传到了我的 GitHub,如果对您有帮助欢迎 Star 支持我下:https://github.com/coneypo/Dlib_face_recognition_from_camera
# 如有问题请留言或者联系邮箱: coneypo@foxmail.com