介紹html
人臉識別是什麼?或識別是什麼?當你看到一個蘋果時,你的大腦會馬上告訴你這是一個蘋果。在這個過程當中,你的大腦告訴你這是一個蘋果水果,用簡單的語言來講就是識別。那麼什麼是人臉識別呢?我確定你猜對了。當你看着你的朋友走在街上或他的照片時,你會認出他是你的朋友保羅。有趣的是,當你看你的朋友或他的照片時,你首先要看他的臉,而後再看其餘東西。你想過爲何要這麼作嗎?這是爲了讓你看他的臉就能認出他來。好吧,這是你的面部識別。python
但真正的問題是人臉識別是如何工做的?它很是簡單和直觀。舉一個現實生活中的例子,當你在生活中第一次碰見一我的,你不認識他,對吧?當他和你說話或握手時,你看着他的臉、眼睛、鼻子、嘴巴、顏色和總體的表情。這是你經過收集面部數據來學習或訓練那我的的面部識別。而後他告訴你他叫保羅。此時,你的大腦知道它剛剛學到的面部數據屬於保羅。如今你的頭腦已經訓練好了,準備在保羅的臉上作面部識別。下次當你在照片中看到保羅或他的臉時,你會馬上認出他。這就是人臉識別的工做原理。你遇到保羅的次數越多,你的大腦就會收集到更多關於他的信息,尤爲是他的臉,你就越能識別他。git
下一個問題是如何用OpenCV編碼人臉識別,畢竟這是你閱讀這篇文章的惟一緣由,對吧?那麼,好吧。你可能會說咱們的大腦能夠很容易地作這些事情,可是把它們編碼到電腦裏是很困難的嗎?別擔憂,不是的。多虧了OpenCV,編碼人臉識別變得愈來愈容易。人臉識別的編碼步驟與咱們在上面的實際示例中討論的同樣。github
·訓練數據收集:收集您想要識別的人的面部數據(本例中爲面部圖像)算法
·識別器的訓練:將人臉數據(以及每一個人臉的相應名稱)輸入人臉識別器,使其可以學習。編程
·識別:輸入這些人的新面孔,看看你剛訓練過的人臉識別器是否識別他們數組
OpenCV帶有內置的人臉識別器,你所要作的就是給它輸入人臉數據。這很簡單,一旦咱們完成了編碼,它就會看起來很簡單。網絡
OpenCV面部識別器機器學習
OpenCV有三個內置的人臉識別器,多虧了OpenCV乾淨的編碼,你能夠經過改變一行代碼來使用它們中的任何一個。下面是這些人臉識別器的名稱和它們的OpenCV調用
一、EigenFaces人臉識別器識別器 - cv2.face.createEigenFaceRecognizer()
二、FisherFaces人臉識別器識別器 - cv2.face.createFisherFaceRecognizer()
三、局部二值模式直方圖(LBPH)人臉識別器 - cv2.face.createLBPHFaceRecognizer()
如今咱們有三我的臉識別器,可是你知道該用哪個嗎?何時用嗎?或者哪一個更好?我猜你不知道。那麼,接下來咱們將深刻研究每個識別器。
EigenFaces面部識別器
這個算法考慮的事實是,並非臉的全部部分都一樣重要,或一樣有用。當你看一我的的時候,你會經過他獨特的特徵認出他/她,好比眼睛、鼻子、臉頰、前額以及他們之間的差別。因此你實際上關注的是最大變化的區域(數學上說,這個變化是方差)。例如,從眼睛到鼻子有一個顯著的變化,從鼻子到嘴也是如此。當你看多張臉的時候你能夠經過看臉的這些部分來比較它們由於這些部分是臉最有用和最重要的組成部分。重要的是,它們捕捉到人臉之間的最大變化,這種變化能夠幫助你區分不一樣的人臉,這就是特徵人臉識別系統的工做原理。
EignFaces人臉識別器將全部人的訓練圖像做爲一個總體,並試圖提取重要和有用的成分(捕捉最大方差/變化的成分),並丟棄其他的成分。這樣,它不只從訓練數據中提取重要的組件,並且經過丟棄不重要的組件來節省內存。它提取的這些重要成分被稱爲主成分。
我所用主成分,方差,高變化區域,可互換的有用特徵等術語,它們的性質基本上是同樣的東西。
如下是顯示從面部列表中提取的主要組件的圖像。
主成分
你能夠看到,主份量實際上表示面的,這些面被稱爲特徵面,也就是算法的名字。
這就是特徵面識別器自身的訓練方式(經過提取主成分),它還記錄了哪一個主成分屬於哪一個人。在上面的圖像中須要注意的一點是特徵面算法也將光照做爲一個重要的組成部分。
在隨後的識別過程當中,當你向算法輸入新圖像時,它也會在該圖像上重複一樣的過程。它重新映像中提取主組件,並將該組件與它在訓練期間存儲的組件列表進行比較,並找到匹配最好的組件,並返回與該最佳匹配組件關聯的person標籤。
輕鬆+容易,對吧? 下一個比這個更容易。
FisherFaces人臉識別器
該算法是改進後的FisherFaces人臉識別算法。FisherFaces人臉識別器同時查看全部人的訓練面,並從全部人的訓練面中找到主要的組成部分。經過從全部的人臉中捕獲主要的組成部分,你並無把注意力集中在區分一我的和另外一我的的特徵上,而是集中在表明整個訓練數據中全部人的全部面孔的特徵上。
這種方法有一個缺點。 例如,考慮下面的面光照變化。
你知道特徵麪人臉識別器也認爲照明是一個重要的組成部分,對吧?想象一個場景,一我的全部的臉都有很是高的亮度變化(很是暗或者很是亮等等)。特徵人臉識別者將會考慮這些光照變化很是有用的特徵,而且可能會忽略其餘人的面部特徵,認爲這些特徵不太有用。如今所提取的特徵特徵面只表明一我的的面部特徵,而不是全部人的面部特徵。
如何解決這個問題? 咱們能夠經過調整EigenFaces人臉識別器來解決這個問題,以便從每一個人的臉部分別提取有用的特徵,而不是提取全部臉部組合的有用特徵。 這樣,即便一我的的光照變化很大,也不會影響其餘人物特徵提取過程。 這正是FisherFaces人臉識別器算法的功能。
Fisherfaces算法不是提取表示全部人員全部面部的有用特徵,而是提取可區分一我的和另外一我的的有用特徵。 經過這種方式,一我的的特徵不會佔據主導地位(被認爲是更有用的特徵)而其餘人則具備區分一我的和另外一我的的特徵。
下面是使用Fisherfaces算法提取的特徵的圖像。
Fisher Faces
你能夠看到提取的特徵實際上表明瞭面孔,這些面被稱爲Fisher faces,所以算法的名稱。
這裏須要注意的一點是,Fisherfaces人臉識別器只會阻止一我的的特徵凌駕於另外一我的的特徵之上,但它仍然認爲光照變化是有用的特徵。咱們知道光照變化不是一個有用的特徵來提取,由於它不是真正的臉的一部分。那麼,該怎麼擺脫這個照明問題?這就是咱們的下一我的臉識別器鎖解決的問題。
局部二值模式直方圖(LBPH)人臉識別器
咱們知道Eigenfaces和Fisherfaces都受光線影響,在現實生活中,咱們沒法保證完美的光照條件。 LBPH人臉識別器是克服這個缺點的一種改進。
這種想法是不看整個圖像,而是查找圖像的局部特徵。 LBPH算法試圖找出圖像的局部結構,並經過比較每一個像素與其相鄰像素來實現。
取一個3x3的窗口,每移動一個圖像(圖像的每一個局部),將中心的像素與相鄰像素進行比較。強度值小於或等於中心像素的鄰域用1表示,其它鄰域用0表示。而後你以順時針的順序讀取3x3窗口下的0/1值,你會獲得一個像11100011這樣的二進制模式,這個模式在圖像的特定區域是局部的。在整個圖像上這樣作,就會獲得一個局部二進制模式的列表。
LBP標籤
如今你明白爲何這個算法的名字中有局部二進制模式? 由於你獲得一個局部二進制模式列表。 如今你可能想知道,LBPH的直方圖部分呢? 在得到局部二進制模式列表後,您可使用二進制到十進制轉換將每一個二進制模式轉換爲十進制數(如上圖所示),而後對全部這些十進制值進行直方圖製做。 樣本直方圖是像下面這樣的。
樣本直方圖
我猜這回答了直方圖部分的問題。因此最終你會獲得訓練數據集中每一個人臉圖像的一個直方圖,這意味着若是訓練數據集中有100個圖像,那麼LBPH會在訓練後提取100個直方圖,並儲存起來以便之後識別。記住,算法也會跟蹤哪一個直方圖屬於哪一個人。
在識別後期,當您將新圖像送入識別器進行識別時,它將生成新圖像的直方圖,將該直方圖與其已有的直方圖進行比較,找到最佳匹配直方圖並返回與該最佳匹配關聯的人員標籤 匹配直方圖。
下面是一張臉和它們各自的局部二進制模式圖像的列表。您能夠看到,LBP圖像不受光照條件變化的影響。
局部人臉
理論部分已經結束,如今是編碼部分!準備好開始編寫代碼了嗎?那咱們開始吧。
使用OpenCV編碼人臉識別
本教程中的人臉識別過程分爲三個步驟。
一、準備訓練數據:在這一步中,咱們將讀取每一個人/主體的訓練圖像及其標籤,從每一個圖像中檢測人臉併爲每一個檢測到的人臉分配其所屬人員的整數標籤。
二、訓練人臉識別器:在這一步中,咱們將訓練OpenCV的LBPH人臉識別器,爲其提供咱們在步驟1中準備的數據。
三、測試:在這一步中,咱們會將一些測試圖像傳遞給人臉識別器,並查看它是否可以正確預測它們
編程工具:
注:Numpy使Python中的計算變得容易。 除此以外,它還包含一個強大的N維數組實現,咱們將使用它來將數據做爲OpenCV函數的輸入。
導入必需的模塊
在開始實際編碼以前,咱們須要導入所需的編碼模塊。 因此讓咱們先導入它們。
cv2:是Python的OpenCV模塊,咱們將用它來進行人臉檢測和人臉識別。
os:咱們將使用這個Python模塊來讀取咱們的培訓目錄和文件名。
numpy:咱們將使用此模塊將Python列表轉換爲numpy數組,由於OpenCV人臉識別器接受numpy數組。
#導入OpenCV模塊 import cv2 #導入os模塊用於讀取訓練數據目錄和路徑 import os # 導入numpy將python列表轉換爲numpy數組,OpenCV面部識別器須要它 import numpy as np
訓練數據
訓練中使用的圖像越多越好。 一般不少圖像用於訓練面部識別器,以便它能夠學習同一我的的不一樣外觀,例如戴眼鏡,不戴眼鏡,笑,傷心,快樂,哭泣,留着鬍子,沒有鬍子等。 簡單的教程咱們將只爲每一個人使用12張圖片。
因此咱們的訓練數據由共2人組成,每一個人有12張圖像。 全部培訓數據都在培訓數據文件夾內。 訓練數據文件夾包含每一個人的一個文件夾,而且每一個文件夾以格式sLabel(例如s1,s2)命名,其中標籤其實是分配給該人的整數標籤。 例如,名爲s1的文件夾意味着該文件夾包含人員1的圖像。培訓數據的目錄結構樹以下所示:
training-data |-------------- s1 | |-- 1.jpg | |-- ... | |-- 12.jpg |-------------- s2 | |-- 1.jpg | |-- ... | |-- 12.jpg
測試數據文件夾包含咱們將用於在成功培訓完成後測試人臉識別器的圖像
因爲OpenCV人臉識別器接受標籤爲整數,所以咱們須要定義整數標籤和人物實際名稱之間的映射,因此下面我定義了人員整數標籤及其各自名稱的映射。
注意:因爲咱們還沒有將標籤0分配給任何人,所以標籤0的映射爲空。
#咱們的訓練數據中沒有標籤0,所以索引/標籤0的主題名稱爲空 subjects = ["", "Ramiz Raja", "Elvis Presley"]
準備訓練數據
您可能想知道爲何要進行數據準備,對嗎? 那麼,OpenCV人臉識別器接受特定格式的數據。 它接受兩個矢量,一個矢量是全部人的臉部,第二個矢量是每一個臉部的整數標籤,所以在處理臉部時,臉部識別器會知道該臉部屬於哪一個人。
例如,若是咱們有兩我的和兩個圖像爲每一個人。
PERSON-1 PERSON-2 img1 img1 img2 img2
而後,準備數據步驟將生成如下面和標籤向量。
FACES LABELS person1_img1_face 1 person1_img2_face 1 person2_img1_face 2 person2_img2_face 2
準備數據步驟能夠進一步分爲如下子步驟。
一、閱讀培訓數據文件夾中提供的全部主題/人員的文件夾名稱。 例如,在本教程中,咱們有文件夾名稱:s1,s2。
二、對於每一個主題,提取標籤號碼。 你還記得咱們的文件夾有一個特殊的命名約定嗎? 文件夾名稱遵循格式sLabel,其中Label是一個整數,表明咱們已分配給該主題的標籤。 所以,例如,文件夾名稱s1表示主題具備標籤1,s2表示主題標籤爲2等。 將在此步驟中提取的標籤分配給在下一步中檢測到的每一個面部。
三、閱讀主題的全部圖像,從每張圖像中檢測臉部。
四、將添加到標籤矢量中的具備相應主題標籤(在上述步驟中提取)的每一個臉部添加到臉部矢量。
#使用OpenCV用來檢測臉部的函數 def detect_face(img): #將測試圖像轉換爲灰度圖像,由於opencv人臉檢測器須要灰度圖像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #加載OpenCV人臉檢測器,我正在使用的是快速的LBP #還有一個更準確但緩慢的Haar分類器 face_cascade = cv2.CascadeClassifier('opencv-files/lbpcascade_frontalface.xml') #讓咱們檢測多尺度(一些圖像可能比其餘圖像更接近相機)圖像 #結果是一張臉的列表 faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5); #若是未檢測到面部,則返回原始圖像 if (len(faces) == 0): return None, None #假設只有一張臉, #提取面部區域 (x, y, w, h) = faces[0] #只返回圖像的正面部分 return gray[y:y+w, x:x+h], faces[0]
我正在使用OpenCV的LBP人臉檢測器。 在第4行,我將圖像轉換爲灰度,由於OpenCV中的大多數操做都是以灰度進行的,而後在第8行使用cv2.CascadeClassifier類加載LBP人臉檢測器。 在第12行以後,我使用cv2.CascadeClassifier類'detectMultiScale方法來檢測圖像中的全部面部。 在第20行中,從檢測到的臉部我只挑選第一張臉部,由於在一張圖像中只有一張臉部(假設只有一張醒目的臉部)。 因爲detectMultiScale方法返回的面其實是矩形(x,y,寬度,高度),而不是實際的面部圖像,因此咱們必須從主圖像中提取面部圖像區域。 因此在第23行我從灰色圖像中提取人臉區域並返回人臉圖像區域和人臉矩形。
如今您已經有了一個面部檢測器,您知道準備數據的4個步驟,那麼您準備好編寫準備數據步驟了嗎?是嗎?讓咱們來作它。
#該功能將讀取全部人的訓練圖像,從每一個圖像檢測人臉 #並將返回兩個徹底相同大小的列表,一個列表 # 每張臉的臉部和另外一列標籤 def prepare_training_data(data_folder_path): #------STEP-1-------- #獲取數據文件夾中的目錄(每一個主題的一個目錄) dirs = os.listdir(data_folder_path) #列表來保存全部主題的面孔 faces = [] #列表以保存全部主題的標籤 labels = [] #讓咱們瀏覽每一個目錄並閱讀其中的圖像 for dir_name in dirs: #咱們的主題目錄以字母's'開頭 #若是有的話,忽略任何不相關的目錄 if not dir_name.startswith("s"): continue; #------STEP-2-------- #從dir_name中提取主題的標籤號 #目錄名稱格式= slabel #,因此從dir_name中刪除字母''會給咱們標籤 label = int(dir_name.replace("s", "")) #創建包含當前主題主題圖像的目錄路徑 #sample subject_dir_path = "training-data/s1" subject_dir_path = data_folder_path + "/" + dir_name #獲取給定主題目錄內的圖像名稱 subject_images_names = os.listdir(subject_dir_path) #------STEP-3-------- #瀏覽每一個圖片的名稱,閱讀圖片, #檢測臉部並將臉部添加到臉部列表 for image_name in subject_images_names: #忽略.DS_Store之類的系統文件 if image_name.startswith("."): continue; #創建圖像路徑 #sample image path = training-data/s1/1.pgm image_path = subject_dir_path + "/" + image_name #閱讀圖像 image = cv2.imread(image_path) #顯示圖像窗口以顯示圖像 cv2.imshow("Training on image...", image) cv2.waitKey(100) #偵測臉部 face, rect = detect_face(image) #------STEP-4-------- #爲了本教程的目的 #咱們將忽略未檢測到的臉部 if face is not None: #將臉添加到臉部列表 faces.append(face) #爲這張臉添加標籤 labels.append(label) cv2.destroyAllWindows() cv2.waitKey(1) cv2.destroyAllWindows() return faces, labels
我已經定義了一個函數,它將存儲培訓主題文件夾的路徑做爲參數。 該功能遵循上述的4個準備數據子步驟。
(step--1)在第8行,我使用os.listdir方法讀取存儲在傳遞給函數的路徑上的全部文件夾的名稱做爲參數。 在第10-13行,我定義了標籤並面向矢量。
(step--2)以後,我遍歷全部主題的文件夾名稱以及第27行中每一個主題的文件夾名稱,我將提取標籤信息。 因爲文件夾名稱遵循sLabel命名約定,因此從文件夾名稱中刪除字母將給咱們分配給該主題的標籤。
(step--3)在第34行,我讀取了當前被攝體的全部圖像名稱,而且在第39-66行中我逐一瀏覽了這些圖像。 在53-54行,我使用OpenCV的imshow(window_title,image)和OpenCV的waitKey(interval)方法來顯示當前正在傳播的圖像。 waitKey(interval)方法將代碼流暫停給定的時間間隔(毫秒),我以100ms的間隔使用它,以便咱們能夠查看100ms的圖像窗口。 在第57行,我從當前正在遍歷的圖像中檢測出臉部。
(step--4)在第62-66行,我將檢測到的面和標籤添加到它們各自的向量中。
可是一個函數只能在須要準備的某些數據上調用它時才能作任何事情,對嗎? 別擔憂,我有兩張臉的數據。 我相信你至少會認出其中的一個!
讓咱們在這些美麗的名人的圖像上調用這個函數來準備數據來訓練咱們的人臉識別器。 下面是一個簡單的代碼來作到這一點。
#讓咱們先準備好咱們的訓練數據 #數據將在兩個相同大小的列表中 #一個列表將包含全部的面孔 #數據將在兩個相同大小的列表中 print("Preparing data...") faces, labels = prepare_training_data("training-data") print("Data prepared") #打印總面和標籤 print("Total faces: ", len(faces)) print("Total labels: ", len(labels))
訓練人臉識別器
咱們知道,OpenCV配備了三我的臉識別器。
一、EigenFaces人臉識別器識別器 - cv2.face.createEigenFaceRecognizer()
二、FisherFaces人臉識別器識別器 - cv2.face.createFisherFaceRecognizer()
三、局部二值模式直方圖(LBPH)人臉識別器 - cv2.face.createLBPHFaceRecognizer()
我將使用LBPH人臉識別器,但您可使用您選擇的任何人臉識別器。 不管您使用哪一個OpenCV的臉部識別器,其代碼都將保持不變。 您只需更改一行,即下面給出的面部識別器初始化行。
#建立咱們的LBPH人臉識別器 face_recognizer = cv2.face.createLBPHFaceRecognizer() #或者使用EigenFaceRecognizer替換上面的行 #face_recognizer = cv2.face.createEigenFaceRecognizer() #或者使用FisherFaceRecognizer替換上面的行 #face_recognizer = cv2.face.createFisherFaceRecognizer()
如今咱們已經初始化了咱們的人臉識別器,也準備了咱們的訓練數據,如今是時候訓練人臉識別器了。咱們將經過調用人臉識別器的序列(面向量,標籤向量)方法來實現這一點。
#訓練咱們的面部識別器 face_recognizer.train(faces, np.array(labels))
你有沒有注意到,不是直接將標籤矢量直接傳遞給人臉識別器,而是先把它轉換成numpy數組?這是由於OpenCV但願標籤向量是一個numpy數組。
仍然不滿意? 想看到一些行動? 下一步是真正的行動,我保證!
預測
如今是我最喜歡的部分,預測部分。 這是咱們真正瞭解咱們的算法是否確實可以識別受過訓練的對象臉部的地方。 咱們將拍攝兩張咱們的景點的測試圖像,從他們每一個人身上檢測臉部,而後將這些臉部傳遞給咱們訓練有素的臉部識別器,看看它們是否識別它們。
下面是一些實用功能,咱們將用它來繪製圍繞臉部的邊界框(矩形)並將邊界名稱放在邊界框附近。
#函數在圖像上繪製矩形 #根據給定的(x,y)座標和 #給定的寬度和高度 def draw_rectangle(img, rect): (x, y, w, h) = rect cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2) #函數在從圖像開始繪製文本 #經過(x,y)座標。 def draw_text(img, text, x, y): cv2.putText(img, text, (x, y), cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0), 2)
第一個函數draw_rectangle根據傳入的矩形座標在圖像上繪製一個矩形。 它使用OpenCV的內置函數cv2.rectangle(img,topLeftPoint,bottomRightPoint,rgbColor,lineWidth)繪製矩形。 咱們將使用它在測試圖像中檢測到的臉部周圍畫一個矩形。
第二個函數draw_text使用OpenCV的內置函數cv2.putText(img,text,startPoint,font,fontSize,rgbColor,lineWidth)在圖像上繪製文本。
既然咱們有繪圖功能,咱們只須要調用人臉識別器預測(人臉)方法來測試咱們的測試圖像上的人臉識別器。 如下功能爲咱們作了預測。
#this function recognizes the person in image passed #and draws a rectangle around detected face with name of the #學科 def predict(test_img): #製做圖像的副本,由於咱們不想更改原始圖像 img = test_img.copy() #從圖像中檢測臉部 face, rect = detect_face(img) #使用咱們的臉部識別器預測圖像 label= face_recognizer.predict(face) #獲取由人臉識別器返回的相應標籤的名稱 label_text = subjects[label] #在檢測到的臉部周圍畫一個矩形 draw_rectangle(img, rect) #畫預計人的名字 draw_text(img, label_text, rect[0], rect[1]-5) return img
第6行讀取測試圖像
第7行從測試圖像中檢測臉部
第11行經過調用面部識別器的預測(面部)方法來識別面部。 該方法將返回一個標籤
第12行獲取與標籤關聯的名稱
第16行在檢測到的臉部周圍繪製矩形
第18行繪製預測主體在面部矩形上方的名稱
如今咱們已經很好地定義了預測函數,下一步就是在咱們的測試圖像上實際調用這個函數,並顯示這些測試圖像以查看咱們的人臉識別器是否能正確識別它們。 因此讓咱們來作。 這就是咱們一直在等待的。
print("Predicting images...") #加載測試圖像 test_img1 = cv2.imread("test-data/test1.jpg") test_img2 = cv2.imread("test-data/test2.jpg") #執行預測 predicted_img1 = predict(test_img1) predicted_img2 = predict(test_img2) print("Prediction complete") #顯示兩個圖像 cv2.imshow(subjects[1], predicted_img1) cv2.imshow(subjects[2], predicted_img2) cv2.waitKey(0) cv2.destroyAllWindows()
Predicting images... Prediction complete
結語
你能夠從這個Github下載完整的代碼和相關文件 打開GitHab.
人臉識別是一個很是有趣的想法,OpenCV使得它很是簡單,易於咱們對其進行編碼。 只需幾行代碼便可完成全面工做的人臉識別應用程序,而且咱們能夠經過一行代碼更改在全部三我的臉識別器之間切換。 就這麼簡單。
儘管EigenFaces,FisherFaces和LBPH人臉識別器都不錯,可是使用面向梯度直方圖(HOG)和神經網絡進行人臉識別還有更好的方法。 所以,更先進的人臉識別算法如今是一個使用OpenCV和機器學習相結合的日子。 我還計劃寫一些關於這些更高級方法的文章,敬請關注!