大牛教你使用dlib中的深度殘差網路(ResNet)實現實時人臉識別
opencv中提供的基於haar特徵級聯進行人臉檢測的方法效果非常不好,本文使用dlib中提供的人臉檢測方法(使用HOG特徵或卷積神經網方法),並使用提供的深度殘差網路(ResNet)實現實時人臉識別,不過本文的目的不是構建深度殘差網路,而是利用已經訓練好的模型進行實時人臉識別,實時性要求一秒鐘達到10幀以上的速率,並且保證不錯的精度。opencv和dlib都是非常好用的計算機視覺庫,特別是dlib,前面文章提到了其內部封裝了一些比較新的深度學習方法,使用這些演算法可以實現很多應用,比如人臉檢測、車輛檢測、目標追蹤、語義分割等等。由於這兩個庫相應的都包含了C++和Python的版本,而Python的配置和相對使用起來更加簡單,因此這篇文章主要通過Python來實現。
先上測試的識別效果,第一張識別吳恩達和Bengio,後者我沒有打標籤所以識別的是“other”;另外一張gif 是識別梁朝偉、劉德華和一個女主持的過程,本地庫中沒有儲存女主持的圖片。
因為部落格園不方便上傳本地視訊,所以用的gif顯示效果圖,源視訊要比gif清楚,640X480畫素大小,總的來說效果識別的效果還不錯。
一、準備
(1)需要安裝opencv和dlib的Python庫,之前的一篇文章提到了怎樣安裝:http://www.cnblogs.com/supersayajin/p/8446685.html
(2)需要一個和PC連線的攝像頭;在本文中使用的是串列埠的攝像頭,膝上型電腦整合的攝像頭就是串列埠的,opencv中提供了直接獲取串列埠攝像頭的介面,非常方便使用;如果是網口的攝像頭那麼就要看攝像頭提供方,比如大華、海康他們的攝像頭可能會提供官方的SDK,如果有介面那是最好;或者,如果攝像頭支援RTSP協議,opencv也可以通過RTSP協議獲取攝像頭的資料;否則可能就要寫一套socket通訊來實現資料傳輸,這個不在本文範圍之內,預設使用的是串列埠的攝像頭。
二、策略
人臉識別分為人臉檢測和識別兩個階段,人臉檢測會找到人臉區域的矩形視窗,識別則通過ResNet返回人臉特徵向量,並進行匹配。
(1)人臉檢測階段。人臉檢測演算法需要用大小位置不同的視窗在影象中進行滑動,然後判斷視窗中是否存在人臉。在深度學習之前的主流方法是特徵提取+整合學習分類器,比如以前火熱的haar特徵+adaboost級聯分類器,opencv中實現的人臉檢測方法就採用了這種,不過實驗結果來看,這種檢測方法效果很不好,經常誤檢測人臉,或者檢測不到真實的人臉;dlib中使用的是HOG(histogram of oriented gradient)+ 迴歸樹的方法,使用dlib訓練好的模型進行檢測效果要好很多。dlib也使用了卷積神經網路來進行人臉檢測,效果好於HOG的整合學習方法,不過需要使用GPU加速,不然程式會卡爆了,一張圖片可能幾秒甚至幾十秒。
(2)識別階段。識別也就是我們常說的“分類”,攝像頭採集到這個人臉時,讓機器判斷是張三還是其他人。分類分為兩個部分:
- 特徵向量抽取。本文用到的是dlib中已經訓練好的ResNet模型的介面,此介面會返回一個128維的人臉特徵向量。
- 距離匹配。在獲取特徵向量之後可以使用歐式距離和本地的人臉特徵向量進行匹配,使用最近鄰分類器返回樣本的標籤。
根據以上,識別的大致過程如下:
圖1 人臉識別分類過程
對於圖1中的獲取人臉特徵向量,其過程如下:
圖2 獲取人臉特徵向量過程
用簡單的話總結,整個過程分為兩個階段,本地儲存已標記人臉資料;識別階段把從攝像頭讀取的人臉和本地進行匹配,得到分類結果。
三、程式實現
(1)構建本地人臉特徵向量庫,並且打標籤。
首先載入需要的python庫:
import dlib import numpy as np import cv2 import os import json
然後載入模型引數:
detector = dlib.cnn_face_detection_model_v1('mmod_human_face_detector.dat') sp = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat') facerec = dlib.face_recognition_model_v1('dlib_face_recognition_resnet_model_v1.dat')
上面程式碼中的模型引數可以到這裡下載:http://dlib.net/files/。detector是使用卷積神經網路(CNN)進行人臉檢測的檢測運算元,當然如果你使用CNN的話需要使用GPU加速,否則速度會超級慢。也可以使用另一種方法,即HOG特徵級聯分類的檢測方法,效果略差於CNN。變數sp,使用預測運算元獲取得到的人臉區域中的五官的幾何點區域,這裡載入的是68特徵點的landmark模型;然後facerec會得到ResNet模型,He Kaiming(2009年和2015的CVPR best paper作者)提出的方法的一個實現,這裡訓練模型已經給出,因此不需要自己手動去訓練了。
最後,對某個目錄中的所有圖片進行處理,處理的方式是一張一張地讀取某個目錄中的圖片,每讀取一張就檢測人臉,如果存在人臉就使用ResNet的介面獲取人臉特性向量,儲存到事先準備好的矩陣中,並且按照檔名存取標籤,完了之後把所有的人臉特徵向量和標籤都存到本地的文字檔案中。注意這裡給圖片打標籤的方式,我把每張圖片命名為標籤名+下劃線+序號+點號+字尾名的形式,標籤名是手動命名的標記名稱,序號用以區分同一類中的第幾張。以下是demo中存放的部分圖片:
也有很多其他的方法打標籤,這裡不多舉例。
imagePath = 'LocalImage/' #影象的目錄 data = np.zeros((1,128)) #定義一個128維的空向量data label = [] #定義空的list存放人臉的標籤 for file in os.listdir(imagePath): #開始一張一張索引目錄中的影象 if '.jpg' in file or '.png' in file: fileName = file labelName = file.split('_')[0] #獲取標籤名 print('current image: ', file) print('current label: ', labelName) img = cv2.imread(imagePath + file) #使用opencv讀取影象資料 if img.shape[0]*img.shape[1] > 500000: #如果圖太大的話需要壓縮,這裡畫素的閾值可以自己設定 img = cv2.resize(img, (0,0), fx=0.5, fy=0.5) dets = detector(img, 1) #使用檢測運算元檢測人臉,返回的是所有的檢測到的人臉區域 for k, d in enumerate(dets): rec = dlib.rectangle(d.rect.left(),d.rect.top(),d.rect.right(),d.rect.bottom()) shape = sp(img, rec) #獲取landmark face_descriptor = facerec.compute_face_descriptor(img, shape) #使用resNet獲取128維的人臉特徵向量 faceArray = np.array(face_descriptor).reshape((1, 128)) #轉換成numpy中的資料結構 data = np.concatenate((data, faceArray)) #拼接到事先準備好的data當中去 label.append(labelName) #儲存標籤 cv2.rectangle(img, (rec.left(), rec.top()), (rec.right(), rec.bottom()), (0, 255, 0), 2) #顯示人臉區域 cv2.waitKey(2) cv2.imshow('image', img) data = data[1:, :] #因為data的第一行是空的128維向量,所以實際儲存的時候從第二行開始 np.savetxt('faceData.txt', data, fmt='%f') #儲存人臉特徵向量合成的矩陣到本地 labelFile=open('label.txt','w') json.dump(label, labelFile) #使用json儲存list到本地 labelFile.close() cv2.destroyAllWindows() #關閉所有的視窗
上面的程式碼中,會索引imagePath這個存放影象的目錄;然後定義一個128維的空向量data,在後續獲取每一張人臉特徵向量的時候可以往這個向量後面追加,即data的每一行是一個樣本的特徵向量;然後定義一個list來儲存標籤。之後開始索引某個目錄下所有的圖片檔案。注意我這裡用的是opencv的介面讀取影象,也可以使用其他的影象讀取介面,比如dlib自帶的或者PIL介面中的,都可以使用,不過重要的是介面一定要統一,因為每個介面讀取圖片轉成矩陣的數值可能會有差異。然後使用前面定義的測運算元開始檢測人臉,返回的是dlib中的一個數據結構,這個資料結構儲存了所有檢測到的人臉區域資訊,對每個檢測到的人臉區域獲取landmark,並且呼叫深度殘差模型的介面獲取128維的人臉特徵向量,之後我們把這個人臉向量儲存到data中去,這裡使用numpy中提供的concatenate方法進行拼接,同時把標籤新增到label列表中去。最後,因為data事先定義的是一個128維的空向量,之後利用concatenate方法進行拼接得到,我們需要拋棄第一行;最後把得到的人臉特徵和標籤儲存到本地檔案。
這裡使用的是CNN進行人臉檢測,如果你沒有GPU,或者你有GPU但沒有進行GPU的配置,那麼速度巨慢,此時你可以使用傳統的HOG特徵+級聯分類的方法,不過效果沒有CNN的好。這時程式碼的第6行中模型需要替換成:
detector = dlib.get_frontal_face_detector()
其餘的基本保持不變。
以上的程式碼可以直接執行,執行之後會檢測所有的影象,類似於:
並且存取得到本地的人臉特徵向量庫和標籤:
(2)實時讀取攝像頭進行人臉識別
在(1)中我們已經得到了本地的打過標籤的人臉特徵向量,這一部分是實現讀取攝像頭實時識別。首先載入需要的python庫:
import dlib import numpy as np import cv2 import json
然後載入神經網路模型:
detector = dlib.cnn_face_detection_model_v1('mmod_human_face_detector.dat') sp = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat') facerec = dlib.face_recognition_model_v1('dlib_face_recognition_resnet_model_v1.dat') threshold = 0.54
其中threshold是人臉識別的閾值,當測試圖片和本地圖片歐式距離最近的值大於這個值的時候,我們認為不屬於本都圖片的任何一個類別。然後定義最近鄰分類器:
def findNearestClassForImage(face_descriptor, faceLabel): temp = face_descriptor - data e = np.linalg.norm(temp,axis=1,keepdims=True) min_distance = e.min() print('distance: ', min_distance) if min_distance > threshold: return 'other' index = np.argmin(e) return faceLabel[index]
當距離值大於threshold的時候我們返回標籤“other”,否則返回本地的標籤,你可以根據實際情況來設定這個閾值。題外話,安全閾值很依賴於具體的場合,比如安檢、銀行裡進行人臉驗證、iPhone解鎖,這些對安全要求很高的場合需要比較小的threshold來保證安全,在嫌犯追蹤的時候,需要比較大的threshold以保證由嫌疑的人不會漏過。
然後是讀取影象進行識別的函式:
def recognition(img): dets = detector(img, 1) for k, d in enumerate(dets): print("Detection {}: Left: {} Top: {} Right: {} Bottom: {}".format( k, d.rect.left(), d.rect.top(), d.rect.right(), d.rect.bottom())) rec = dlib.rectangle(d.rect.left(),d.rect.top(),d.rect.right(),d.rect.bottom()) print(rec.left(),rec.top(),rec.right(),rec.bottom()) shape = sp(img, rec) face_descriptor = facerec.compute_face_descriptor(img, shape) class_pre = findNearestClassForImage(face_descriptor, label) print(class_pre) cv2.rectangle(img, (rec.left(), rec.top()+10), (rec.right(), rec.bottom()), (0, 255, 0), 2) cv2.putText(img, class_pre , (rec.left(),rec.top()), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2, cv2.LINE_AA) cv2.imshow('image', img)
最後是實時讀取攝像頭影象,並且進行識別的過程:
labelFile=open('label.txt','r') label = json.load(labelFile) #載入本地人臉庫的標籤 labelFile.close() data = np.loadtxt('faceData.txt',dtype=float) #載入本地人臉特徵向量 cap = cv2.VideoCapture(0) fps = 10 size = (640,480) fourcc = cv2.VideoWriter_fourcc(*'XVID') videoWriter = cv2.VideoWriter('video.MP4', fourcc, fps, size) while(1): ret, frame = cap.read() #frame = cv2.resize(frame, (0,0), fx=0.5, fy=0.5) recognition(frame) videoWriter.write(frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() videoWriter.release() cv2.destroyAllWindows()
在上面的程式碼中為了展示檢測的效果,我用opencv的介面把影象儲存到了視訊當中。識別效果截圖:
四、總結
利用已有的計算機視覺庫可以實現很多好玩和有用的應用,本文只是粗略地展示了一個進行實時人臉識別的demo,還有很多可以改善的點來提高精度和效率,比如人臉受角度、表情影響很大,或者需要處理速度要求更高的場景;同時影象類別規模很大的情況下如何保證效果,如何優化這些都是難點。另外dlib中的提供的這些模型都是已經訓練好的,我們可以到官方demo下載,demo給出了在一些benchmark中的效果,也可以自己訓練得到這些模型,當然前提是你需要有GPU,並且要求很大量的資料以及豐富的調參經驗,這些也都是深度學習中的點~