PCA算法的基本原理可以參考:http://www.cnblogs.com/mikewolf2002/p/3429711.html
對一副寬p、高q的二維灰度圖,要完整表示該圖像,需要m = p*q維的向量空間,比如100*100的灰度圖像,它的向量空間為100*100=10000。下圖是一個3*3的灰度圖和表示它的向量表示:
該向量為行向量,共9維,用變量表示就是[v0, v1, v2, v3, v4, v5, v6, v7, v8],其中v0...v8,的范圍都是0-255。
現在的問題是假如我們用1*10000向量,表示100*100的灰度圖,是否向量中的10000維對我們同樣重要?肯定不是這樣的,有些維的值可能對圖像更有用,有些維相對來說作用小些。為了節省存儲空間,我們需要對10000維的數據進行降維操作,這時就用到了PCA算法,該算法主要就是用來處理降維的,降維后會盡量保留更有意義的維數,它的思想就是對於高維的數據集來說,一部分維數表示大部分有意義的數據。
算法的基本原理:
假設
表示一個特征向量,其中
【注:xi可能也是一個列向量】
2.計算協方差矩陣 S
3.計算S的特征值
和對應的特征向量
,根據線性代數知識我們知道有公式:![]()
4. 對特征值按照大小進行遞減排序,特征向量的順序和特征值是一致的。假設我們只需要保留K個維數(K<n),則我們會選取特征值最大的前K個特征向量,用這K個特征向量,來表示圖像,這K個向量就是圖像K個主成分分量。
對於被觀測的向量
,它的K個主成分量可以通過下面公式計算得到:
下面我們在OpenCV中看一個計算PCA的例子:
1.首先讀入10副人臉圖像,這些圖像大小相等,是一個人的各種表情圖片。
2.把圖片轉為1*pq的一維形式,p是圖像寬,q是圖像高。這時我們的S矩陣就是10行,每行是pq維的向量。
3.然后我們在S上執行PCA算法,設置K=5,求得5個特征向量,這5個特征向量就是我們求得的特征臉,用這5個特征臉圖像,可以近似表示之前的十副圖像。
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/contrib/contrib.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
using namespace cv;
using namespace std;
//把圖像歸一化為0-255,便於顯示
Mat norm_0_255(const Mat& src)
{
Mat dst;
switch(src.channels())
{
case 1:
cv::normalize(src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
break;
case 3:
cv::normalize(src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
break;
default:
src.copyTo(dst);
break;
}
return dst;
}
//轉化給定的圖像為行矩陣
Mat asRowMatrix(const vector<Mat>& src, int rtype, double alpha = 1, double beta = 0)
{
//樣本數量
size_t n = src.size();
//如果沒有樣本,返回空矩陣
if(n == 0)
return Mat();
//樣本的維數
size_t d = src[0].total();
Mat data(n, d, rtype);
//拷貝數據
for(int i = 0; i < n; i++)
{
if(src[i].empty())
{
string error_message = format("Image number %d was empty, please check your input data.", i);
CV_Error(CV_StsBadArg, error_message);
}
// 確保數據能被reshape
if(src[i].total() != d)
{
string error_message = format("Wrong number of elements in matrix #%d! Expected %d was %d.", i, d, src[i].total());
CV_Error(CV_StsBadArg, error_message);
}
Mat xi = data.row(i);
//轉化為1行,n列的格式
if(src[i].isContinuous())
{
src[i].reshape(1, 1).convertTo(xi, rtype, alpha, beta);
} else {
src[i].clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta);
}
}
return data;
}
int main(int argc, const char *argv[])
{
vector<Mat> db;
string prefix = "../att_faces/";
db.push_back(imread(prefix + "s1/1.pgm", IMREAD_GRAYSCALE));
db.push_back(imread(prefix + "s1/2.pgm", IMREAD_GRAYSCALE));
db.push_back(imread(prefix + "s1/3.pgm", IMREAD_GRAYSCALE));
db.push_back(imread(prefix + "s1/4.pgm", IMREAD_GRAYSCALE));
db.push_back(imread(prefix + "s1/5.pgm", IMREAD_GRAYSCALE));
db.push_back(imread(prefix + "s1/6.pgm", IMREAD_GRAYSCALE));
db.push_back(imread(prefix + "s1/7.pgm", IMREAD_GRAYSCALE));
db.push_back(imread(prefix + "s1/8.pgm", IMREAD_GRAYSCALE));
db.push_back(imread(prefix + "s1/9.pgm", IMREAD_GRAYSCALE));
db.push_back(imread(prefix + "s1/10.pgm", IMREAD_GRAYSCALE));
// Build a matrix with the observations in row:
Mat data = asRowMatrix(db, CV_32FC1);
// PCA算法保持5主成分分量
int num_components = 5;
//執行pca算法
PCA pca(data, Mat(), CV_PCA_DATA_AS_ROW, num_components);
//copy pca算法結果
Mat mean = pca.mean.clone();
Mat eigenvalues = pca.eigenvalues.clone();
Mat eigenvectors = pca.eigenvectors.clone();
//均值臉
imshow("avg", norm_0_255(mean.reshape(1, db[0].rows)));
//五個特征臉
imshow("pc1", norm_0_255(pca.eigenvectors.row(0)).reshape(1, db[0].rows));
imshow("pc2", norm_0_255(pca.eigenvectors.row(1)).reshape(1, db[0].rows));
imshow("pc3", norm_0_255(pca.eigenvectors.row(2)).reshape(1, db[0].rows));
imshow("pc4", norm_0_255(pca.eigenvectors.row(3)).reshape(1, db[0].rows));
imshow("pc5", norm_0_255(pca.eigenvectors.row(4)).reshape(1, db[0].rows));
while(1)
waitKey(0);
// Success!
return 0;
}
我們輸入的10副圖像為:
得到的5副特征臉為:
均值臉為:
程序代碼:參照工程FirstOpenCV32



















