簡介
利用 Python 開發,藉助 Dlib 庫捕獲攝像頭中的人臉,提取人臉特徵,通過計算特徵值之間的歐氏距離,來和預存的人臉特徵進行對比,判斷是否匹配,達到人臉識別的目的;
最終的的追蹤識別效果如圖:
原始人臉實時檢測識別邏輯
在之前的部落格裡面介紹到如何利用 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