使用opencv進行人臉識別
介紹
從OpenCV2.4開始,加入了新的類FaceRecognizer,我們可以使用它便捷地進行人臉識別實驗。本文既介紹程式碼使用,又介紹演算法原理。
目前支援的演算法有
Eigenfaces特徵臉createEigenFaceRecognizer()
Fisherfaces createFisherFaceRecognizer()
LocalBinary Patterns Histograms區域性二值直方圖 createLBPHFaceRecognizer()
下面所有的例子中的程式碼在OpenCV安裝目錄下的samples/cpp下面都能找到,所有的程式碼商用或者學習都是免費的。
注:Opencv 3.0以後人臉識別的部分被分離開到opencv_contrib,如果想用人臉識別的類,需要將opencv_contrib 模組新增到Opencv裡面,新增方法可查詢網路。
人臉識別
對人類來說,人臉識別很容易。僅僅是才三天的嬰兒已經可以區分周圍熟悉的人臉了。那麼對於計算機來說,到底有多難?其實,迄今為止,我們對於人類自己為何可以區分不同的人所知甚少。是人臉內部特徵(眼睛、鼻子、嘴巴)還是外部特徵(頭型、髮際線)對於人類識別更有效?我們怎麼分析一張影象,大腦是如何對它編碼的?我們的大腦針對不同的場景,如線、邊、角或者運動這些區域性特徵有專門的神經細胞作出反應。顯然我們沒有把世界看成零散的塊塊,我們的視覺皮層必須以某種方式把不同的資訊來源轉化成有用的模式。自動人臉識別就是如何從一幅影象中提取有意義的特徵,把它們放入一種有用的表示方式,然後對他們進行一些分類。基於幾何特徵的人臉的人臉識別可能是最直觀的方法來識別人臉。
資料準備
我們從網上下了資料,下了我們需要在程式中讀取它。
很多網上的事例都介紹使用csv檔案來標記圖片和對應的標籤,這裡我們不做這一步,因為我們已經知道每張圖片屬於哪個類別,CSV檔案只是為了更直觀的讀人臉圖片和標籤。如果下載的圖片檔名是沒有規律的,我們可以用Python將檔名修改為特定的組合方式,例如我們讀入圖片輸入正確的圖片名和標籤,這裡也貼出Python3.4的程式碼。
import os import os.path #三個引數:分別返回1.父目錄 2.所有資料夾名字(不含路徑) 3.所有檔名字 #輸出資料夾資訊 #輸出檔案資訊 #輸出檔案路徑資訊 #rootdir = 'd:\\code\python_image_learn\identfying_code_recognize\charSamples\charSamples' rootdir = 'Z:\\workSpace\TestForMySelf\opencvTest\study\code\python_image_learn\identfying_code_recognize\charSamples' oldparent = '' i = 0; for parent,dirnames,filenames in os.walk(rootdir): for dirname in dirnames: print ("parent is:" + parent) print ("dirname is" + dirname) for filename in filenames: if oldparent != parent : i = 0 oldparent = parent print ("parent is:" + parent) print ("filename is:" + filename) print ("the full name of the file is:" + os.path.join(parent,filename)) os.rename(os.path.join(parent, filename), os.path.join(parent, str(i)+'.png')) i = i + 1
人臉識別
opencv進行人臉識別的方法有三種。
特徵臉(Eigenfaces)
影象表示的問題是他的高維問題。二維灰度影象p*q大小,是一個m=qp維的向量空間,所以一個100*100畫素大小的影象就是10,000維的影象空間。問題是,是不是所有的維數空間對我們來說都有用?我們可以做一個決定,如果資料有任何差異,我們可以通過尋找主元來知道主要資訊。
其解決方法就是降維,例如上面舉例,如果是 100*100畫素的影象,就會對應一個有10000維的特徵向量,那麼如何能有效的把裡面的冗餘資訊給剔除就是該演算法的主要用處。該演算法也就是著名的PCA,把高維資料降成低維資料,減少處理時間與空間。
PCA計算過程
假設我們有 2維資料如下:
X , Y
M1[2.5 , 2,4]
M2[0.5, 0.7]
M3[2.2, 2.9]
`````````
`````````
M10[1.1, 0.9]
行代表了樣例 ,列代表特徵 列代表特徵 列代表特徵 ,這裡有 10 個樣例 ,每個樣例兩特徵。可以這認 每個樣例兩特徵。可以這認 為,有 10 篇文件 ,x是 10 篇文件中 “learn” 出現的 TF -IDFIDFIDF,y是 10 篇文件中 “study” 出現的 TF -IDF。也可以認為有 有 10 輛汽車 ,x是千米 /小時的速度 ,y是英里 /小時的速 度,等.
第一步:分別求 x和 y的平均值 ,然後對於所有的樣例 然後對於所有的樣例 然後對於所有的樣例 ,都減去對應的均值。這裡 都減去對應的均值。這裡 都減去對應的均值。這裡 x第一步的均值是 1.81 1.81,y的均值是 1.911.91 ,那麼一個樣例減去均值後即為( 0.69 0.69 ,0.49 0.49 ),
第二步: 求特徵協方差矩陣 ,如果資料是 3維,那麼協方差矩陣是
這樣 就將原始樣例的n維特徵變成了K維,這K維就是原始資料特徵在K維上的投影。
該處理方法其實是求協防差矩陣的特徵值和特徵向量,按照特徵值大的排序,選取K個特徵向量做為對映矩陣。
然後特徵臉通過下面的方式進行人臉識別:
A. 把所有的訓練資料投影到PCA子空間
B. 把待識別影象投影到PCA子空間
C. 找到訓練資料投影后的向量和待識別影象投影后的向量最近的那個(求其歐氏距離)。
Opencv 實現程式碼:
#include<opencv2/opencv.hpp> #include<opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> #include <opencv2/ml/ml.hpp> #include <opencv2/face/facerec.hpp> #include<iostream> #include<sstream> using namespace std; using namespace cv; using namespace ml; using namespace face; static Mat norm_0_255(InputArray _src) { Mat src = _src.getMat(); // 建立和返回一個歸一化後的影象矩陣: Mat dst; switch(src.channels()) { case 1: cv::normalize(_src, dst, 0,255, NORM_MINMAX, CV_8UC1); break; case 3: cv::normalize(_src, dst, 0,255, NORM_MINMAX, CV_8UC3); break; default: src.copyTo(dst); break; } return dst; } int main() { Mat image,testimage; vector<Mat> images; vector<int> labels; string path = "att_faces/"; int num = 0; int classfilternum = 40; int modlenum = 10; for(int i = 1 ; i <= classfilternum ; i++){ for(int j = 1; j <= modlenum; j++){ ostringstream oss; oss<<path<<i<<"/"<<j<<".pgm"; //cout<<oss.str()<<endl; image=imread(oss.str(), 0); images.push_back(image); labels.push_back(i); if(i == 30 && j == 10){ ostringstream oss; oss<<path<<i<<"/"<<j<<".pgm"; //cout<<oss.str()<<endl; testimage=imread(oss.str(), 0); } if(j == 10){ images.pop_back(); labels.pop_back(); } } } imshow("test",testimage); cout<<"images = "<<images.size()<<endl; Ptr<BasicFaceRecognizer> model = createEigenFaceRecognizer(10); //Ptr<BasicFaceRecognizer> model = createFisherFaceRecognizer(); //Ptr<FaceRecognizer> model = createLBPHFaceRecognizer(); model->train(images, labels); int prediclabel; double confidence; model->predict(testimage, prediclabel, confidence); cout << "prediclabel = "<<prediclabel<<" confidence= "<<confidence << endl; // Here is how to get the eigenvalues of this Eigenfaces model: //Mat eigenvalues = model->getEigenValues(); //imshow("test1",eigenvalues); // And we can do the same to display the Eigenvectors (read Eigenfaces): //Mat W = model->getEigenVectors(); //imshow("test2",W); // Get the sample mean from the training data //Mat mean = model->getMean(); //imshow("mean", norm_0_255(mean.reshape(1, images[0].rows))); waitKey(0); return 0; }
FisherFaces
主成分分析是一種基於特徵臉的方法,找到使資料中最大方差的特徵線性組合。這是一個表現資料的強大方法,但它沒有考慮類別資訊,並且在扔掉主元時,同時許多有鑑別的資訊都被扔掉。假設你資料庫中的變化主要是光照變化,那麼PCA此時幾乎失效了。線性鑑別分析在降維的同時考慮類別資訊,由統計學家 Sir R. A. Fisher發明。在他1936年的文獻中,他成功對花進行了分類:The useof multiple measurements in taxonomic problems [Fisher36]。為了找到一種特徵組合方式,達到最大的類間離散度和最小的類內離散度。這個想法很簡單:在低維表示下,相同的類應該緊緊的聚在一起,而不同的類別儘量距離越遠。
該演算法的實現大部分是依賴於EigenFace 的實現原理,同時,該演算法增加了最大類間離散度S(b)和最小類內離散度S(w)。
S(t) = S(w)的逆 乘以 S(b)
然後求S(t) 的特徵值與特徵向量,選擇最大的K個特徵值的特徵向量 每個向量轉秩作為 對映矩陣,達到降維的目的(該過程與PCA過程一致,所以不做過多介紹)。
Fisherfaces方法學習一個正對標籤的轉換矩陣,所依它不會如特徵臉那樣那麼注重光照。鑑別分析是尋找可以區分人的面部特徵。需要說明的是,Fisherfaces的效能也很依賴於輸入資料。實際上,如果你對光照好的圖片上學習Fisherfaces,而想對不好的光照圖片進行識別,那麼他可能會找到錯誤的主元,因為在不好光照圖片上,這些特徵不優越。這似乎是符合邏輯的,因為這個方法沒有機會去學習光照。
區域性二值模式直方圖(cal Binary Patterns Histograms)
一些研究專注於影象區域性特徵的提取。主意是我們不把整個影象看成一個高維向量,僅僅用區域性特徵來描述一個物體。通過這種方式提取特徵,你將獲得一個低維隱式。一個好主意!但是你很快發現這種影象表示方法不僅僅遭受光照變化。你想想影象中的尺度變化、形變、旋轉—我們的區域性表示方式起碼對這些情況比較穩健。正如SIFT,LBP方法在2D紋理分析中舉足輕重。LBP的基本思想是對影象的畫素和它區域性周圍畫素進行對比後的結果進行求和。把這個畫素作為中心,對相鄰畫素進行閾值比較。如果中心畫素的亮度大於等於他的相鄰畫素,把他標記為1,否則標記為0。你會用二進位制數字來表示每個畫素,比如11001111。因此,由於周圍相鄰8個畫素,你最終可能獲取2^8個可能組合,被稱為區域性二值模式,有時被稱為LBP碼。第一個在文獻中描述的LBP運算元實際使用的是3*3的鄰域。
LBP發展優化[編輯 | 編輯原始碼]
LBP提取特徵向量
對LBP影象成m個塊,每個塊提取直方圖。通過連線區域性特直方圖(而不是合併)然後就能得到空間增強的特徵向量。這些直方圖被稱為區域性二值模式直方圖。
得到對應的特徵向量以後 同樣根據前面的方法,求取該圖片跟樣本的特徵向量的歐式距離來判斷該圖片屬於哪一類。