利用目標跟蹤來提高實時人臉識別處理速度

coneypo發表於2020-09-02

簡介

利用 Python 開發,藉助 Dlib 庫捕獲攝像頭中的人臉,提取人臉特徵,通過計算特徵值之間的歐氏距離,來和預存的人臉特徵進行對比,判斷是否匹配,達到人臉識別的目的;

最終的的追蹤識別效果如圖:

 

原始人臉實時檢測識別邏輯

在之前的部落格裡面介紹到如何利用 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

相關文章