人脸识别算法一:特征脸方法(Eigenface)
一、特征臉
? ? ? ? 特征臉EigenFace從思想上其實挺簡單。就相當于把人臉從像素空間變換到另一個空間,在另一個空間中做相似性的計算。這么說,其實圖像識別的基本思想都是一樣的,首先選擇一個合適的子空間,將所有的圖像變換到這個子空間上,然后再在這個子空間上衡量相似性或者進行分類學習。
? ? ? ?通過變換到另一個空間,同一個類別的圖像會聚到一起,不同類別的圖像會距離比較遠,因為圖像各種因素的影響,包括光照、視角、背景和形狀等等不同,會造成同一個目標的圖像都存在很大的視覺信息上的不同,在原像素空間中不同類別的圖像在分布上很難用個簡單的線或者面把他們切分開,但是如果變換到另一個空間,就可以很好的把他們分開了。
? ? ?萬千世界,同一類事物都存在共性,也存在個性,只要在我們把同一類目標的共性找出來就好了,而且這個共性最好和我們要區分的類是不一樣的。那么要變換到什么空間,才具備上述這種良好類內相似、類間區分的效果?計算機視覺領域發展了幾十年,就為了這一個問題傾注了無數研究者的智慧與心血。當然了,也誕生和孕育了很多經典和有效的解答。(實際上就是特征提取)。從一開始的顏色特征(顏色直方圖)、紋理特征(Harr、LBP、HOG、SIFT等)、形狀特征等到視覺表達Bag of Words,再到特征學習Deep Learning,技術的發展總能帶給人希望,曙光也越來越清晰,但路還很遠,是不???????
? ? ? ?上面說到,特征臉EigenFace的思想是把人臉從像素空間變換到另一個空間,在另一個空間中做相似性的計算。EigenFace選擇的空間變換方法是PCA,也就是大名鼎鼎的主成分分析。它廣泛的被用于預處理中以消去樣本特征維度之間的相關性。當然了,這里不是說這個。
? ? ? ? PCA原理請參見 :PCA的數學原理
? ? ? ? EigenFace方法利用PCA得到人臉分布的主要成分,具體實現是對訓練集中所有人臉圖像的協方差矩陣進行特征值分解,得對對應的特征向量,這些特征向量就是“特征臉”。每個特征向量或者特征臉相當于捕捉或者描述人臉之間的一種變化或者特性。這就意味著每個人臉都可以表示為這些特征臉的線性組合。實際上,空間變換就等同于“基變換”,原始像素空間的基就是單位“基”,經過PCA后空間就是以每一個特征臉或者特征向量為基,在這個空間(或者坐標軸)下,每個人臉就是一個點,這個點的坐標就是這個人臉在每個特征基下的投影坐標。
??????下面就直接給出基于特征臉的人臉識別實現過程:
1)將訓練集的每一個人臉圖像都拉長一列,將他們組合在一起形成一個大矩陣A。假設每個人臉圖像是MxM大小,那么拉成一列后每個人臉樣本的維度就是N=MxM大小了。假設有20個人臉圖像,那么樣本矩陣A的維度就是20xN了。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
2)將所有的20個人臉在對應維度上加起來,然后求個平均,就得到了一個“平均臉”,這個“平均臉”矩陣E是一個1xN的向量,將這個向量reshape成MxM矩陣,你就可以把這個臉顯示出來的話,還挺帥的哦。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
3)將20個圖像都減去那個平均臉圖像(即矩陣A的每一行減去向量E),得到差值圖像的數據矩陣Φ,矩陣Φ的維度也是20xN。
4)計算協方差矩陣?。協方差矩陣C的維度是NxN,(注意:協方差衡量的是不同像素位置間的相關性,并不是圖像與圖像之間的相關性)再對其進行特征值分解。就可以得到想要的特征向量(特征臉)。
? ? ?這些特征向量如果還原成像素排列的話,其實還蠻像人臉的,所以稱之為特征臉(如下圖)。圖里有10個特征臉,有論文表明一般的應用40個特征臉已經足夠了。論文Eigenface for recognition里只用了7個特征臉來表明實驗。
? ? ? ? ? ? ? ? ? ? ? ? ?
5)將訓練集圖像和測試集的圖像都投影到這些特征向量上了,再對測試集的每個圖像找到訓練集中的最近鄰或者k近鄰等處理,進行分類即可。
? ? ? ?對于步驟4),涉及到求特征值分解。如果人臉的特征維度很大,例如256x256的人臉圖像,N就是65536了。那么協方差矩陣C的維度就是NxN=65536x65536。對這個大矩陣求解特征值分解是很費力的。那怎么辦呢?如果人臉的樣本不多,如上例中20個人臉,我們可以通過求解矩陣來獲得同樣的特征向量。可以看到這個只有20x20的大小。如果樣本人臉數遠遠小于人臉維度(圖像像素數)的話,那么這個力氣就省得很值了。那為什么求解矩陣的特征向量可以獲得的特征向量?萬眾矚目時刻,數學以完美舞姿登上舞臺。證明如下:(下面的推導中矩陣C和C'的含義與上文定義正好反了,博主懶得改了,請讀者注意一下,推導原理很簡單)
? ? ? ? ? ??
??????其中,ei是的第i個特征向量,vi是的第i個特征向量,由證明可以看到,?。
? ? ? 所以通過求解的特征值分解得到,再左乘Φ就得到的特征向量vi了。也就是我們想要的特征臉。
from:https://blog.csdn.net/feirose/article/details/39552887
from:https://blog.csdn.net/zouxy09/article/details/45276053
OpenCV官方代碼示例:
#include "opencv2/core.hpp" #include "opencv2/face.hpp" #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp"#include <iostream> #include <fstream> #include <sstream>using namespace cv; using namespace cv::face; using namespace std;static Mat norm_0_255(InputArray _src) {Mat src = _src.getMat();// Create and return normalized image: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; }static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {std::ifstream file(filename.c_str(), ifstream::in);if (!file) {string error_message = "No valid input file was given, please check the given filename.";CV_Error(Error::StsBadArg, error_message);}string line, path, classlabel;while (getline(file, line)) {stringstream liness(line);getline(liness, path, separator);getline(liness, classlabel);if(!path.empty() && !classlabel.empty()) {images.push_back(imread(path, 0));labels.push_back(atoi(classlabel.c_str()));}} }int main(int argc, const char *argv[]) {// Check for valid command line arguments, print usage// if no arguments were given.if (argc < 2) {cout << "usage: " << argv[0] << " <csv.ext> <output_folder> " << endl;exit(1);}string output_folder = ".";if (argc == 3) {output_folder = string(argv[2]);}// Get the path to your CSV.string fn_csv = string(argv[1]);// These vectors hold the images and corresponding labels.vector<Mat> images;vector<int> labels;// Read in the data. This can fail if no valid// input filename is given.try {read_csv(fn_csv, images, labels);} catch (cv::Exception& e) {cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;// nothing more we can doexit(1);}// Quit if there are not enough images for this demo.if(images.size() <= 1) {string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";CV_Error(Error::StsError, error_message);}// Get the height from the first image. We'll need this// later in code to reshape the images to their original// size:int height = images[0].rows;// The following lines simply get the last images from// your dataset and remove it from the vector. This is// done, so that the training data (which we learn the// cv::BasicFaceRecognizer on) and the test data we test// the model with, do not overlap.Mat testSample = images[images.size() - 1];int testLabel = labels[labels.size() - 1];images.pop_back();labels.pop_back();// The following lines create an Eigenfaces model for// face recognition and train it with the images and// labels read from the given CSV file.// This here is a full PCA, if you just want to keep// 10 principal components (read Eigenfaces), then call// the factory method like this://// EigenFaceRecognizer::create(10);//// If you want to create a FaceRecognizer with a// confidence threshold (e.g. 123.0), call it with://// EigenFaceRecognizer::create(10, 123.0);//// If you want to use _all_ Eigenfaces and have a threshold,// then call the method like this://// EigenFaceRecognizer::create(0, 123.0);//Ptr<EigenFaceRecognizer> model = EigenFaceRecognizer::create();model->train(images, labels);// The following line predicts the label of a given// test image:int predictedLabel = model->predict(testSample);//// To get the confidence of a prediction call the model with://// int predictedLabel = -1;// double confidence = 0.0;// model->predict(testSample, predictedLabel, confidence);//string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);cout << result_message << endl;// Here is how to get the eigenvalues of this Eigenfaces model:Mat eigenvalues = model->getEigenValues();// And we can do the same to display the Eigenvectors (read Eigenfaces):Mat W = model->getEigenVectors();// Get the sample mean from the training dataMat mean = model->getMean();// Display or save:if(argc == 2) {imshow("mean", norm_0_255(mean.reshape(1, images[0].rows)));} else {imwrite(format("%s/mean.png", output_folder.c_str()), norm_0_255(mean.reshape(1, images[0].rows)));}// Display or save the Eigenfaces:for (int i = 0; i < min(10, W.cols); i++) {string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i));cout << msg << endl;// get eigenvector #iMat ev = W.col(i).clone();// Reshape to original size & normalize to [0...255] for imshow.Mat grayscale = norm_0_255(ev.reshape(1, height));// Show the image & apply a Jet colormap for better sensing.Mat cgrayscale;applyColorMap(grayscale, cgrayscale, COLORMAP_JET);// Display or save:if(argc == 2) {imshow(format("eigenface_%d", i), cgrayscale);} else {imwrite(format("%s/eigenface_%d.png", output_folder.c_str(), i), norm_0_255(cgrayscale));}}// Display or save the image reconstruction at some predefined steps:for(int num_components = min(W.cols, 10); num_components < min(W.cols, 300); num_components+=15) {// slice the eigenvectors from the modelMat evs = Mat(W, Range::all(), Range(0, num_components));Mat projection = LDA::subspaceProject(evs, mean, images[0].reshape(1,1));Mat reconstruction = LDA::subspaceReconstruct(evs, mean, projection);// Normalize the result:reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows));// Display or save:if(argc == 2) {imshow(format("eigenface_reconstruction_%d", num_components), reconstruction);} else {imwrite(format("%s/eigenface_reconstruction_%d.png", output_folder.c_str(), num_components), reconstruction);}}// Display if we are not writing to an output folder:if(argc == 2) {waitKey(0);}return 0; }createEigenFaceRecognizer?
使用的是將特征向量(直接將圖像轉換成1*mn)進行PCA降維,然后使用距離函數,循環訓練的數據判斷屬于哪個類別
總結
以上是生活随笔為你收集整理的人脸识别算法一:特征脸方法(Eigenface)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对服务器系统盘扩容,服务器系统盘扩容
- 下一篇: Windows两台服务器之间实现文件共享