Opencv+C++之人臉識別


最近一直在忙課程,老師讓我看看他的論文也沒放在心上。總算閑下來,看了他在人臉識別方面的相關論文,拿出一篇放在博客上跟大家共同分析下。在看以下內容前,首先要閱讀下徐勇老師的這篇論文

A Two-Phase Test Sample Sparse Representation Method for Use With Face Recognition;當前人臉識別方面最熱的方法就是稀疏表示方法(sparse represent),其主要思想是利用線性的或者非線性的表示方法將檢查樣本用訓練樣本表示出來,訓練樣本前的系數為代表比重,選取比重較大的訓練樣本所屬的類來標記測試樣本。這種方法在某些模式識別中效果較好,但是其原理並不明確,沒有很好的理論基礎,所以就方法的科學性而言相對欠缺。徐老師提出兩步法,第一步利用所有訓練樣本來標示出測試樣本,並提取M近鄰訓練樣本;第二步利用第一步中提取的M近鄰樣本表出測試樣本,選取代表比重大的訓練樣本所屬於的類來標記測試樣本。

關於該方法的理論,希望大家去下載論文閱讀,這里就不在多說,重點在於算法的實現上:算法中將實現分為兩步,第一步是用所有訓練樣本表示出測試樣本,可以用SVD來計算出系數陣,但在這之前要通過PCA或者LDA的方法給特征向量降維;

opencv中PCA有現成的方法,具體代碼如下(我的風格是先給出代碼,在代碼中介紹實現邏輯)

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

#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; } // 將一副圖像的數據轉換為Row Matrix中的一行;這樣做是為了跟opencv給出的PCA類的接口對應
//參數中最重要的就是第一個參數,表示的是訓練圖像樣本集合
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); } // 圖像數據的維度要是d,保證可以復制到返回矩陣中 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); // 將一副圖像映射到返回矩陣的一行中: 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; // 本例中使用的是ORL人臉庫,可以自行在網上下載
//將數據讀入到集合中
db.push_back(imread("s1/1.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s1/2.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s1/3.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s2/1.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s2/2.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s2/3.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s3/1.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s3/2.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s3/3.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s4/1.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s4/2.pgm", IMREAD_GRAYSCALE)); db.push_back(imread("s4/3.pgm", IMREAD_GRAYSCALE)); // 將訓練數據讀入到數據集合中,實現PCA類的接口 Mat data = asRowMatrix(db, CV_32FC1); // PCA中設定的主成分的維度,這里我們設置為10維度 int num_components = 10; // 構建一份PCA類 PCA pca(data, Mat(), CV_PCA_DATA_AS_ROW, num_components); // 復制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)); // Show the images: waitKey(0); // Success! return 0; }

 以上代碼中主要用到的opencv函數介紹:

Mat Mat::reshape(int cn, int rows=0) const

  opencv手冊上的解釋為:Changes the shape and/or the number of channels of a 2D matrix without copying the data.

參數cn:新的通道數;如果cn值為0表示變換前后通道數不變

參數rows:新的行數;如果rows值為0表示變換后矩陣的行數不變

該函數會為當前矩陣創建一個新的矩陣頭(指針),新的矩陣擁有不同的尺寸或者不同的通道數,其優點在於運算復雜度為O(1),不用復制矩陣數據.正是因為不用復制數據,所以在轉變過程中要保證原數據矩陣在數據上的連續性(這里的連續性是相對於原矩陣來說)為了更好的說明,舉個例子:

std::vector<Point3f> vec;//一個3D數據點的集合
...
Mat pointMat = Mat(vec). // 將這個三維向量集合轉換為矩陣,復制度為O(1);實際上形成的矩陣為一個N*1的3通道圖像陣
                             reshape(1). // 用reshape方法將其映射為N*3的1通道圖像陣,同樣運算復雜度為O(1)


boolMat::isContinuous() const


       opencv手冊上的解釋:Reports whether the matrix is continuous or not.

如果矩陣元素相對於原始矩陣在元素存儲上是連續的,行與行之間沒有間隙,那么就返回true否則就返回false;很顯然如果是1*1或者1*N矩陣,那么其返回值永遠是true。這個矩陣的連續性比較晦澀,我們看下該方法的可替代方法的實現

// 替代 Mat::isContinuous()的方法
bool myCheckMatContinuity(const Mat& m)
{
return m.rows == 1 || m.step == m.cols * m.elemSize();//如果矩陣只有一行就不會出現行與行之間的間斷;如果為多行,矩陣的步階應該是列數*元素尺寸
}

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等分別對應着原始數據的均值,協方差矩陣的特征值和特征向量。

獲得的結果如下:

 

avrageface

 

            

EignFace

OK,我們已經可以獲得ORL數據庫中每個人物的PCA特征臉,下一步也是我們下一節要研究的就是用訓練樣本表示出測試樣本,從而找到M近鄰樣本;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM