在模式识别领域中,PCA是一种常用的数据集降维手段,在此基础上,保留数据集中对方差贡献最大的特征从而进行模式分类。OpenCV中提供PCA的类,因此可以方便地使用PCA来进行人脸识别研究。在学习了网上的相关实现和代码,在以下开发平台跑通了代码:win8.1+OpenCV2.4.9+Qt5.3.2。
一、基本步骤
关于PCA的一些理论,可参照:http://blog.csdn.net/liyuefeilong/article/details/45126255 以下是实现PCA的基本思路:
1.把原始数据中每个样本用一个向量表示,然后把所有样本组合起来构成一个矩阵。这里为了避免样本单位对后续处理的影响,样本集需要标准化。
2.求样本的散布矩阵。事实上,散布矩阵是样本协方差矩阵的(n-1)倍,而协方差矩阵则表示不同随机变量之间的相互关系,在图像中则等价为求两个像素之间的关系。这里散布矩阵是实对称矩阵。
3.对第二步中得到的散布矩阵求相应的特征值和特征向量。
4.所谓主成分分析,即需要得到具有最大特征值的特征向量,所以我们需要将特征向量按照特征值由大到小排序并形成一个映射矩阵,并根据指定的PCA保留的特征个数取出映射矩阵的前n行或者前n列作为最终的映射矩阵。
5.用第四步的映射矩阵对训练样本数据进行映射,达到数据降维的目的。假设原始的图像数据是m*n的矩阵,只包含主成分的特征向量构成一个n*p的矩阵,其中每一列都是一个特征向量。将两个矩阵相乘,即可获得降维之后的图像矩阵m*p,这个矩阵远小于原始的图像数据。
6.同步骤五,读取所有测试集图像,并对其进行降维操作。如果测试集有M幅图像,则降维后的矩阵为M*p。
7.最后,对测试集进行模式识别。
在本次实验实现的过程中,需要用到opencv的这些函数,下面简单介绍下这些函数。
二、OpenCV中需要用到的几个函数
PCA::PCA(InputArray data, // 输入一个矩阵
InputArray mean, // 输出一个句子
int flags, // 输入矩阵数据的存储方式,有以下两种参数设定
// CV_PCA_DATA_AS_ROW:代表输入矩阵的每一行表示一个样本
// CV_PCA_DATA_AS_COL:代表输入矩阵的每一列表示一个样本
int maxComponents=0) // 计算PCA时保留的最大主成分的个数
// 该函数将输入数据投影到PCA主成分空间中去
// 返回每一个样本主成分特征组成的矩阵
cv::Mat PCA::project(InputArray vec) const
// 调用backProject函数前一般已经调用过project()函数
// 其作用可理解为project()函数的逆运算
// 函数的作用就是用vec来重构原始数据集(原理有待进一步了解)
cv::Mat PCA::backProject(InputArray vec) const
另外PCA类中还有几个重要的成员变量:
mean // 原始数据的均值
eigenvectors // 散布矩阵(协方差矩阵)的特征值
eigenvalues // 散布矩阵(协方差矩阵)的特征向量
三、相关代码
根据网上提供的代码,修改成可以在开发平台上使用的版本:
#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
#include "pcaface.h"
#include "ui_pcaface.h"
#include <QString>
#include <iostream>
#include <stdio.h>
#include <QDir>
#include <QDebug>
using namespace std;
QDir dir;
QString runPath = dir.currentPath();
PCAFace::PCAFace(QWidget *parent) :
QDialog(parent),
ui(new Ui::PCAFace)
{
ui->setupUi(this);
src_face1 = imread("C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/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 str;
string num;
str<<i;// 将整数i读入字符串流
str>>num;// 将字符串流中的数据传入num,这2句代码即把数字转换成字符
string image_name = ("C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/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,归一化后的类型为8位无符号整型单通道
Mat PCAFace::normalize(const Mat& src)
{
Mat norm_src;
cv::normalize(src, norm_src, 0, 255, NORM_MINMAX, CV_8UC1);
return norm_src;
}
void PCAFace::on_startButton_clicked()
{
//先显示3张原图
ui->face1Browser->append("<img src=C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/images/5.pgm>");
ui->face2Browser->append("<img src=C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/images/7.pgm>");
ui->face3Browser->append("<img src=C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/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("C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/pca_face1.jpg", pca_face1);//显示主成分特征脸1
ui->face7Browser->append("<img src=C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/pca_face1.jpg>");
pca_face2 = normalize(pca.eigenvectors.row(1)).reshape(1, src[0].rows);//第二个主成分脸
imwrite("C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/pca_face2.jpg", pca_face2);//显示主成分特征脸2
ui->face8Browser->append("<img src=C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/pca_face2.jpg>");
pca_face3 = normalize(pca.eigenvectors.row(2)).reshape(1, src[0].rows);//第三个主成分脸
imwrite("C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/pca_face3.jpg", pca_face3);//显示主成分特征脸3
ui->face9Browser->append("<img src=C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/pca_face3.jpg>");
//将原始数据通过PCA方向投影,即通过特征向量的前面几个作用后的数据,因此这里的dst的尺寸变小了
dst = pca.project(mat);
//通过方向投影重构原始人脸图像
project_face1 = normalize(pca.backProject(dst).col(0)).reshape(1, src[0].rows);
imwrite("C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/project_face1.jpg", project_face1);
ui->face4Browser->append("<img src=C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/project_face1.jpg>");
project_face2 = normalize(pca.backProject(dst).col(1)).reshape(1, src[0].rows);
imwrite("C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/project_face2.jpg", project_face2);
ui->face5Browser->append("<img src=C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/project_face2.jpg>");
project_face3 = normalize(pca.backProject(dst).col(2)).reshape(1, src[0].rows);
imwrite("C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/project_face3.jpg", project_face3);
ui->face6Browser->append("<img src=C:/Users/peng__000/Desktop/PR.proj05/OpenCV4PCA/PCA_Face/result/project_face3.jpg>");
}
void PCAFace::on_closeButton_clicked()
{
close();
}
#include <QApplication>
#include "pcaface.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
PCAFace w;
w.show();
return a.exec();
}
其中第一行的3张人脸分别为ORL人脸库其中的3张,分别取3个不同的人。
第二行中显示的3张人脸分别为第一行中人脸经过PCA投影函数Project后,又调用反向投影函数backProject变换回来的人脸图像。
第三行的人脸图为取的原始数据计算散布矩阵的特征向量的最前面3个,使用这3个向量所得到的输出最能代表人脸特征。
最后感谢代码原文介绍:http://www.cnblogs.com/tornadomeet/archive/2012/09/06/2673104.html
原文地址:http://blog.csdn.net/liyuefeilong/article/details/45419377