OpenCV+Qt:基於PCA主成分分析的Face Service常式,opencvpca
在模式識別領域中,PCA是一種常用的資料集降維手段,在此基礎上,保留資料集中對方差貢獻最大的特徵從而進行模式分類。OpenCV中提供PCA的類,因此可以方便地使用PCA來進行Face Service研究。在學習了網上的相關實現和代碼,在以下開發平台跑通了代碼: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_OBJECTpublic: 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