PCA学习啦

   在图形识别方面,主成分分析(Principal Comonents Analysis,PCA)算是比较快速而且又准确的方式之一,它可以对抗图形平移旋转的事件发生,并且藉由主要特征(主成分)投影过后的数据做数据的比对,在多个特征信息里面,取最主要的K,做为它的特征依据,在这边拿前面共变量矩阵的数据来做沿用,主成分分析使用的方法为计算共变量矩阵,在加上计算共变量矩阵的特征值及特征向量,将特征值以及所对应的特征向量排序之后,取前面主要K个特征向量当做主要特征,OpenCV也可以对高维度的向量进行主成分分析的计算。

PCAprincipal component analysis)翻译过来就是主分量分析,是一种流行的数据降维方法。通过数据降维可以实现数据的压缩,同时方便数据分析和提高算法的处理速度。PCA的原理就是通过正交变换,最大化样本协方差阵的对角元素,最小化非对角元素。具体的介绍可以参考Shlens, J., A tutorial on principal component analysis. Systems Neurobiology Laboratory, University of California at San Diego, 2005.这个介绍的很不错,从信号处理的角度出发分析了PCA。由于PCA的计算简单,效果显著所以得到很多人的青睐,但是PCA应用本身是基于一定假设的:1.线性。即特征的变换是线性变换,作用有限,目前也有非线性的特征变换kernel PCA2.处理的数据分布式服从指数族概率密度函数,即能通过均值和协方差来表征数据的分布,因为只有在这个情况下信噪比和协方差矩阵才能表示噪声和数据冗余。好在实际应用时,我们碰到的大多数数据是服从高斯分布或近似高斯分布的(这得益与概率论理的中心极限定理,这个概念经常会碰到,多理解理解有好处)。

PCA是一个无参数的数据分析技术,所以变换结果只受样本影响,结果不受经验影响,与用户独立。就是说PCA无法判断哪个特征比较重要,虽然这个特征的方差比较小。所有特征都平等对待,不能发挥出先验知识的作用。而且经过变换后特征的含义也无法与实际相联系,仅仅是个数据上的表示。毕竟天下没有免费的午餐,有得必有失,PCA是得到的便捷多。

在这里顺便提下PCAKL变换、奇异值分解的关系,其实这三个变换殊途同归,都实现相类似的功能。在信号处理中都得到广泛应用,真要说有什么区别就是中间的过程细微区别。奇异值分解个人感觉是矩阵论上的一种矩阵分界方法,并不和实际工程性的技术挂钩,只是很多问题如PCA可以通过奇异值分解的方法得到相同的结果,同时奇异值分解也反映了PCA的不同维度的内在转换关系。这里我有个疑惑:网上有说用奇异值分解方法解PCA会提高速度,但是根据书上说的,用奇异值分解的方法必须先计算协方差矩阵,求解特征值和特征向量,相当于奇异值分解开始了一半就已经解了PCA,何来的奇异值分解更快之说,是不是有别的奇异值分解方法呢?

代码好的要学习:

#include <cv.h>
#include <highgui.h>
#include <stdio.h>
#include <stdlib.h>

float Coordinates[20]={1.5,2.3,
3.0,1.7,
1.2,2.9,
2.1,2.2,
3.1,3.1,
1.3,2.7,
2.0,1.7,
1.0,2.0,
0.5,0.6,
1.0,0.9};

void PrintMatrix(CvMat *Matrix,int Rows,int Cols);

int main()
{
	CvMat *Vector1;
	CvMat *AvgVector;
	CvMat *EigenValue_Row;
	CvMat *EigenVector;

	Vector1=cvCreateMat(10,2,CV_32FC1);
	cvSetData(Vector1,Coordinates,Vector1->step);
	AvgVector=cvCreateMat(1,2,CV_32FC1);
	EigenValue_Row=cvCreateMat(2,1,CV_32FC1);
	EigenVector=cvCreateMat(2,2,CV_32FC1);

	cvCalcPCA(Vector1,AvgVector,EigenValue_Row,EigenVector,CV_PCA_DATA_AS_ROW);

	printf("Original Data:\n");
	PrintMatrix(Vector1,10,2);

	printf("==========\n");
	PrintMatrix(AvgVector,1,2);

	printf("\nEigne Value:\n");
	PrintMatrix(EigenValue_Row,2,1);

	printf("\nEigne Vector:\n");
	PrintMatrix(EigenVector,2,2);

	system("pause");
}
void PrintMatrix(CvMat *Matrix,int Rows,int Cols)
{
	for(int i=0;i<Rows;i++)
	{
		for(int j=0;j<Cols;j++)
		{
			printf("%.2f ",cvGet2D(Matrix,i,j).val[0]);
		}
		printf("\n");
	}
}

  opencv中使用步骤:

 在OPENCV中使用PCA非常简单,只要几条语句就可以了。

1、初始化数据

 //每一行表示一个样本

 CvMat* pData = cvCreateMat( 总的样本数, 每个样本的维数, CV_32FC1 );

 CvMat* pMean = cvCreateMat(1, 样本的维数, CV_32FC1);

 //pEigVals中的每个数表示一个特征值

 CvMat* pEigVals = cvCreateMat(1, min(总的样本数,样本的维数), CV_32FC1);

 //每一行表示一个特征向量

 CvMat* pEigVecs = cvCreateMat( min(总的样本数,样本的维数), 样本的维数, CV_32FC1);

2、PCA处理,计算出平均向量pMean,特征值pEigVals和特征向量pEigVecs

 cvCalcPCA( pData, pMean, pEigVals, pEigVecs, CV_PCA_DATA_AS_ROW );

3、选出前P个特征向量(主成份),然后投影,结果保存在pResult中,pResult中包含了P个系数

 CvMat* pResult = cvCreateMat( 总的样本数, PCA变换后的样本维数(即主成份的数目), CV_32FC1 );

 cvProjectPCA( pData, pMean, pEigVecs, pResult );

4、 重构,结果保存在pRecon中

 CvMat* pRecon = cvCreateMat( 总的样本数, 每个样本的维数, CV_32FC1 );

 cvBackProjectPCA( pResult, pMean, pEigVecs, pRecon );

5、重构误差的计算

 计算pRecon和pData的"差"就可以了.

使用时如果是想用PCA判断“是非”问题,则可以先用正样本计算主成分,判断时,对需要判断得数据进行投影,然后重构,计算重构出的数据与原数据的差异,如果差异在给定范围内,可以认为“是”。

如果相用PCA进行分类,例如对数字进行分类,则先用所有数据(0-9的所有样本)计算主成分,然后对每一类数据进行投影,计算投影的系数,可简单得求平均。即对每一类求出平均系数。分类时,将需要分类得数据进行投影,得到系数,与先前计算出得每一类得平均系数进行比较,可判为最接近得一类。当然这只是最简单得使用方法。

网友chinasun84在vC6.0下使用PCA的经验,正好适合我啊,mark!

最近在研究人脸识别,打算用PCA(主原分析)对图像数据进行降维后用神经网络训练的方法实现,在网上找了一下PCA的C++算法,发现很难用,而且速度奇慢,后来知道opencv上有实现PCA算法的函数,于是下载了一个2.0版,发现原来已经不支持VC6了,由于之前的代码都是在VC6下实现的,现在要移植也不太可能,无奈之下,用了1.0,但是却不知为什么只要维数大于数据量时就出错,真的要放弃?最后就孤注一掷,在VS2008下作一个封装2.0版下PCA类相关函数的库,再在VC6上链接。

  首先做成了静态库,居然链接不成功,看来VC6与VS2008的静态库还是不兼容,我想如果做动态库链接时应该还是会有这个问题啊,就想放弃了,不过经过高人指点,可以直接用LoadLibrary的方法调用动态库的函数啊。好,这个方法可以完全避开编译器和链接器,是最后的机会了。

 

  首先声明一个struct

  struct PCA

     {void* pca;}

     这个pca就是指向opencv2.0中PCA对象的指针。

 

  接着声明创建和释放该对象的函数。

 

  最后再声明其它PCA类方法的函数,如Project。

  extern "C" void Project(PCA* pca, float* vec, int veclen, float*& res, int reslen);

 

     这里记着最好要加上extern "C",好让编译器用C函数的方法对该函数进行编译,这样在dll库的函数名就会是Project,否则用C++编译的就会显示加上参数的一串很长的函数名,这时就需要VC6的一个工具Depends打开dll文件才能看到正确的函数名。

 

  在VS2008下写好了动态库,就要在VC6下调用了,这个网上就有很多资料了,大概步骤如下:

  // 声明这个函数指针

  typedef bool (__cdecl *ProjectPtr)(CPCA*, const data_t*, int, data_t*&, int);

 

  // 加载这个动态库

  HMODULE hModule = LoadLibrary("pcalib.dll");

 

  // 取得这个函数的地址

  ProjectPtr projectPtr = (ProjectPtr)GetProcAddress(hModule, "Project");

 

  // 创建对象

  PCA* pca = (createPCAPtr)(orl_input, TrainNum, InputUnits, MaxComponents);

 

     // 调用这个函数

   (projectPtr)(pca, orl_input[i], InputUnits, train_input[i], MaxComponents);

 

  该方法的确行,而且opencv中的PCA算法速度超快,opevcv果然强大,里面还有很多矩阵运算等算法,一点也不比Matlab差。

posted @ 2011-09-03 22:30  hailong  阅读(4743)  评论(0编辑  收藏  举报