【火爐煉AI】機器學習055-使用LBP直方圖建立人臉識別器
(本文所使用的Python庫和版本號: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2 )
在我前面的博文 ofollow,noindex">【火爐煉AI】機器學習052-OpenCV構建人臉鼻子眼睛檢測器 中,講到了人臉檢測的方法和程式碼實現,但在很多實際場合,我們需要做的是人臉識別,即判斷圖片中的那張臉是張三還是李四,故而本篇文章我們來看看如何使用LBP直方圖來建立一個人臉識別器。
1. 區域性二值模式簡介
區域性二值模式(Local Binary Pattern, LBP)是一種用來描述影象區域性紋理特徵的運算元,其最大優勢在於旋轉不變性,灰度不變性,能夠多分辨分析。區域性紋理分析有很多潛在的應用,比如工業表層檢測,遠端監控,影象分析等。
LBP的基本思想是:原始的LBP運算元是3*3的視窗,以中心畫素為閾值,將相鄰的8個畫素的灰度值與中心畫素進行比較,如果大於,則設為1,小於則為0,故而得到這9個畫素的二值化圖,故而名稱為區域性二值化,如下圖所示。從二值化圖的左邊中心點畫素為起點,逆時針方向為正方形,按順序取該二值化數值,便得到圖中Pattern的二進位制數值,此數值就是一個LBP編碼,此時,我們稱該中心畫素點的LBP值為11110001。如果對一幅影象中的所有畫素點都計算LBP值,得到的就是這幅圖的LBP特徵圖。

關於灰度不變性:很明顯,原始的區域性圖中如果灰度值都同時增加一個值或同時減去一個值,便相當於亮度增加或減少,但此時,得到的LBP編碼不變,故而稱為灰度不變性。需要注意的是:該灰度不變性僅僅適用於灰度值的單調變化。如下圖

上面的LBP運算元有一個缺陷,它只覆蓋一個固定半徑範圍內的小區域,這顯然不能滿足不同尺寸和頻率紋理的需要,故而有人對其進行改進,將3*3領域擴充套件到任意領域,並用圓形領域代替正方形領域,如下圖為以中心畫素點為圓心,R為半徑,在圓上均勻的選取P個點作為取樣點的情況。

上圖中,R的大小決定了圓的大小,反映了二維空間的尺度;而P的大小決定了取樣點數,反映了角度空間的解析度。同樣的,我們還可以改變R和P的值,實現不同的尺度和角度解析度(如下圖)。這也是以後“多解析度分析”的理論基礎。

上面的LBP運算元雖然能夠實現多解析度,但卻不是旋轉不變性,影象的旋轉會得到不同的LBP值,故有人提出了具有旋轉不變性的LBP運算元,即不斷旋轉圓形領域得到一系列初始定義的LBP值,取其最小值作為該領域的LBP值。

在LBP的應用中,比如人臉識別,紋理分析中,我們一般不將LBP圖譜作為特徵向量用於分類識別,而是採用LBP特徵圖的統計直方圖來作為特徵向量。
使用LBP直方圖來進行特徵提取的步驟一般為:
1) 首先將檢測視窗劃分為16×16的小區域(cell)
2) 對於每個cell中的一個畫素,將相鄰的8個畫素的灰度值與其進行比較,若周圍畫素值大於中心畫素值,則該畫素點的位置被標記為1,否則為0。這樣,3*3鄰域內的8個點經比較可產生8位二進位制數,即得到該視窗中心畫素點的LBP值
3) 然後計算每個cell的直方圖,即每個數字(假定是十進位制數LBP值)出現的頻率;然後對該直方圖進行歸一化處理
4)最後將得到的每個cell的統計直方圖進行連線成為一個特徵向量,也就是整幅圖的LBP紋理特徵向量;然後便可利用SVM或者其他機器學習演算法進行分類了。
關於LBP的深入理論,可以參考博文: LBP(區域性二值模式)特徵提取原理 和 區域性二值模式(Local Binary Patterns)進行紋理分類
2. 準備資料集
本專案所用的資料集是臉部資料集的一個子集,此處我只選擇三個人的臉部圖片來進行測試。資料集有兩部分,一個train的資料夾中有三個子資料夾,每個子資料夾代表一個人的臉部圖片,test的資料夾只含有各種人臉圖片,沒有子資料夾。所以首先我們需要將這些圖片載入到記憶體中,下面定義一個函式來載入圖片。
# 定義一個函式來載入圖片資料集 def load_train_set(imgs_folder,face_cascade): ''' 從imgs_folder中載入圖片資料和標記,注意imgs_folder中包含有多個子資料夾,每個子資料夾的名稱就是label ''' folders=glob(os.path.join(imgs_folder,'*')) imgs_paths=[] [imgs_paths.extend(glob(os.path.join(folder, '*.*'))) for folder in folders] face_imgs=[] labels=[] # 對每一張圖片都檢測畫面上的人臉 for img_path in imgs_paths: image = cv2.imread(img_path, 0) label=os.path.split(img_path)[0] img_folder=os.path.split(img_path)[0] faces = face_cascade.detectMultiScale(image, 1.1, 2, minSize=(100,100)) for (x, y, w, h) in faces: face_imgs.append(image[y:y+h, x:x+w]) labels.append(os.path.split(img_folder)[1]) # 此處有點不合理,本資料集中每張圖片只有一個人臉,故而可以用這個方式, # 如果有多個不同人的臉,則不能用折衝方式。 # 將labels轉換為數字 label_encoder=LabelEncoder() encode_labels=label_encoder.fit_transform(labels) return face_imgs, encode_labels, label_encoder,labels 複製程式碼
測試下上面的函式是否正常,且顯示下載入的臉部照片
# 測試上面函式是否正常 face_cascade=cv2.CascadeClassifier('E:\PyProjects\DataSet\FireAI\cascade_files/haarcascade_frontalface_alt.xml') face_imgs, labels, label_encoder,labels=load_train_set('E:\PyProjects\/DataSet\FireAI\/faces_dataset/train',face_cascade) print(len(face_imgs)) # 有53張臉,但是檢測得到56個結果,顯然有幾張圖片中檢測了多張臉 # 顯示任一張人臉 # 由於cv2讀取的是BGR,而plt是RGB,故而需要轉化一下 plt.imshow(face_imgs[3],cmap='gray') 複製程式碼
從列印的結果可以看出,多了三張圖片,說明有三張不是臉部照片的圖片混入,故而需要找出來刪除。定義一個函式來找出錯誤圖片
def find_false_faces(face_imgs): ''' 將所有臉部照片顯示出來,如果發現有錯誤的,按d鍵,記錄下錯誤的臉部照片 ''' need_del_ids=[] for idx,face in enumerate(face_imgs): cv2.namedWindow('check', cv2.WINDOW_NORMAL) cv2.resizeWindow('check', 500, 500) cv2.imshow('check', face) key = cv2.waitKey(0) if key==27: # 如果輸入時Esc,則退出迴圈 print('esc to exit') break elif key==100: # 如果輸入d鍵,則記錄該臉對應的id need_del_ids.append(idx) cv2.destroyAllWindows() print('finished...') return need_del_ids 複製程式碼



故而需要從原始資料集中刪除這三張圖片以及對應的label資訊
# 從資料集中刪除這三張照片對應的資訊 face_imgs=np.delete(np.array(face_imgs), need_del_ids, axis=0) encode_labels=np.delete(np.array(encode_labels), need_del_ids, axis=0) labels=np.delete(np.array(labels), need_del_ids,axis=0) print(face_imgs.shape) # 53張圖沒錯,元素已經變成了np.ndarray,故而只有行 複製程式碼
3. 構建LBP直方圖識別器
此處的LBP直方圖識別器相當於一個分類模型,cv2已經幫我們封裝好了這個分類模型,我們只需要呼叫即可。
# 構建createLBPHFaceRecognizer分類模型 from cv2.face import LBPHFaceRecognizer_create recognizer=LBPHFaceRecognizer_create() recognizer.train(face_imgs, encode_labels) # 模型訓練 複製程式碼
一旦人臉識別器模型訓練好之後,就可以用來進行人臉識別了,下面看看識別新圖片的人臉結果。
# 用訓練好的模型預測新照片 def predict_imgs(new_imgs_folder, face_cascade,recognizer,label_encoder): ''' 用訓練好的人臉識別器來識別人臉''' img_paths=glob(new_imgs_folder+'/*.*') predicted_imgs=[] for img_path in img_paths: image=cv2.imread(img_path) gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) faces=face_cascade.detectMultiScale(gray,1.1, 2, minSize=(100,100)) for (x, y, w, h) in faces: cv2.rectangle(image,(x,y),(x+w,y+h),(0,0,255),3) predicted_index, conf = recognizer.predict(gray[y:y+h, x:x+w]) predicted_label=label_encoder.inverse_transform([predicted_index])[0] cv2.putText(image, predicted_label,(x,y-20), cv2.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), 3) predicted_imgs.append(image) return predicted_imgs 複製程式碼
得到的結果分別為:



當然,這個識別器只能識別訓練圖片中已經有的人臉,對於訓練集中沒有的人臉,它會預測不準確。比如,拿鳳姐的圖片來預測一下試試。

估計鳳姐這張照片和Person3長的比較像,所以本模型將其預測為Person3
########################小**********結###############################
1,LBP直方圖模型可以快速訓練並快速識別,在人臉識別領域中有著比較廣泛的應用。
#################################################################
注:本部分程式碼已經全部上傳到( 我的github )上,歡迎下載。
參考資料:
1, Python機器學習經典例項,Prateek Joshi著,陶俊傑,陳小莉譯