COPY FROM:http://blog.csdn.net/longxiaoshi/article/details/7411333
人臉識別要牽涉到一些數學計算和一些算法的理解,雖然這些算法和計算opencv已經幫我們完成,但我們還是要對其有一定的瞭解,才能進行人臉識別的實踐,畢竟基礎不牢,上層建築也不穩。
要理解如何進行人臉識別,首先一定要理解主成分分析算法,即PCA,使用這種算法的原因是因爲,一般圖像數據量太大,而且其中的大部分數據點對我們進行人臉識別沒有太大的幫助,因此爲了減少數據量,採用了主成分分析法將數據進行壓縮(算法中稱爲投影),然後用壓縮後的圖像進行圖像識別的進一步應用。PCA的原理在前面的一篇文章中已經講到過,大家可以參考:PCA原理。肯能看完這個文章,大家開始對PCA有一定的理解,但總還是感覺有點模糊,因爲那篇文章我只是轉載,並沒有進行太多自己的想法的標註,因此有些混亂,那麼大家可以再繼續讀下面這篇文章:
PCA原理詳解。
在前面的PCA原理介紹中曾提到了兩個函數,cvCalcPCA,cvProjectPCA。我在實際使用中參考了另外一篇文章,用的是另外兩個函數,這個後面會講到。這個不用太糾結,前面的文章可以只當學習原理的參考,等原理懂了之後看後面將要介紹的兩個函數也會很簡單。其中,有一點要說明,cvCalcPCA是新的PCA處理函數,而後面將要講到的cvCalcEigenObjects是老的PCA處理函數。
經過上面的的兩片文章相信大家應該對PCA的原理有所瞭解,下面開始講怎樣實現用PCA算法進行人臉識別。這個可以參考這篇文章:
http://www.cognotics.com/opencv/servo_2007_series/part_5/index.html
這篇是英文文章,希望大家多讀英文文章,因爲我們在學習過程中如果遇到國內教材較少的情況,一般都是要參考國外的文章的,並且擅長閱讀英文文章也爲我們打開了通往另外一個更爲博大的知識庫的大門。讀完這篇文章一定可以自己做出人臉識別程序的,因爲這篇文章介紹的已經十分詳細。
但是,在這裏我還是將上面那篇文章的原理介紹一下。我下面的敘述主要是針對在一個圖像庫中找出我們給定的圖像最接近的圖像,以人臉圖像爲例也就是說數據庫中肯定是包含我所給出的這個人的人臉,雖然數據庫中的圖片跟我給出的圖片不是同一張圖片,但必須是同一個人。
首先假如我有代號爲1,2,3三個訓練人的人臉圖像,人臉大小調整爲一致,例如文章中爲92*112,在我們的分析中是以一個像素點作爲一維,因此每個圖像可以認爲是一個1*10304的行向量,這樣三個訓練人的人臉圖像就組成了一個3*10304的矩陣。(注意,實際中每個圖像還是存在92*112的iplimage結構中,這裏只是爲了講解方便而進行的假象)。根據這個矩陣,就可以計算出相應的協方差矩陣,大小爲10304*10304(這個大小我並沒有考究,我覺得應該是這個大小,因爲可以解釋通上面那篇英文文章),接着再求出這個10304*10304的矩陣的特徵值和特徵向量,並取前nEigens個(這個個數由我們自己確定,文章中是取nEigens爲訓練人個數-1)特徵值(注意由於前面特徵值已經是由大到小排好順序的,因此這幾個就是最大的幾個)對應的特徵向量組成10304*nEigens大小投影矩陣(注意這裏發生了轉置,有opencv函數內部完成),即所謂的特徵臉(因爲每一列的規模就是一張人臉圖像的規模),也即子空間。然後再將每個大小爲1*10304的訓練人臉圖像矩陣乘以這個投影矩陣,就可以得到每個圖像在主成分子空間的投影,大小爲1*nEigens,並用這個投影矩陣進行後續的分析。可見,經過投影后圖像數據量大大減小。上面的計算投影矩陣的過程由cvCalcEigenObjects完成,而投影則由cvEigenDecomposite函數完成,具體實現見英文文章。爲了方便理解這兩個函數可以參見:PCA的兩個主要函數,這個空間內有兩篇介紹這兩個函數的文章。
同樣,當給出測試圖像時,先將測試人臉縮放爲與訓練人臉相同的大小,即92*112,然後用前面的特徵矩陣將其投影到子空間中,即投影爲大小爲1*nEigens的矩陣。接着就可以用這個子空間投影跟前面的訓練圖像在子空間的投影進行比較,最接近的就是目標圖像。比較方法文章中也有提到,有歐式算法 Euclidean
和較新的 Mahalanobis算法。
至此,整個人臉識別原理已經講完,雖然有點複雜,但花點時間也不難理解。
下面貼出文章中給出的程序,我進行了一些註釋,使用方法程序頭部也有介紹。
- // eigenface.c, by Robin Hewitt, 2007
- //
- // Example program showing how to implement eigenface with OpenCV
- // Usage:
- //
- // First, you need some face images. I used the ORL face database.
- // You can download it for free at
- // www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html
- //
- // List the training and test face images you want to use in the
- // input files train.txt and test.txt. (Example input files are provided
- // in the download.) To use these input files exactly as provided, unzip
- // the ORL face database, and place train.txt, test.txt, and eigenface.exe
- // at the root of the unzipped database.
- //
- // To run the learning phase of eigenface, enter
- // eigenface train
- // at the command prompt. To run the recognition phase, enter
- // eigenface test
- #include <stdio.h>
- #include <string.h>
- #include "cv.h"
- #include "cvaux.h"
- #include "highgui.h"
- ////定義幾個重要的全局變量
- IplImage ** faceImgArr = 0; // 指向訓練人臉和測試人臉的指針(在學習和識別階段指向不同)
- CvMat * personNumTruthMat = 0; // 人臉圖像的ID號
- int nTrainFaces = 0; // 訓練圖像的數目
- int nEigens = 0; // 自己取的主要特徵值數目
- IplImage * pAvgTrainImg = 0; // 訓練人臉數據的平均值
- IplImage ** eigenVectArr = 0; // 投影矩陣,也即主特徵向量
- CvMat * eigenValMat = 0; // 特徵值
- CvMat * projectedTrainFaceMat = 0; // 訓練圖像的投影
- //// 函數原型
- void learn();
- void recognize();
- void doPCA();
- void storeTrainingData();
- int loadTrainingData(CvMat ** pTrainPersonNumMat);
- int findNearestNeighbor(float * projectedTestFace);
- int loadFaceImgArray(char * filename);
- void printUsage();
- //主函數,主要包括學習和識別兩個階段,需要運行兩次,通過命令行傳入的參數區分
- void main( int argc, char** argv )
- {
- // validate that an input was specified
- if( argc != 2 )
- {
- printUsage();
- return;
- }
- //通過判斷命令行參數分別執行學習和識別代碼
- if( !strcmp(argv[1], "train") ) learn();
- else if( !strcmp(argv[1], "test") ) recognize();
- else
- {
- printf("Unknown command: %s\n", argv[1]);
- printUsage();
- }
- }
- //學習階段代碼
- void learn()
- {
- int i, offset;
- //加載訓練圖像集
- nTrainFaces = loadFaceImgArray("train.txt");
- if( nTrainFaces < 2 )
- {
- fprintf(stderr,
- "Need 2 or more training faces\n"
- "Input file contains only %d\n", nTrainFaces);
- return;
- }
- // 進行主成分分析
- doPCA();
- //將訓練圖集投影到子空間中
- projectedTrainFaceMat = cvCreateMat( nTrainFaces, nEigens, CV_32FC1 );
- offset = projectedTrainFaceMat->step / sizeof(float);
- for(i=0; i<nTrainFaces; i++)
- {
- //int offset = i * nEigens;
- cvEigenDecomposite(
- faceImgArr[i],
- nEigens,
- eigenVectArr,
- 0, 0,
- pAvgTrainImg,
- //projectedTrainFaceMat->data.fl + i*nEigens);
- projectedTrainFaceMat->data.fl + i*offset);
- }
- //將訓練階段得到的特徵值,投影矩陣等數據存爲.xml文件,以備測試時使用
- storeTrainingData();
- }
- //識別階段代碼
- void recognize()
- {
- int i, nTestFaces = 0; // 測試人臉數
- CvMat * trainPersonNumMat = 0; // 訓練階段的人臉數
- float * projectedTestFace = 0;
- // 加載測試圖像,並返回測試人臉數
- nTestFaces = loadFaceImgArray("test.txt");
- printf("%d test faces loaded\n", nTestFaces);
- // 加載保存在.xml文件中的訓練結果
- if( !loadTrainingData( &trainPersonNumMat ) ) return;
- //
- projectedTestFace = (float *)cvAlloc( nEigens*sizeof(float) );
- for(i=0; i<nTestFaces; i++)
- {
- int iNearest, nearest, truth;
- //將測試圖像投影到子空間中
- cvEigenDecomposite(
- faceImgArr[i],
- nEigens,
- eigenVectArr,
- 0, 0,
- pAvgTrainImg,
- projectedTestFace);
- iNearest = findNearestNeighbor(projectedTestFace);
- truth = personNumTruthMat->data.i[i];
- nearest = trainPersonNumMat->data.i[iNearest];
- printf("nearest = %d, Truth = %d\n", nearest, truth);
- }
- }
- //加載保存過的訓練結果
- int loadTrainingData(CvMat ** pTrainPersonNumMat)
- {
- CvFileStorage * fileStorage;
- int i;
- fileStorage = cvOpenFileStorage( "facedata.xml", 0, CV_STORAGE_READ );
- if( !fileStorage )
- {
- fprintf(stderr, "Can't open facedata.xml\n");
- return 0;
- }
- nEigens = cvReadIntByName(fileStorage, 0, "nEigens", 0);
- nTrainFaces = cvReadIntByName(fileStorage, 0, "nTrainFaces", 0);
- *pTrainPersonNumMat = (CvMat *)cvReadByName(fileStorage, 0, "trainPersonNumMat", 0);
- eigenValMat = (CvMat *)cvReadByName(fileStorage, 0, "eigenValMat", 0);
- projectedTrainFaceMat = (CvMat *)cvReadByName(fileStorage, 0, "projectedTrainFaceMat", 0);
- pAvgTrainImg = (IplImage *)cvReadByName(fileStorage, 0, "avgTrainImg", 0);
- eigenVectArr = (IplImage **)cvAlloc(nTrainFaces*sizeof(IplImage *));
- for(i=0; i<nEigens; i++)
- {
- char varname[200];
- sprintf( varname, "eigenVect_%d", i );
- eigenVectArr[i] = (IplImage *)cvReadByName(fileStorage, 0, varname, 0);
- }
- cvReleaseFileStorage( &fileStorage );
- return 1;
- }
- //存儲訓練結果
- void storeTrainingData()
- {
- CvFileStorage * fileStorage;
- int i;
- fileStorage = cvOpenFileStorage( "facedata.xml", 0, CV_STORAGE_WRITE );
- //存儲特徵值,投影矩陣,平均矩陣等訓練結果
- cvWriteInt( fileStorage, "nEigens", nEigens );
- cvWriteInt( fileStorage, "nTrainFaces", nTrainFaces );
- cvWrite(fileStorage, "trainPersonNumMat", personNumTruthMat, cvAttrList(0,0));
- cvWrite(fileStorage, "eigenValMat", eigenValMat, cvAttrList(0,0));
- cvWrite(fileStorage, "projectedTrainFaceMat", projectedTrainFaceMat, cvAttrList(0,0));
- cvWrite(fileStorage, "avgTrainImg", pAvgTrainImg, cvAttrList(0,0));
- for(i=0; i<nEigens; i++)
- {
- char varname[200];
- sprintf( varname, "eigenVect_%d", i );
- cvWrite(fileStorage, varname, eigenVectArr[i], cvAttrList(0,0));
- }
- cvReleaseFileStorage( &fileStorage );
- }
- //尋找最接近的圖像
- int findNearestNeighbor(float * projectedTestFace)
- {
- double leastDistSq = DBL_MAX; //定義最小距離,並初始化爲無窮大
- int i, iTrain, iNearest = 0;
- for(iTrain=0; iTrain<nTrainFaces; iTrain++)
- {
- double distSq=0;
- for(i=0; i<nEigens; i++)
- {
- float d_i =
- projectedTestFace[i] -
- projectedTrainFaceMat->data.fl[iTrain*nEigens + i];
- distSq += d_i*d_i / eigenValMat->data.fl[i]; // Mahalanobis算法計算的距離
- // distSq += d_i*d_i; // Euclidean算法計算的距離
- }
- if(distSq < leastDistSq)
- {
- leastDistSq = distSq;
- iNearest = iTrain;
- }
- }
- return iNearest;
- }
- //主成分分析
- void doPCA()
- {
- int i;
- CvTermCriteria calcLimit;
- CvSize faceImgSize;
- // 自己設置主特徵值個數
- nEigens = nTrainFaces-1;
- //分配特徵向量存儲空間
- faceImgSize.width = faceImgArr[0]->width;
- faceImgSize.height = faceImgArr[0]->height;
- eigenVectArr = (IplImage**)cvAlloc(sizeof(IplImage*) * nEigens); //分配個數爲住特徵值個數
- for(i=0; i<nEigens; i++)
- eigenVectArr[i] = cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);
- //分配主特徵值存儲空間
- eigenValMat = cvCreateMat( 1, nEigens, CV_32FC1 );
- // 分配平均圖像存儲空間
- pAvgTrainImg = cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);
- // 設定PCA分析結束條件
- calcLimit = cvTermCriteria( CV_TERMCRIT_ITER, nEigens, 1);
- // 計算平均圖像,特徵值,特徵向量
- cvCalcEigenObjects(
- nTrainFaces,
- (void*)faceImgArr,
- (void*)eigenVectArr,
- CV_EIGOBJ_NO_CALLBACK,
- 0,
- 0,
- &calcLimit,
- pAvgTrainImg,
- eigenValMat->data.fl);
- cvNormalize(eigenValMat, eigenValMat, 1, 0, CV_L1, 0);
- }
- //加載txt文件的列舉的圖像
- int loadFaceImgArray(char * filename)
- {
- FILE * imgListFile = 0;
- char imgFilename[512];
- int iFace, nFaces=0;
- if( !(imgListFile = fopen(filename, "r")) )
- {
- fprintf(stderr, "Can\'t open file %s\n", filename);
- return 0;
- }
- // 統計人臉數
- while( fgets(imgFilename, 512, imgListFile) ) ++nFaces;
- rewind(imgListFile);
- // 分配人臉圖像存儲空間和人臉ID號存儲空間
- faceImgArr = (IplImage **)cvAlloc( nFaces*sizeof(IplImage *) );
- personNumTruthMat = cvCreateMat( 1, nFaces, CV_32SC1 );
- for(iFace=0; iFace<nFaces; iFace++)
- {
- // 從文件中讀取序號和人臉名稱
- fscanf(imgListFile,
- "%d %s", personNumTruthMat->data.i+iFace, imgFilename);
- // 加載人臉圖像
- faceImgArr[iFace] = cvLoadImage(imgFilename, CV_LOAD_IMAGE_GRAYSCALE);
- if( !faceImgArr[iFace] )
- {
- fprintf(stderr, "Can\'t load image from %s\n", imgFilename);
- return 0;
- }
- }
- fclose(imgListFile);
- return nFaces;
- }
- //
- void printUsage()
- {
- printf("Usage: eigenface <command>\n",
- " Valid commands are\n"
- " train\n"
- " test\n");
- }
通過上面的介紹,大家應該對人臉識別的方法有所瞭解,並針對自己的問題編寫人臉識別程序了