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;如果你有GPU並且開啟了加速,那么實現的人臉識別程序速度非常快,可以滿足實時性,以我運行的結果來看,檢測+識別640X480像素的視頻流一秒鍾大約十幾幀;如果你沒有GPU那么速度就會很慢了,而且在檢測階段不能使用卷積神經網絡的方法了,否則檢測一幀數據可能需要幾秒甚至幾十秒:)
(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,並且要求很大量的數據以及豐富的調參經驗,這些也都是深度學習中的點~
(完)