前言:

  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等分别对应着原始数据的均值,协方差矩阵的特征值和特征向量。

  实验结果:

  本次实验是用4个人人脸图像,其中每个人分别有5张,共计20张人脸图片。用这些图片组成原始数据集来提取他们的PCA主特征脸。该20张图片如下所示:

  

  当运行软件后,单击start按钮,该程序的结果显示如下:

  

  其中第一行的3张人脸分别为20张原图中的3张,这里取的是3个不同人的。

  第二行中显示的3张人脸分别为第一行中人脸经过PCA投影后,又方向投影过来的人脸图像,仔细观察可以看到第二行的人脸图像整体比第一行的亮度上要亮些,且细节上也有所不同。

  3行的人脸图为取的原始数据协方差矩阵特征向量的最前面3个,因此这3个人脸为最具代表人脸特征的3PCA人脸特征。

  实验主要部分代码即注释(附录有实验工程code下载链接):

pcaface.h:

#ifndef PCAFACE_H
#define PCAFACE_H
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp> using namespace cv; #include <QDialog> namespace Ui {
class PCAFace;
} class PCAFace : public QDialog
{
Q_OBJECT public:
explicit 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::PCAFace *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

pcaface.cpp:

#include "pcaface.h"
#include "ui_pcaface.h"
#include <QString>
#include <iostream>
#include <stdio.h> using namespace std; PCAFace::PCAFace(QWidget *parent) :
QDialog(parent),
ui(new Ui::PCAFace)
{
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");//需要读取的图片全名
src.push_back(imread(image_name, 0));
}
total= src[0].rows*src[0].cols;
} PCAFace::~PCAFace()
{
delete ui;
} void PCAFace::changeEvent(QEvent *e)
{
QDialog::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
break;
default:
break;
}
} //将Mat内的内容归一化到0~255,归一化后的类型为但通道整型
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的每一列对应1张图片,该实现在下面的for函数中
Mat mat(total, src.size(), CV_32FC1);
for(int i = 0; i < src.size(); i++)
{
Mat col_tmp = mat.col(i);
src[i].reshape(1, total).col(0).convertTo(col_tmp, CV_32FC1, 1/255.);
}
int number_principal_compent = 12;//保留最大的主成分数
//构造pca数据结构
PCA 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);//显示主成分特征脸1
ui->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);//显示主成分特征脸2
ui->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);//显示主成分特征脸3
ui->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();
}

main.cpp:

#include <QApplication>
#include "pcaface.h" int main(int argc, char *argv[])
{
QApplication a(argc, argv);
PCAFace w;
w.show(); return a.exec();
}

  实验总结:

  通过本次实验,对Opencv中的PCA这个类的使用有了一定的了解。

【转】PCA算法学习_1(OpenCV中PCA实现人脸降维)的更多相关文章

  1. PCA算法学习(Matlab实现)

    PCA(主成分分析)算法,主要用于数据降维,保留了数据集中对方差贡献最大的若干个特征来达到简化数据集的目的. 实现数据降维的步骤: 1.将原始数据中的每一个样本用向量表示,把所有样本组合起来构成一个矩 ...

  2. OpenCV学习(22) opencv中使用kmeans算法

    kmeans算法的原理参考:http://www.cnblogs.com/mikewolf2002/p/3368118.html 下面学习一下opencv中kmeans函数的使用.      首先我们 ...

  3. OpenCV学习(35) OpenCV中的PCA算法

    PCA算法的基本原理可以参考:http://www.cnblogs.com/mikewolf2002/p/3429711.html     对一副宽p.高q的二维灰度图,要完整表示该图像,需要m = ...

  4. OpenCV中PCA实现人脸降维

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

  5. opencv2.4.13+python2.7学习笔记--OpenCV中的图像处理--图像轮廓

    阅读对象:无要求. 1.代码 ''' OpenCV中的轮廓 轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度.为了更加准确,要使用二值化图像.在寻找轮廓之前,要进行阈值化 ...

  6. opencv2.4.13+python2.7学习笔记--opencv中的Gui特性--图片:读图像,显示图像,保存图像

    阅读对象:可以配置opencv+Python环境的任何人,毕竟写这篇文章的人就是小白. 1.环境说明 1.1opencv版本: 1.2Python版本: 1.3系统:win7 注: (1)opencv ...

  7. 大约PCA算法学习总结

    文章来源:http://blog.csdn.net/xizhibei ============================= PCA,也就是说,PrincipalComponents Analys ...

  8. 数据挖掘算法学习(四)PCA算法

    转载请附上链接http://blog.csdn.net/iemyxie/article/details/38236647 算法简单介绍 主成分分析(PrincipalComponentAnalysis ...

  9. OpenCV学习(39) OpenCV中的LBP图像

    本章我们学习LBP图像的原理和使用,因为接下来教程我们要使用LBP图像的直方图来进行脸部识别. 参考资料: http://docs.opencv.org/modules/contrib/doc/fac ...

随机推荐

  1. shell编程 条件判断式----利用 if .... then ----多重

    条件判断式----利用 if .... then ----多重 在同一个数据的判断中,如果该数据需要进行多种不同的判断时,应该怎么作?举例来说,上面的 sh06.sh 脚本中,我们只要进行一次 $yn ...

  2. IntelliJ IDEA 打包Maven 构建的 Java 项目

    方法 2,一键生成方便到哭 打开maven项目路径     一键生成     3.生成jar 目标文件在 path/target/xx.jar下面 方法 1 选中Java项目工程名称,在菜单中选择 F ...

  3. CC14:集合栈

    题目 请实现一种数据结构SetOfStacks,由多个栈组成,其中每个栈的大小为size,当前一个栈填满时,新建一个栈.该数据结构应支持与普通栈相同的push和pop操作. 给定一个操作序列int[] ...

  4. [Java]Vector源码分析

    第1部分 Vector介绍 Vector简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长.继承于AbstractList,实现了List, RandomAccess, Clone ...

  5. build spark

    Error : Failed to find Spark jars directory (/home/pl62716/spark-2.2.0-SNAPSHOT/assembly/target/scal ...

  6. 获取jar包当前的路径

    转自:http://kinganpo.iteye.com/blog/876243 import java.io.File; /** * 获取打包后jar的路径信息 * @author Administ ...

  7. X Samara Regional Intercollegiate Programming Contest DIV2

    http://codeforces.com/gym/101341 其实我觉得这份题很不错的,虽然是div2,但是感觉对我挺有帮助(我比较垃圾0.0),还没补完(做的时候一直蒙逼,要补很多题)先写一点点 ...

  8. Selenium~自动化测试来了

    这段时候研究了一下Selenium,它是一个自动化测试工具,在asp.net平台可以通过nuget去安装,同时支持多种开发语言,之前支持java,而现在也支持C#了,所以我们通过nuget就可以安装了 ...

  9. Main函数中的参数argc,argv的使用简单解析

    本篇文章是对Main函数中的参数argc,argv的使用进行了简单的分析介绍,需要的朋友参考下: C/C++语言中的main函数,经常带有参数argc,argv,如下:  int main(int a ...

  10. spring中自动装配bean

    首先用@Component注解类: package soundsystem: import org.springframework.stereotype.Component; @Component p ...