利用目标跟踪来提高实时人脸识别处理速度

简介

利用 Python 开发,借助 Dlib 库捕获摄像头中的人脸,提取人脸特征,通过计算特征值之间的欧氏距离,来和预存的人脸特征进行对比,判断是否匹配,达到人脸识别的目的;

最终的的追踪识别效果如图(Face_1 和 Face_2 是检测出来的人脸,Person_1 和 Person_2 是通过目标追踪得到的标识 ID):

 

原始人脸实时检测识别逻辑

在之前的博客里面介绍到如何利用 Dlib 进行实时的人脸识别(https://www.cnblogs.com/AdaminXie/p/9010298.html),但是会遇到 FPS 很低 (FPS 差不多在 5 左右)的问题;

对于视频流中的某一帧 N,需要进行以下步骤来进行识别:

  1. 对于当前帧进行人脸检测(~0.03s);
  2. 对于检测出的人脸,提取特征描述子(~0.158s);
  3. 对于当前帧中的所有人脸,都要和已知人脸数据库进行遍历比对(~0.003s);
  4. 对于当前帧中的人脸 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 左右,比之前的方法提高了很多:

 

完整实现的代码在 https://github.com/coneypo/Dlib_face_recognition_from_camera/blob/master/face_reco_from_camera_ot_multi_people.py

 

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

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

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

posted @ 2020-09-02 16:51  coneypo  阅读(592)  评论(0编辑  收藏