大家好,又见面了,我是你们的朋友全栈君。
项目要求
- 使用opencv实现对人脸库的主成分提取(不使用PCA类),完成特征模型保存
- 对一张测试照片进行识别,找到图片库中和测试图片最像的图
配置说明
- Opencv3.0
- VS2015
- Win10
配置过程网上太多了,就不做过多解释了,可以参照某个教程来做。主要的也就几步,下载Opencv,配Path,配置VC++目录的包含目录和库目录,配置链接器附加项的附加依赖项。
人脸库
结果
- 训练集是AT&T人脸库40×8(两张做测试用)
- 左图是输入的测试人脸,右图两张是人脸库中的匹配到后还原人脸
测试人脸 | 匹配结果 | 匹配结果(前50%特征向量) |
---|---|---|
前言
-
为什么要做PCA (principle component analysis)
主成分分析做的就是给了一堆很高维的数据,我们需要把它变成低维的数据,变成低维数据有一个好,最直观的就是数据量下来了。那图片举例,比如1000个样本,100×100的分辨率,以前1000×10000的数据,每个点就算是1Btye的数据,要10M。我把它映射到500维(取前50%特征向量)的空间,变成1000×500的数据,一下子降低到了500K,一下子降低了一个量级,更关键的是计算量瞬间也降了一个档次。一些场景如嵌入式设备上(如ZKteco的考勤机),计算和空间都是很奢侈的东西,我们的PCA就发挥作用了。
-
怎么做数据的降维
为什么要做降维原因还有很多很多,有兴趣的可以去查查,不在这里讲的主要原因是我自己也不知道。那么接下来就要分析怎么降维,降维大概的意思就是把一个数据点降到低维的数据点,比如XY二维的点映射到一维的话,如果是映射到x轴,那么所有点点乘个(1, 0)就好了;如果是三维的点降到一维那么点乘(1, 0, 0)就好了,降到二维点乘(1, 0, 0);(0, 1, 0)就好了,也就是相当于原来的数据矩阵乘了一个变换矩阵。问题来了,这个变换矩阵要怎么设定呢?是不是直接所有的都映射到X,Y,Z…轴上就好了呢?当然不是!比如下面有张数据点图表示男女的身高体重图
黑点表示男生,红点表示女生
X轴表示体重,Y轴表示身高
如果判断都是按照体重来划分的话,m1很可能就被判断成了女生,fm1则被判断成了男生。“虽然我体重轻一点,但是我身高比较高啊,我应该被划分成男生”。讲道理的话按照那条蓝线来分比较科学,为什么科学啊,因为比较符合图的分布,为什么符合分布啊?….这个时候有人就弄了一个PCA做降维的准则,有人说:“我觉得使得新的数据集方差最大的那种分法比较好。”大家想想,是这么个道理,方差比较小,大家挤成一团,很多数据点很容易重叠,也不容易区分。
数学推导
更加细致的推导可以去参考其他大学的pca课程ppt,如JHU这里有pca处理人脸步骤的详解,CMU的ppt则对整个过程的数学推导有详细的证明,CMU的ML课程主页还有更多的资源,是一个很好的学习的地方
看他们的ppt对PCA的理解帮助更大,如果想简单了解下直接看下我的证明也可以
假设数据集是
新的数据集是
原来的数据是p维,新的数据是k维,对于新数据的第一维数据
我们需要选择 使得最大
因此,目标变成了
用拉格朗日乘子法求值
(看不懂的话可以查The Matrix Cookbook)
因此最大的方差就是最大的特征值,对应的
是最大特征值对应的特征向量。
再来求
这个时候
需要满足要求使得
同理可得它就是第二大的特征根,我们要找投影方差最大的K个向量,也就是协方差矩阵的前K大特征值对应的特征向量。
所以为了保存最多的信息,数据变换到K维空间我们的变换矩阵就是
通过
进行数据的还原
讲了那么多我们队PCA的大概求解过程也了解了,那么,具体用在人脸识别的分析上面是否又是如此呢?有点小小的不一样。
我们这时候不是图像的灰度值来算协方差矩阵,而是
为什么呢,因为用offset值算的协方差矩阵,重构图像后的error是最小的。具体的推导我实在是懒得打了,把CMU课程上的两张推导图放上来,大家将就着看好了。
计算小Tips
我们在计算协方差矩阵的时候,会发现特征值没法算,为什么呢,一个图像一万维,10000×10000的矩阵特征值不是算爆炸了吗?你会发现自己opencv的函数半天输出不出结果,这个时候如果人脸训练样本比较少的话,假设
可以算
的特征向量,然后乘以 A,就可以得到要求得协方差矩阵的特征向量,证明如下(摘自JHU的ppt)
这再次说明了一个问题,多看看别人学校的课程公开的资源,用google随便一搜很多,或者是coursera,比很多博客上的靠谱全面太多了,囧
源码
有时间我再挑代码重点解释下吧,大家上面理解了,下面的大家应该基本都能够看懂,原谅我翔一样的代码,看个高兴就好了…人脸库是上面提到的ATT/T,上面有链接,可以自己去下
#include "opencv2/opencv.hpp"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
cv::String root_folder = "att_faces";
int population = 10;
int gesture = 8;
double EigenPerc = 0.5;
/* --load model data-- */
int img_rows;
cv::Mat LoadNewSpaceData, loadEigenVector, loadDataMean; // datamean is a mean face (a vector)
/* ---------------- */
void showFace(cv::String windowName, cv ::Mat faceVector, int rows) {
faceVector = faceVector.reshape(1, rows);
faceVector.convertTo(faceVector, CV_8U);
cv::imshow(windowName, faceVector);
}
void saveModel() {
cv::Mat dataMat; // original image data
int img_rows; // will be wriiten to model file
//read image data
for (int man = 1; man <= population; man++)
for (int pos = 1; pos <= gesture; pos++) {
char filename[50];
sprintf(filename, "att_faces/s%d/%d.pgm", man, pos);
cv::Mat img = cv::imread(filename);
cv::cvtColor(img, img, CV_RGB2GRAY);
img_rows = img.rows;
img = img.reshape(1, 1);
dataMat.push_back(img);
}
dataMat.convertTo(dataMat, CV_32F);
//calc covariance and dataMean (mean matrix will be written to model file)
cv::Mat cov, dataMean, dataMatOff, temp, temp12;
cv::reduce(dataMat, dataMean, 0, CV_REDUCE_SUM);
dataMean = dataMean / dataMat.rows;
cv::repeat(dataMean, dataMat.rows, 1, temp12); //tips: source and destination array can't be the same
cv::subtract(dataMat, temp12, dataMatOff);
cov = dataMatOff * dataMatOff.t();
// Calculate eigenVectors of the corvariance matrix of original image matrix
cv::Mat eigenVal, eigenVec;
cv::Mat eigenVecFloat;
cv::eigen(cov, eigenVal, eigenVec);
eigenVec = eigenVec.t();
eigenVec.convertTo(eigenVecFloat, CV_32F);
cv::Mat realEigenVec = dataMatOff.t() * eigenVecFloat;
/* normalize eigenvector */
cv::Mat temp1, temp2, temp3;
cv::pow(realEigenVec, 2, temp1);
cv::reduce(temp1, temp2, 0, CV_REDUCE_SUM);
cv::sqrt(temp2, temp2);
cv::repeat(temp2, temp1.rows, 1, temp3);
cv::divide(realEigenVec, temp3, realEigenVec);
cv::Mat newSpaceData, newFace;
int cutcols = (int)(EigenPerc * realEigenVec.cols);
if (cutcols == 0) cutcols = 1;
newSpaceData = dataMatOff * realEigenVec.colRange(0, cutcols);
std::cout << "cols: "<< cutcols << std::endl;
/*
newFace = newSpaceData * (realEigenVec.colRange(0, cutcols).t());
newFace = newFace + dataMean;*/
cv::FileStorage fsw("model.yml", cv::FileStorage::WRITE);
fsw << "imageRows" << img_rows;
fsw << "newSpaceData" << newSpaceData << "EigenVector" << realEigenVec.colRange(0, cutcols)
<< "dataMean" << dataMean;
fsw.release();
return;
}
void loadModel() {
cv::FileStorage fsr("model.yml", cv::FileStorage::READ);
img_rows = (int)fsr["imageRows"];
fsr["newSpaceData"] >> LoadNewSpaceData;
fsr["EigenVector"] >> loadEigenVector;
fsr["dataMean"] >> loadDataMean;
fsr.release();
cv::Mat newFace = LoadNewSpaceData * loadEigenVector.t();
cv::Mat expandDataMean = cv::repeat(loadDataMean, newFace.rows, 1);
newFace = newFace + expandDataMean;
//show face
showFace("model_face", newFace.rowRange(0, 1), img_rows);
}
void checkFace(cv::Mat testFace) {
cv::Mat testNewSpaceFace;
testFace.convertTo(testFace, CV_32F);
testNewSpaceFace = (testFace - loadDataMean) * loadEigenVector;
// find a nearest face from newSpaceDataLoad
float minDis = 3.402823466e+38F;
cv::Mat matchFace;
for (int i = 0; i < LoadNewSpaceData.rows; i++) {
//std::cout << testNewSpaceFace;
cv::Mat off = (testNewSpaceFace - LoadNewSpaceData.rowRange(i, i + 1));
cv::Mat val = off * off.t();
val.convertTo(val, CV_32F);
float dis;
dis = val.at<float>(0, 0);
if (dis < minDis) {
minDis = dis;
matchFace = LoadNewSpaceData.rowRange(i, i + 1) * loadEigenVector.t();
}
}
cv::Mat face8U;
matchFace = matchFace + loadDataMean;
matchFace.convertTo(face8U, CV_8U);
std::cout << minDis;
showFace("matched_face", face8U, img_rows);
cv::waitKey(0);
}
int main() {
saveModel();
loadModel();
cv::namedWindow("original_face");
cv::namedWindow("matched_face");
cv::namedWindow("model_face");
cv::Mat testFace = cv::imread("test.pgm");
imshow("original_face", testFace);
cv::cvtColor(testFace, testFace, CV_RGB2GRAY);
testFace = testFace.reshape(1, 1);
checkFace(testFace);
return 0;
}
CV的教材及参考资料
- Richard Szeliski, Computer Vision: Algorithms and Applications, 2010 Draft PDF, Sep.3 2010
- Simon J.D. Prince, Computer Vision: Models, Learning, and Inference, Cambridge University Press, Draft PDF, Jul.7 2012
- David A. Forsyth, Jean Ponce, Computer Vision: A Modern Approach (2nd Edition)
- Gary Bradski, Adrian Kaebler, Learning OpenCV, O’Reilly, 2008.
- Rafael C. Gonzalez, Richard E. Woods, Digital Image Processing, 2nd or 3rd, Prentice Hall,电子工业出版社(影印版,第二版2004年2月,第三版2010年1月)
- Richard O.Duda, Peter E.Hart, David G.Stork, Pattern Classification (2nd Edition), John Wiley, 机械工业出版社(影印版,中译版,2003年9月)
- 矩阵速查手册 the matrix cookbook,学习DM的时候Prof强力推荐的书,矩阵求导不知道怎么求的话可以看这本书
- numerical recipes in c++
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/135290.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...