声明:本文代码来源于http://www.cognotics.com/opencv/servo_2007_series/,实现平台为Linux+OpenCV,共分为两部分:人脸检测与人脸识别。本文为后半部分的代码,关于第一部分请参见http://blog.youkuaiyun.com/liudekuan/article/details/8560251。不多言,以下给出代码及相关注解
- #include <stdio.h>
- #include <string.h>
- #include "cv.h"
- #include "cvaux.h"
- #include "highgui.h"
- using namespace cv;
- //globle variables
- int nTrainFaces = 0; // number of trainning images
- int nEigens = 0; // number of eigenvalues
- IplImage** faceImgArr = 0; // array of face images
- CvMat* personNumTruthMat = 0; // array of person numbers
- IplImage* pAvgTrainImg = 0; // the average image
- IplImage** eigenVectArr = 0; // eigenvectors
- CvMat* eigenValMat = 0; // eigenvalues
- CvMat* projectedTrainFaceMat = 0; // projected training faces
- //// Function prototypes
- void learn();
- void recognize();
- void doPCA();
- void storeTrainingData();
- int loadTrainingData(CvMat** pTrainPersonNumMat);
- int findNearestNeighbor(float* projectedTestFace);
- int loadFaceImgArray(char* filename);
- void printUsage();
- int main( int argc, char** argv )
- {
- if((argc != 2) && (argc != 3)){
- printUsage();
- return -1;
- }
- if( !strcmp(argv[1], "train" )){
- learn();
- } else if( !strcmp(argv[1], "test") ){
- recognize();
- } else {
- printf("Unknown command: %s\n", argv[1]);
- }
- return 0;
- }
- void printUsage(){
- printf("Usage: eigenface <command>\n",
- " Valid commands are\n"
- " train\n"
- " test\n"
- );
- }
- void learn(){
- int i;
- // load training data
- nTrainFaces = loadFaceImgArray("train.txt");
- if( nTrainFaces < 2){
- fprintf(
- stderr,
- "Need 2 or more training faces\n"
- "Input file contains only %d\n",
- nTrainFaces
- );
- return;
- }
- // do PCA on the training faces
- doPCA();
- // project the training images onto the PCA subspace
- projectedTrainFaceMat = cvCreateMat(nTrainFaces, nEigens, CV_32FC1);
- for(i = 0; i < nTrainFaces; i ++){
- cvEigenDecomposite(
- faceImgArr[i],
- nEigens,
- eigenVectArr,
- 0, 0,
- pAvgTrainImg,
- projectedTrainFaceMat->data.fl + i*nEigens
- );
- }
- // store the recognition data as an xml file
- storeTrainingData();
- }
- int loadFaceImgArray(char* filename){
- FILE* imgListFile = 0;
- char imgFilename[512];
- int iFace, nFaces = 0;
- // open the input file
- imgListFile = fopen(filename, "r");
- // count the number of faces
- while( fgets(imgFilename, 512, imgListFile) ) ++ nFaces;
- rewind(imgListFile);
- // allocate the face-image array and person number matrix
- faceImgArr = (IplImage **)cvAlloc( nFaces*sizeof(IplImage *) );
- personNumTruthMat = cvCreateMat( 1, nFaces, CV_32SC1 );
- // store the face images in an array
- for(iFace=0; iFace<nFaces; iFace++){
- //read person number and name of image file
- fscanf(imgListFile, "%d %s", personNumTruthMat->data.i+iFace, imgFilename);
- // load the face image
- faceImgArr[iFace] = cvLoadImage(imgFilename, CV_LOAD_IMAGE_GRAYSCALE);
- }
- fclose(imgListFile);
- return nFaces;
- }
- void doPCA(){
- int i;
- CvTermCriteria calcLimit;
- CvSize faceImgSize;
- // set the number of eigenvalues to use
- nEigens = nTrainFaces - 1;
- // allocate the eigenvector images
- 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);
- }
- // allocate the eigenvalue array
- eigenValMat = cvCreateMat( 1, nEigens, CV_32FC1 );
- // allocate the averaged image
- pAvgTrainImg = cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);
- // set the PCA termination criterion
- calcLimit = cvTermCriteria( CV_TERMCRIT_ITER, nEigens, 1);
- // compute average image, eigenvalues, and eigenvectors
- cvCalcEigenObjects(
- nTrainFaces,
- (void*)faceImgArr,
- (void*)eigenVectArr,
- CV_EIGOBJ_NO_CALLBACK,
- 0,
- 0,
- &calcLimit,
- pAvgTrainImg,
- eigenValMat->data.fl
- );
- }
- void storeTrainingData(){
- CvFileStorage* fileStorage;
- int i;
- // create a file-storage interface
- fileStorage = cvOpenFileStorage( "facedata.xml", 0, CV_STORAGE_WRITE);
- // store all the data
- 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));
- }
- //release the file-storage interface
- cvReleaseFileStorage( &fileStorage );
- }
- void recognize(){
- int i, nTestFaces = 0; // the number of test images
- CvMat* trainPersonNumMat = 0; // the person numbers during training
- float* projectedTestFace = 0;
- // load test images and ground truth for person number
- nTestFaces = loadFaceImgArray("test.txt");
- printf("%d test faces loaded\n", nTestFaces);
- // load the saved training data
- if( !loadTrainingData( &trainPersonNumMat ) ) return;
- // project the test images onto the PCA subspace
- projectedTestFace = (float*)cvAlloc( nEigens*sizeof(float) );
- for(i=0; i<nTestFaces; i++){
- int iNearest, nearest, truth;
- // project the test image onto PCA subspace
- 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;
- // create a file-storage interface
- 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);
- }
- // release the file-storage interface
- cvReleaseFileStorage( &fileStorage );
- return 1;
- }
- 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;
- }
- if(distSq < leastDistSq){
- leastDistSq = distSq;
- iNearest = iTrain;
- }
- }
- return iNearest;
- }
代码解析:
OpenCV实现了基于PCA的特征脸人脸识别方法,相关理论可参考Paul Viola和Michael Jones于2001年发表的《Rapid Object Detection using a Boosted Cascade of SimpleFeatures》。整个算法又分为样本训练和人脸识别两个过程,在上述代码中,分别通过函数learn()与recognize()来实现。在样本训练阶段,将样本库中的人脸图像转换为特征向量表示,并投影到PCA子空间,最终将这些向量数据保存到中间文件facedata.xml中。而在识别阶段,同样将待识别的人脸图像使用PCA子空间的向量表示,通过计算待识别图像的向量与样本中的向量之间的距离,寻找其中最相近的人脸图像,作为识别结果。
需要说明的是,在代码中训练样本图像及待识别的图像分别通过文本文件train.txt和test.txt记录,所用图像均为pgm格式,train.txt及test.txt中的内容如下所示:
其中,在这两个文件中,每一行记录一幅人脸图像。每条记录开始的数字表示人的序号,紧跟其后的则是此照片的存储路径。显然,此例在训练样本时只使用了序号为1,2,4三个人的第一幅照片,而识别时则对这三个人的多幅人脸图像进行了识别。其最终的识别结果如下所示:
nearest表示最相似的样本图像的序号,而Truth则表示待识别图像的序号。当二者相同时,表示正确识别,否则识别错误。