opencv实现PCA人脸降维

来源:互联网 发布:奔驰c级coupe 知乎 编辑:程序博客网 时间:2024/06/01 19:11

 文章转载自:http://www.cnblogs.com/tornadomeet/archive/2012/09/06/2673104.html,代码做了一点点的修改和注释,亲测可行。

前言:

  PCA是大家经常用来减少数据集的维数,同时保留数据集中对方差贡献最大的特征来达到简化数据集的目的。本文通过使用PCA来提取人脸中的特征脸这个例子,来熟悉下在oepncv中怎样使用PCA这个类。

  开发环境:ubuntu12.04+Qt4.8.2+QtCreator2.5.1+opencv2.4.2

 

  PCA数学理论:

  关于PCA的理论,资料很多,公式也一大把,本人功底有限,理论方面这里就不列出了。下面主要从应用的角度大概来讲讲具体怎么实现数据集的降维。

  1. 把原始数据中每个样本用一个向量表示,然后把所有样本组合起来构成一个矩阵。当然了,为了避免样本的单位的影响,样本集需要标准化。

  2. 求该矩阵的协防差矩阵(关于协方差的介绍可以参考我的博文:一些知识点的初步理解_4(协方差矩阵,ing...))。

  3. 求步骤2中得到的协方差矩阵的特征值和特征向量。

  4. 将求出的特征向量按照特征值的大小进行组合形成一个映射矩阵,并根据指定的PCA保留的特征个数取出映射矩阵的前n行或者前n列作为最终的映射矩阵。

  5. 用步骤4的映射矩阵对原始数据进行映射,达到数据降维的目的。

 

  实验说明:

  在本次实验实现的过程中,需要用到opencv的这些函数,下面简单介绍下这些函数。

  Mat Mat::reshape(int cn, int rows=0) const

  该函数是改变Mat的尺寸,即保持尺寸大小=行数*列数*通道数 不变。其中第一个参数为变换后Mat的通道数,如果为0,代表变换前后通道数不变。第二个参数为变换后Mat的行数,如果为0也是代表变换前后通道数不变。但是该函数本身不复制数据(这点不是很理解,调用一个Matreshape,如果我们不把调用后的Mat做为返回值去用,难道此时调用前的Mat一点变化都没有?)。

 

  void Mat::convertTo(OutputArray m, int rtype, double alpha=1, double beta=0 ) const

  该函数其实是对原Mat的每一个值做一个线性变换。参数1为目的矩阵,参数2为目d矩阵的类型,参数34变换的系数,看完下面的公式就明白了:

  

 

  PCA::PCA(InputArray data, InputArray mean, int flags, int maxComponents=0)

  该构造函数的第一个参数为要进行PCA变换的输入Mat;参数2为该Mat的均值向量;参数3为输入矩阵数据的存储方式,如果其值为CV_PCA_DATA_AS_ROW则说明输入Mat的每一行代表一个样本,同理当其值为CV_PCA_DATA_AS_COL时,代表输入矩阵的每一列为一个样本;最后一个参数为该PCA计算时保留的最大主成分的个数。如果是缺省值,则表示所有的成分都保留。

 

  Mat PCA::project(InputArray vec) const

  该函数的作用是将输入数据vec(该数据是用来提取PCA特征的原始数据)投影到PCA主成分空间中去,返回每一个样本主成分特征组成的矩阵。因为经过PCA处理后,原始数据的维数降低了,因此原始数据集中的每一个样本的维数都变了,由改变后的样本集就组成了本函数的返回值。

 

  Mat PCA::backProject(InputArray vec) const

  一般调用backProject()函数前需调用project()函数,因为backProject()函数的参数vec为经过PCA投影降维过后的矩阵。 因此backProject()函数的作用就是用vec来重构原始数据集(关于该函数的本质数学实现暂时还不是很了解)。

  另外PCA类中还有几个成员变量,mean,eigenvectors, eigenvalues等分别对应着原始数据的均值,协方差矩阵的特征值和特征向量。

下面是主要代码

pcaface.cpp

#include "pcaface.h"PCAface::PCAface(QWidget *parent): QMainWindow(parent){ui.setupUi(this);src_face1 = imread("./images/1.pgm", 0);//下面的代码为设置图片显示区域自适应图片的大小ui.face1Browser->setFixedHeight(src_face1.rows + 1);ui.face1Browser->setFixedWidth(src_face1.cols + 1);ui.face2Browser->setFixedHeight(src_face1.rows + 1);ui.face2Browser->setFixedWidth(src_face1.cols + 1);ui.face3Browser->setFixedHeight(src_face1.rows + 1);ui.face3Browser->setFixedWidth(src_face1.cols + 1);ui.face4Browser->setFixedHeight(src_face1.rows + 1);ui.face4Browser->setFixedWidth(src_face1.cols + 1);ui.face5Browser->setFixedHeight(src_face1.rows + 1);ui.face5Browser->setFixedWidth(src_face1.cols + 1);ui.face6Browser->setFixedHeight(src_face1.rows + 1);ui.face6Browser->setFixedWidth(src_face1.cols + 1);ui.face7Browser->setFixedHeight(src_face1.rows + 1);ui.face7Browser->setFixedWidth(src_face1.cols + 1);ui.face8Browser->setFixedHeight(src_face1.rows + 1);ui.face8Browser->setFixedWidth(src_face1.cols + 1);ui.face9Browser->setFixedHeight(src_face1.rows + 1);ui.face9Browser->setFixedWidth(src_face1.cols + 1);for (int i = 1; i <= 15; i++){stringstream ss;string num;ss << i;//将整数i读入字符串流ss >> num;//将字符串流中的数据传入num,这2句代码即把数字转换成字符string image_name = ("./images/" + num + ".pgm");//需要读取的图片全名//imread(const string& filename, intflags=1)第二个参数默认为1表示读取彩色图//下面imread第二个参数是0表示读入的是灰度图src.push_back(imread(image_name, 0));}total = src[0].rows*src[0].cols;//total是像素个数}PCAface::~PCAface(){}void PCAface::changeEvent(QEvent *e){QMainWindow::changeEvent(e);switch (e->type()) {case QEvent::LanguageChange:ui.retranslateUi(this);break;default:break;}}//将Mat内的内容归一化到0~255,归一化后的类型为单通道整型,归一化结果放到srcnorm里面Mat PCAface::normalize(const Mat& src) {Mat srcnorm;cv::normalize(src, srcnorm, 0, 255, NORM_MINMAX, CV_8UC1);return srcnorm;}void PCAface::on_startButton_clicked(){//先显示3张原图ui.face1Browser->append("<img src=./images/1.pgm>");ui.face2Browser->append("<img src=./images/7.pgm>");ui.face3Browser->append("<img src=./images/14.pgm>");//mat数组每一列表示一张图像//mat数组用来存放读取进来的所有图像的数据Mat mat(total, src.size(), CV_32FC1);for (int i = 0; i < src.size(); i++){Mat col_tmp = mat.col(i);//这样不新申请空间,对col_tmp操作就是对mat操作,下面就是把图像拉成向量存进mat里面    src[i].reshape(1, total).convertTo(col_tmp, CV_32FC1, 1 / 255.);}int number_principal_compent = 12;//保留最大的主成分数//构造pca数据结构//pac的第一个参数为要进行PCA变换的输入MatPCA pca(mat, Mat(), CV_PCA_DATA_AS_COL, number_principal_compent);//pca.eigenvectors中的每一行代表输入数据协方差矩阵一个特征向量,且是按照该协方差矩阵的特征值进行排序的pca_face1 = normalize(pca.eigenvectors.row(0)).reshape(1, src[0].rows);//第一个主成分脸imwrite("./result/pca_face1.jpg", pca_face1);//显示主成分特征脸1ui.face7Browser->append("<img src=./result/pca_face1.jpg>");pca_face2 = normalize(pca.eigenvectors.row(1)).reshape(1, src[0].rows);//第二个主成分脸imwrite("./result/pca_face2.jpg", pca_face2);//显示主成分特征脸2ui.face8Browser->append("<img src=./result/pca_face2.jpg>");pca_face3 = normalize(pca.eigenvectors.row(2)).reshape(1, src[0].rows);//第三个主成分脸imwrite("./result/pca_face3.jpg", pca_face3);//显示主成分特征脸3ui.face9Browser->append("<img src=./result/pca_face3.jpg>");//将原始数据通过PCA方向投影,即通过特征向量的前面几个作用后的数据,因此这里的dst的尺寸变小了dst = pca.project(mat);//通过方向投影重构原始人脸图像(其本质暂时还没完全弄明白)project_face1 = normalize(pca.backProject(dst).col(0)).reshape(1, src[0].rows);imwrite("./result/project_face1.jpg", project_face1);ui.face4Browser->append("<img src=./result/project_face1.jpg>");project_face2 = normalize(pca.backProject(dst).col(6)).reshape(1, src[0].rows);imwrite("./result/project_face2.jpg", project_face2);ui.face5Browser->append("<img src=./result/project_face2.jpg>");project_face3 = normalize(pca.backProject(dst).col(13)).reshape(1, src[0].rows);imwrite("./result/project_face3.jpg", project_face3);ui.face6Browser->append("<img src=./result/project_face3.jpg>");}void PCAface::on_closeButton_clicked(){close();}
pcaface.h

#ifndef PCAFACE_H#define PCAFACE_H#include <cxcore.h>#include <highgui.h>#include <cv.h>#include <QtWidgets/QMainWindow>#include <QStringList>#include <QStringListModel>#include <QSortFilterProxyModel>#include <QModelIndex>#include <QMessageBox>#include <QDebug>#include <QPixmap>#include <QImageReader>#include <QImage>#include <QTextStream>#include <QPixmapCache>#include <QProgressDialog>#include "ui_pcaface.h"#include <vector>#include<opencv2/core/core.hpp>#include<opencv2/imgproc/imgproc.hpp>#include<opencv2/highgui/highgui.hpp>using namespace std;using namespace cv;class PCAface : public QMainWindow{Q_OBJECTpublic:PCAface(QWidget *parent = 0);~PCAface();Mat normalize(const Mat& src);protected:void changeEvent(QEvent *e);private slots:void on_startButton_clicked();void on_closeButton_clicked();private:Ui::PCAfaceClass ui;Mat src_face1, src_face2, src_face3;Mat project_face1, project_face2, project_face3;Mat dst;Mat pca_face1, pca_face2, pca_face3;vector<Mat> src;int total;};#endif // PCAFACE_H