问题描述:
前段时间实现了一个简单的人脸识别,主要是根据PCA和特征人脸法来实现,具体训练集有20个,如下图,测试集为10个,实现从测试集选一张图片,能够从训练库里匹配出最相近的。
解决思路:
这个问题听起来很简单,其实也很简单,只是有些地方有些小麻烦,比如图片的读取,CImage方法实在是太老了,所以我选择的是opencv来读取图片;还有C++对矩阵的运算,包括特征值和特征向量的计算,没有Matlab,Python等方便,当然你也可以选择自己编写函数,但是我选择了Eigen库,可以直接进行矩阵的计算,并且有C++接口。
关于opencv和Eigen的安装,我这里就不多说了,Eigen还好,opencv一定要注意版本和vs版本对应。可以参考这个教程(vs2010+opencv2.4.10) 点击打开链接
具体方案:
1)首先读取数据,建立数据库
2) 进行PCA降维,构建特征脸
3) 选取测试图片,与库进行比较,选取距离最小的图片
代码实现:
首先是读取图片,构建数据集,主要就是把读取的数据转到Eigen的矩阵中:
MatrixXd CreateDataBase()
{
MatrixXd database = MatrixXd::Zero(20,256*384);
for(int i=0;i<20;i++)
{
int count = 0;
Mat img=imread("Train/" + Int_to_String(i+1)+".jpg");
uchar* p;
for (int j = 0; j < 256; j++)
{
p = img.ptr<uchar>(j);
for (int k = 0; k < 384; k++)
{
database(i,count) = (double)p[k];
count++;
}
}
}
return database;
}
这里的MatrixXd是Eigen的动态矩阵写法,Int_to_String是整形到字符串转换函数,是自己编写的,为了防止有人不清楚,代码也贴出(头文件自行添加,主要是sstream):
string Int_to_String(int n)
{
ostringstream stream;
stream<<n;
return stream.str();
}
下一步就是
进行PCA降维,构建特征脸:
void computeCov(MatrixXd &X, MatrixXd &C)
{
//C = X.adjoint() * X;
//C = C.array() / X.rows() - 1;
C = X*X.transpose();
C = C/256*384;
}
void computeEig(MatrixXd &C, MatrixXd &vec, MatrixXd &val)
{
SelfAdjointEigenSolver<MatrixXd> eig(C);
vec = eig.eigenvectors();
val = eig.eigenvalues();
}
MatrixXd Choose(MatrixXd &vec, MatrixXd &val)
{
int count2 =0;
int temp[20];
for(int i=0;i<20;i++)
{
if(val(i) > 1)
{
temp[count2]=i;
count2++;
}
}
//cout<<count2++<<endl;
//cout<<temp[1]<<endl;
MatrixXd Lvec = MatrixXd::Zero(count2,20);
for(int j=0;j<20;j++)
{
if(temp[j]>=0)
{
for(int k=0;k<20;k++)
{
Lvec(j,k)=vec(temp[j],k);
}
}
}
return Lvec;
}
void CountEigenfaces(MatrixXd &A, MatrixXd &Lvec,MatrixXd &Eigenfaces)
{
Eigenfaces = A.transpose()*Lvec.transpose();
}
关于特征脸,不知道的可以去了解了解,不然代码可能看不懂,是人脸识别比较远古的方法,不过用在这个问题下效果还不错。上面主要进行了协方差,特征值,特征向量的计算,以及计算了特征脸矩阵。
最后一步就是进行匹配识别,分成几步,首先根据特征脸矩阵把训练集的20张图片转化成低纬度的向量组成的矩阵,然后是对测试集的选取和处理,需要注意的是,数据都要经过预处理,至少要去均值,因为计算协方差的时候默认了数据是去完均值的。
得到测试集和训练集的特征脸表示后,就可以将测试图片与训练集一一匹配,计算距离,(当然是利用降维后的矩阵进行计算),代码如下:
void ComputeTrainCompare(MatrixXd &A, MatrixXd &Eigenfaces, MatrixXd &traincompare)
{
traincompare = Eigenfaces.transpose()*A.transpose();
}
MatrixXd ChooseTestImage(int th)
{
MatrixXd testimg = MatrixXd::Zero(1,256*384);
Mat img=imread("Test/" + Int_to_String(th)+".jpg");
uchar* p;
int count = 0;
for (int j = 0; j < 256; j++)
{
p = img.ptr<uchar>(j);
for (int k = 0; k < 384; k++)
{
testimg(0,count) = (float)p[k];
count++;
}
}
return testimg;
}
void ComputeTestCompare(RowVectorXd &m, MatrixXd &testimg,MatrixXd &Eigenfaces,MatrixXd &testcompare)
{
MatrixXd testimg2;
testimg2 = normlizationMAX_MIN(testimg,256*384,1);
testimg2.rowwise() -=m;
testcompare = Eigenfaces.transpose()*testimg.transpose();
}
int ComputeDistance(MatrixXd &testcompare,MatrixXd &traincompare)
{
double Distance=0.0;
double dis=0.0;
double minDistance=INF;
cout<<"wuqiong" <<minDistance<<endl;
int matchno = 33;
for (int i = 0; i < 20; i++)
{
Distance = 0;
for (int j = 0; j < 20; j++)
{
dis +=(traincompare(j,i)-testcompare(j,0));
cout<<"d : "<<dis<<endl;
}
Distance = sqrt(dis*1.0);
if(Distance < minDistance)
{
minDistance = Distance;
matchno = i+1;
cout<<minDistance<<" "<<matchno<<endl;
}
}
return matchno;
}
#include "Createdatabase.h"
#include "Eigenfaces.h"
#include "Recognition.h"
#include <iostream>
#include <math.h>
#include <limits.h>
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
#include <Eigen\Dense>
using namespace Eigen;
#define INF 9999999999
void main()
{
MatrixXd d = MatrixXd::Zero(20,256*384);
d = CreateDataBase();
MatrixXd x;
x = normlizationMAX_MIN(d,256*384,20);
MatrixXd meanval = x.colwise().mean();
RowVectorXd meanvecRow = meanval;
Normalize(x);
MatrixXd c,s,vec,val;
computeCov(d, c);
computeEig(c,vec,val);
MatrixXd L;
L = Choose(vec,val);
MatrixXd ef;
CountEigenfaces(d,L,ef);
MatrixXd traincompare;
ComputeTrainCompare(d,ef,traincompare);
MatrixXd testm;
int a;
cout<<"please chosse a test image: \n"<<endl;
cin>>a;
testm = ChooseTestImage(int(a));
MatrixXd testcomp;
ComputeTestCompare(meanvecRow, testm,ef,testcomp);
int num;
num = ComputeDistance(testcomp,traincompare);
cout<<"matching image number is : "<<num<<endl;
system("pause");
}
Demo示例中也给出了头文件,可以进行对照。如果是直接复制代码就想执行的话是行不通的,因为代码分段给出,不太完整,而且依赖于版本,我个人还是建议理解代码,只要理解了就很容易排除问题,毕竟这个问题并不复杂。