转载

从QR分解到PCA,再到人脸识别

PCA(Principal Component Analysis,主成分分析)是一种很常用的根据变量协方差对数据进行降维、压缩的方法。它的精髓在于尽量用最少数量的维度,尽可能精确地描述数据。

PCA对数据进行降维的过程可以用下面这个动图来解释(图片摘自http://stats.stackexchange.com/a/140579/93946):

从QR分解到PCA,再到人脸识别 在上图中,一组位于直角坐标系的二维样本集,沿着斜线的方向有很强的相关性。所以如果我们将直角坐标系转换到斜向,也就是让横轴沿斜线方向,纵轴垂直于斜线方向。于是,在这个新的坐标系下,数据点在横轴上分布很分散,但是在纵轴方向比较集中。如果在误差允许范围内,我们完全可以将数据点在新纵轴上的坐标全部置为0,只用新横轴上的坐标来表示点的位置。这样,就完成了对数据降维的过程(即将原始直角坐标系的2个维度减小到新坐标系的1个维度)。对更高维的情况,处理过程与之类似。

PCA人脸识别

将PCA用于人脸识别的过程如下:

1. 假设有400幅尺寸为100*100的图像,构成10000*400的矩阵 从QR分解到PCA,再到人脸识别

2. 计算均值 从QR分解到PCA,再到人脸识别 ,令 从QR分解到PCA,再到人脸识别

3. 根据定义,计算协方差矩阵 从QR分解到PCA,再到人脸识别

4. 计算 从QR分解到PCA,再到人脸识别 的特征值与特征向量,取前h个最大特征值所对应的特征向量,构成矩阵 从QR分解到PCA,再到人脸识别

5. 矩阵 从QR分解到PCA,再到人脸识别 可对数据降维: 从QR分解到PCA,再到人脸识别 ,Y是h行400列的矩阵,也就是将数据从10000维降为h维。

这种做法一个明显的缺陷在于, 从QR分解到PCA,再到人脸识别 的维度为10000×10000,直接进行奇异值分解计算量非常大。利用QR分解,作间接的奇异值分解,可以减小计算量。

利用QR分解减小计算量

基于QR分解的PCA算法步骤如下:

1. 已知 从QR分解到PCA,再到人脸识别 ,其中 从QR分解到PCA,再到人脸识别 为d*d,H为d*n,d代表原始数据的维数,n代表样本数,d远大于n;

2. 对H作QR分解, 从QR分解到PCA,再到人脸识别 ,其中Q为d*t,R为t*n, 从QR分解到PCA,再到人脸识别

3.从QR分解到PCA,再到人脸识别 ,对 从QR分解到PCA,再到人脸识别 作奇异值分解 从QR分解到PCA,再到人脸识别 ,其中U为n*t,V为t*t, 从QR分解到PCA,再到人脸识别

4. 于是 从QR分解到PCA,再到人脸识别 ,其中 从QR分解到PCA,再到人脸识别

5. 由于 从QR分解到PCA,再到人脸识别 ,所以QV可将 从QR分解到PCA,再到人脸识别 对角化,QV为 从QR分解到PCA,再到人脸识别 的特征向量矩阵, 从QR分解到PCA,再到人脸识别从QR分解到PCA,再到人脸识别 的特征值矩阵;

6. 选取D前h个最大对角元所对应于V中的h个列,构成t*h的矩阵 从QR分解到PCA,再到人脸识别 ,则降维矩阵 从QR分解到PCA,再到人脸识别

该过程涉及对一个很大的矩阵的QR分解,和对一个较小矩阵的奇异值分解。计算量与传统方法相比较的结果如下(图片摘自[1]):

从QR分解到PCA,再到人脸识别

进一步,进行人脸识别的过程如下:

1. 假设有c个类别,每类包含s个样本,则n=c∗s;

2. 对X计算 从QR分解到PCA,再到人脸识别 ,将Y(也称特征脸)按类别计算均值,得到c个长度为h的列向量 从QR分解到PCA,再到人脸识别

3. 对于未知类别的新样本x,计算 从QR分解到PCA,再到人脸识别 ,y的长度为h;

4. 计算距离 从QR分解到PCA,再到人脸识别 ,取距离最小的i作为x的类标号。

距离度量d

1. 欧式距离

从QR分解到PCA,再到人脸识别

2. 曼哈顿距离

从QR分解到PCA,再到人脸识别

3. 马氏距离

从QR分解到PCA,再到人脸识别

在马氏距离中,x与y分布相同,且协方差矩阵为S。加入协方差矩阵的逆矩阵的作用是,将如下图(图片部分取自http://stats.stackexchange.com/a/62147/93946)中呈椭圆分布的数据归一化到圆形分布中,再来比较距离,可以抵消不同样本集在特征空间中的分布差异。

从QR分解到PCA,再到人脸识别

C++实现

环境要求:OpenCV(样本图像的读取),Armadillo(高性能线性代数C++函数库),Intel MKL(替代LAPACK为Armadillo提供矩阵分解计算)。

项目使用Visual Studio Ultimate 2012建立,不过核心代码只有一个cpp文件。

全部代码托管在 github.com/johnhany/QR-PCA-FaceRec 。

/************************************************************************/ /*                   QR-PCA-FaceRec  by John Hany                       */ /* */ /* A face recognition algorithm using QR based PCA.               */ /* */ /* Released under MIT license. */ /* */ /* Contact me at johnhany@163.com */ /* */ /* Welcome to my blog http://johnhany.net/, if you can read Chinese:) */ /* */ /************************************************************************/   #include <opencv2/opencv.hpp> #include <armadillo> #include <iostream>   usingnamespace std;   int component_num = 7;   string orl_path = "G://Datasets//orl_faces";   enum distance_type {ECULIDEAN = 0, MANHATTAN, MAHALANOBIS}; //double distance_criterion[3] = { 10.0, 30.0, 3.0}; double distance_criterion[3] = { 1000.0, 1000.0, 1000.0};   bool compDistance(pair<int, double> a, pair<int, double> b); double calcuDistance(const arma::vecvec1, const arma::vecvec2, distance_typedis_type); double calcuDistance(const arma::vecvec1, const arma::vecvec2, const arma::matcov2, distance_typedis_type);   int main(int argc, const char *argv[]) {    int class_num = 40;  int sample_num = 10;    int img_cols = 92;  int img_rows = 112;  cv::Sizesample_size(img_cols, img_rows);    arma::matmat_sample(img_rows*img_cols, sample_num*class_num);    //Load samples in one matrix `mat_sample`.    for(int class_idx = 0; class_idx < class_num; class_idx++) {  for(int sample_idx = 0; sample_idx < sample_num; sample_idx++) {    string filename = orl_path + "//s" + to_string(class_idx+1) + "//" + to_string(sample_idx+1) + ".pgm";  cv::Matimg_frame = cv::imread(filename, CV_LOAD_IMAGE_GRAYSCALE);  cv::Matimg_sample;  cv::resize(img_frame, img_sample, sample_size);    for(int i = 0; i < img_rows; i++) {  uchar* pframe = img_sample.ptr<uchar>(i);  for(int j = 0; j < img_cols; j++) {  mat_sample(i*img_cols+j, class_idx*sample_num+sample_idx) = (double)pframe[j]/255.0;  }  }  }  } // cout << mat_sample.n_rows << endl << mat_sample.n_cols << endl << mat_sample(img_rows*img_cols/2, 0) << endl;    //Calculate PCA transform matrix `mat_pca`.    arma::mat H = mat_sample;  arma::matmean_x = arma::mean(mat_sample, 1);    for(int j = 0; j < class_num * sample_num; j++) {  H.col(j) -= mean_x.col(0);  }  H /= 1.0/sqrt(sample_num-1);    arma::mat Q, R;  arma::qr_econ(Q, R, H);    arma::mat U, V;  arma::vec d;  arma::svd_econ(U, d, V, R.t());   // cout << "d" << endl << d << endl;   // arma::rowvec vec_eigen = d.head(component_num).t(); // cout << "vec_eigen" << endl << vec_eigen << endl;    arma::matV_h(V.n_rows, component_num);  if(component_num == 1) {  V_h = V.col(0);  }else {  V_h = V.cols(0, component_num-1);  }    arma::matmat_pca = Q * V_h;    //Calculate eigenfaces `mat_eigen_vec`.    arma::matmat_eigen = mat_pca.t() * mat_sample; // cout << "mat_eigen" << endl << mat_eigen << endl;  arma::matmat_eigen_vec(component_num, class_num, arma::fill::zeros);  vector<arma::mat> mat_cov_list;    for(int class_idx = 0; class_idx < class_num; class_idx++) {    arma::veceigen_sum(component_num, arma::fill::zeros);  for(int sample_idx = 0; sample_idx < sample_num; sample_idx++) {  eigen_sum += mat_eigen.col(class_idx*sample_num+sample_idx);  }  eigen_sum /= (double)sample_num;  mat_eigen_vec.col(class_idx) = eigen_sum;    mat_cov_list.push_back(arma::cov((mat_eigen.cols(class_idx*sample_num, class_idx*sample_num+sample_num-1)).t()));   // cout << mat_cov_list[class_idx] << endl;    }   // cout << "mat_eigen_vec" << endl << mat_eigen_vec << endl;   /* cout << "dis within class" << endl; for(int class_idx = 0; class_idx < class_num; class_idx++) { for(int sample_idx = 0; sample_idx < sample_num; sample_idx++) { double dis = calcuDistance(mat_eigen.col(class_idx*sample_num+sample_idx), mat_eigen_vec.col(class_idx), mat_cov_list[class_idx], distance_type::MAHALANOBIS); cout << dis << " "; } cout << endl; }   cout << "dis between classes" << endl; for(int class_idx = 0; class_idx < class_num; class_idx++) { for(int sample_idx = 0; sample_idx < class_num; sample_idx++) { double dis = calcuDistance(mat_eigen.col(sample_idx*sample_num), mat_eigen_vec.col(class_idx), mat_cov_list[class_idx], distance_type::MAHALANOBIS); cout << dis << " "; } cout << endl; } */    //Classify new sample.    int correct_count = 0;    double max_dis = 0.0;    for(int class_idx = 0; class_idx < class_num; class_idx++){  for(int sample_idx = 0; sample_idx < sample_num; sample_idx++) {  arma::matmat_new_sample(img_rows*img_cols, 1);    string filename = orl_path + "//s" + to_string(class_idx+1) + "//" + to_string(sample_idx+1) + ".pgm";  cv::Matimg_new_frame = cv::imread(filename, CV_LOAD_IMAGE_GRAYSCALE);  cv::Matimg_new_sample;  cv::resize(img_new_frame, img_new_sample, sample_size);    for(int i = 0; i < img_rows; i++) {  uchar* pframe = img_new_sample.ptr<uchar>(i);  for(int j = 0; j < img_cols; j++) {  mat_new_sample(i*img_cols+j, 0) = (double)pframe[j]/255.0;  }  }    arma::matmat_new_eigen = mat_pca.t() * mat_new_sample;    vector<pair<int, double>> dis_list;  for(int new_class_idx = 0; new_class_idx < class_num; new_class_idx++) {  double dis = calcuDistance(mat_new_eigen.col(0), mat_eigen_vec.col(new_class_idx), mat_cov_list[new_class_idx], distance_type::MAHALANOBIS);  dis_list.push_back(make_pair(new_class_idx, dis));  }  sort(dis_list.begin(), dis_list.end(), compDistance);    if(dis_list[0].first == class_idx && dis_list[0].second <= distance_criterion[distance_type::MAHALANOBIS]) {  correct_count++;  }    if(dis_list.back().second > max_dis) {  max_dis = dis_list.back().second;  }  }  }    cout << "Maximum distance: " << max_dis << endl;    double correct_ratio = (double)correct_count / (class_num * sample_num);  cout << "Correctness ratio: " << correct_ratio * 100.0 << "%" << endl;    cin.get();    return 0; }   bool compDistance(pair<int, double> a, pair<int, double> b) {  return (a.second < b.second); }   double calcuDistance(const arma::vecvec1, const arma::vecvec2, distance_typedis_type) {    if(dis_type == ECULIDEAN) {  return arma::norm(vec1-vec2, 2);  }else if(dis_type == MANHATTAN) {  return arma::norm(vec1-vec2, 1);  }else if(dis_type == MAHALANOBIS) {  arma::mattmp = (vec1-vec2).t() * (vec1 - vec2);  return sqrt(tmp(0,0));  }    return -1.0; }   double calcuDistance(const arma::vecvec1, const arma::vecvec2, const arma::matcov2, distance_typedis_type) {    if(dis_type == ECULIDEAN) {  return arma::norm(vec1-vec2, 2);  }else if(dis_type == MANHATTAN) {  return arma::norm(vec1-vec2, 1);  }else if(dis_type == MAHALANOBIS) {  arma::mattmp = (vec1-vec2).t() * cov2.i() * (vec1 - vec2);  return sqrt(tmp(0,0));  }    return -1.0; } 

分类测试

测试样本采用The AT&T Database of Faces,原称The ORL Database of Faces,包含取自40个人的样本,每人10幅,共400幅图像,图像尺寸92*112。

从QR分解到PCA,再到人脸识别 样本库的链接为 http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html 。

1. 欧式距离降维及分类效果:

从QR分解到PCA,再到人脸识别 即使将h设为最大的400(样本数),其分类正确率也只能达到99%。

2. 曼哈顿距离降维及分类效果:

从QR分解到PCA,再到人脸识别 在h为128时,分类正确率可以达到100%,降维能力略好于欧式距离。

3. 马氏距离降维及分类效果:

从QR分解到PCA,再到人脸识别 在h仅仅为7的时候,其分类正确率就已经达到100%了。采用马氏距离的PCA方法可以将人脸数据的维度从10000左右降到7,降维效果非常优秀。在h超过9时,分类过程中计算的最大马氏距离超出了双精度浮点数double的上限。

参考文献

[1] Sharma A, Paliwal K K, Imoto S, et al. Principal component analysis using QR decomposition[J]. International Journal of Machine Learning and Cybernetics, 2013, 4(6): 679-683.

[2] Raj D. A Realtime Face Recognition system using PCA and various Distance Classifiers[J]. 2011.

[3] Turk M, Pentland A. Eigenfaces for recognition[J]. Journal of Cognitive Neuroscience, 1991, 3(1): 71-86.

原文  http://johnhany.net/2016/05/from-qr-decomposition-to-pca-to-face-recognition/
正文到此结束
Loading...