⑴ 怎樣使用OpenCV進行人臉識別
友情提示,要看懂代碼前,你得先知道OpenCV的安裝和配置,會用C++,用過一些OpenCV函數。基本的圖像處理和矩陣知識也是需要的。[gm:我是簫鳴的注釋]由於我僅僅是翻譯,對於六級才過的我,肯定有一些翻譯錯的或者不當的地方,所以請大家糾錯。
1.1.介紹Introction
從OpenCV2.4開始,加入了新的類FaceRecognizer,我們可以使用它便捷地進行人臉識別實驗。本文既介紹代碼使用,又介紹演算法原理。(他寫的源代碼,我們可以在OpenCV的opencv\moles\contrib\doc\facerec\src下找到,當然也可以在他的github中找到,如果你想研究源碼,自然可以去看看,不復雜)目前支持的演算法有
Eigenfaces特徵臉createEigenFaceRecognizer()
Fisherfaces createFisherFaceRecognizer()
LocalBinary Patterns Histograms局部二值直方圖 createLBPHFaceRecognizer()
下面所有的例子中的代碼在OpenCV安裝目錄下的samples/cpp下面都能找到,所有的代碼商用或者學習都是免費的。1.2.人臉識別Face Recognition
對人類來說,人臉識別很容易。文獻[Tu06]告訴我們,僅僅是才三天的嬰兒已經可以區分周圍熟悉的人臉了。那麼對於計算機來說,到底有多難?其實,迄今為止,我們對於人類自己為何可以區分不同的人所知甚少。是人臉內部特徵(眼睛、鼻子、嘴巴)還是外部特徵(頭型、發際線)對於人類識別更有效?我們怎麼分析一張圖像,大腦是如何對它編碼的?David Hubel和TorstenWiesel向我們展示,我們的大腦針對不同的場景,如線、邊、角或者運動這些局部特徵有專門的神經細胞作出反應。顯然我們沒有把世界看成零散的塊塊,我們的視覺皮層必須以某種方式把不同的信息來源轉化成有用的模式。自動人臉識別就是如何從一幅圖像中提取有意義的特徵,把它們放入一種有用的表示方式,然後對他們進行一些分類。基於幾何特徵的人臉的人臉識別可能是最直觀的方法來識別人臉。第一個自動人臉識別系統在[Kanade73]中又描述:標記點(眼睛、耳朵、鼻子等的位置)用來構造一個特徵向量(點與點之間的距離、角度等)。通過計算測試和訓練圖像的特徵向量的歐氏距離來進行識別。這樣的方法對於光照變化很穩健,但也有巨大的缺點:標記點的確定是很復雜的,即使是使用最先進的演算法。一些幾何特徵人臉識別近期工作在文獻[Bru92]中有描述。一個22維的特徵向量被用在一個大資料庫上,單靠幾何特徵不能提供足夠的信息用於人臉識別。特徵臉方法在文獻[TP91]中有描述,他描述了一個全面的方法來識別人臉:面部圖像是一個點,這個點是從高維圖像空間找到它在低維空間的表示,這樣分類變得很簡單。低維子空間低維是使用主元分析(Principal Component Analysis,PCA)找到的,它可以找擁有最大方差的那個軸。雖然這樣的轉換是從最佳重建角度考慮的,但是他沒有把標簽問題考慮進去。[gm:讀懂這段需要一些機器學習知識]。想像一個情況,如果變化是基於外部來源,比如光照。軸的最大方差不一定包含任何有鑒別性的信息,因此此時的分類是不可能的。因此,一個使用線性鑒別(Linear Discriminant Analysis,LDA)的特定類投影方法被提出來解決人臉識別問題[BHK97]。其中一個基本的想法就是,使類內方差最小的同時,使類外方差最大。
近年來,各種局部特徵提取方法出現。為了避免輸入的圖像的高維數據,僅僅使用的局部特徵描述圖像的方法被提出,提取的特徵(很有希望的)對於局部遮擋、光照變化、小樣本等情況更強健。有關局部特徵提取的方法有蓋伯小波(Gabor Waelets)([Wiskott97]),離散傅立葉變換(DiscreteCosinus Transform,DCT)([Messer06]),局部二值模式(LocalBinary Patterns,LBP)([AHP04])。使用什麼方法來提取時域空間的局部特徵依舊是一個開放性的研究問題,因為空間信息是潛在有用的信息。
1.3.人臉庫Face Database
我們先獲取一些數據來進行實驗吧。我不想在這里做一個幼稚的例子。我們在研究人臉識別,所以我們需要一個真的人臉圖像!你可以自己創建自己的數據集,也可以從這里(http://face-rec.org/databases/)下載一個。
AT&TFacedatabase又稱ORL人臉資料庫,40個人,每人10張照片。照片在不同時間、不同光照、不同表情(睜眼閉眼、笑或者不笑)、不同人臉細節(戴眼鏡或者不戴眼鏡)下採集。所有的圖像都在一個黑暗均勻的背景下採集的,正面豎直人臉(有些有有輕微旋轉)。YaleFacedatabase A ORL資料庫對於初始化測試比較適合,但它是一個簡單的資料庫,特徵臉已經可以達到97%的識別率,所以你使用其他方法很難得到更好的提升。Yale人臉資料庫是一個對於初始實驗更好的資料庫,因為識別問題更復雜。這個資料庫包括15個人(14個男人,1個女人),每一個都有11個灰度圖像,大小是320*243像素。資料庫中有光照變化(中心光照、左側光照、右側光照)、表情變化(開心、正常、悲傷、瞌睡、驚訝、眨眼)、眼鏡(戴眼鏡或者沒戴)。
壞消息是它不可以公開下載,可能因為原來的伺服器壞了。但我們可以找到一些鏡像(比如 theMIT)但我不能保證它的完整性。如果你需要自己剪裁和校準圖像,可以閱讀我的筆記(bytefish.de/blog/fisherfaces)。ExtendedYale Facedatabase B 此資料庫包含38個人的2414張圖片,並且是剪裁好的。這個資料庫重點是測試特徵提取是否對光照變化強健,因為圖像的表情、遮擋等都沒變化。我認為這個資料庫太大,不適合這篇文章的實驗,我建議使用ORL資料庫。
1.3.1. 准備數據
我們從網上下了數據,下了我們需要在程序中讀取它,我決定使用CSV文件讀取它。一個CSV文件包含文件名,緊跟一個標簽。
/path/to/image.ext;0
假設/path/to/image.ext是圖像,就像你在windows下的c:/faces/person0/image0.jpg。最後我們給它一個標簽0。這個標簽類似代表這個人的名字,所以同一個人的照片的標簽都一樣。我們對下載的ORL資料庫進行標識,可以獲取到如下結果:
./at/s1/1.pgm;0
./at/s1/2.pgm;0
...
./at/s2/1.pgm;1
./at/s2/2.pgm;1
...
./at/s40/1.pgm;39
./at/s40/2.pgm;39
想像我已經把圖像解壓縮在D:/data/at下面,而CSV文件在D:/data/at.txt。下面你根據自己的情況修改替換即可。一旦你成功建立CSV文件,就可以像這樣運行示常式序:
facerec_demo.exe D:/data/at.txt
1.3.2 Creating the CSV File
你不需要手工來創建一個CSV文件,我已經寫了一個Python程序來做這事。
[gm:說一個我實現的方法
如果你會cmd命令,或者稱DOS命令,那麼你打開命令控制台。假設我們的圖片放在J:下的Faces文件夾下,可以輸入如下語句:
J:\Faces\ORL>dir /b/s *.bmp > at.txt
然後你打開at.txt文件可能看到如下內容(後面的0,1..標簽是自己加的):
。。。。
J:\Faces\ORL\s1\1.bmp;0
J:\Faces\ORL\s1\10.bmp;0
J:\Faces\ORL\s1\2.bmp;0
J:\Faces\ORL\s1\3.bmp;0
J:\Faces\ORL\s1\4.bmp;0
J:\Faces\ORL\s1\5.bmp;0
J:\Faces\ORL\s1\6.bmp;0
J:\Faces\ORL\s1\7.bmp;0
J:\Faces\ORL\s1\8.bmp;0
J:\Faces\ORL\s1\9.bmp;0
J:\Faces\ORL\s10\1.bmp;1
J:\Faces\ORL\s10\10.bmp;1
J:\Faces\ORL\s10\2.bmp;1
J:\Faces\ORL\s10\3.bmp;1
J:\Faces\ORL\s10\4.bmp;1
J:\Faces\ORL\s10\5.bmp;1
J:\Faces\ORL\s10\6.bmp;1
。。。。
自然還有c++編程等方法可以做得更好,看這篇文章反響,如果很多人需要,我就把這部分的代碼寫出來。(遍歷多個文件夾,標上標簽)
]
特徵臉Eigenfaces
我們講過,圖像表示的問題是他的高維問題。二維灰度圖像p*q大小,是一個m=qp維的向量空間,所以一個100*100像素大小的圖像就是10,000維的圖像空間。問題是,是不是所有的維數空間對我們來說都有用?我們可以做一個決定,如果數據有任何差異,我們可以通過尋找主元來知道主要信息。主成分分析(Principal Component Analysis,PCA)是KarlPearson (1901)獨立發表的,而 Harold Hotelling (1933)把一些可能相關的變數轉換成一個更小的不相關的子集。想法是,一個高維數據集經常被相關變數表示,因此只有一些的維上數據才是有意義的,包含最多的信息。PCA方法尋找數據中擁有最大方差的方向,被稱為主成分。
演算法描述Algorithmic Description
令 2 表示一個隨機特徵,其中 3 .
計算均值向量 4
5
計算協方差矩陣 S
6
計算 的特徵值7 和對應的特徵向量 8 9
對特徵值進行遞減排序,特徵向量和它順序一致. K個主成分也就是k個最大的特徵值對應的特徵向量。
x的K個主成份:
10其中11 .
PCA基的重構:
12其中 13 .
然後特徵臉通過下面的方式進行人臉識別:
A. 把所有的訓練數據投影到PCA子空間
B. 把待識別圖像投影到PCA子空間
C. 找到訓練數據投影後的向量和待識別圖像投影後的向量最近的那個。
還有一個問題有待解決。比如我們有400張圖片,每張100*100像素大小,那麼PCA需要解決協方差矩陣 14的求解,而X的大小是10000*400,那麼我們會得到10000*10000大小的矩陣,這需要大概0.8GB的內存。解決這個問題不容易,所以我們需要另一個計策。就是轉置一下再求,特徵向量不變化。文獻 [Duda01]中有描述。
[gm:這個PCA還是自己搜著看吧,這里的講的不清楚,不適合初學者看]OpenCV中使用特徵臉Eigenfaces in OpenCV
給出示常式序源代碼
#include "opencv2/core/core.hpp"
#include "opencv2/contrib/contrib.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
usingnamespace cv;
usingnamespace std;
static Mat norm_0_255(InputArray _src) {
Mat src = _src.getMat();
// 創建和返回一個歸一化後的圖像矩陣:
Mat dst;
switch(src.channels()) {
case1:
cv::normalize(_src, dst, 0,255, NORM_MINMAX, CV_8UC1);
break;
case3:
cv::normalize(_src, dst, 0,255, NORM_MINMAX, CV_8UC3);
break;
default:
src.To(dst);
break;
}
return dst;
}
//使用CSV文件去讀圖像和標簽,主要使用stringstream和getline方法
staticvoid 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(CV_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, constchar*argv[]) {
// 檢測合法的命令,顯示用法
// 如果沒有參數輸入則退出!.
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]);
}
//讀取你的CSV文件路徑.
string fn_csv = string(argv[1]);
// 2個容器來存放圖像數據和對應的標簽
vector<Mat> images;
vector<int> labels;
// 讀取數據. 如果文件不合法就會出錯
// 輸入的文件名已經有了.
try {
read_csv(fn_csv, images, labels);
} catch (cv::Exception& e) {
cerr <<"Error opening file \""<< fn_csv <<"\". Reason: "<< e.msg << endl;
// 文件有問題,我們啥也做不了了,退出了
exit(1);
}
// 如果沒有讀取到足夠圖片,我們也得退出.
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(CV_StsError, error_message);
}
// 得到第一張照片的高度. 在下面對圖像
// 變形到他們原始大小時需要
int height = images[0].rows;
// 下面的幾行代碼僅僅是從你的數據集中移除最後一張圖片
//[gm:自然這里需要根據自己的需要修改,他這里簡化了很多問題]
Mat testSample = images[images.size() -1];
int testLabel = labels[labels.size() -1];
images.pop_back();
labels.pop_back();
// 下面幾行創建了一個特徵臉模型用於人臉識別,
// 通過CSV文件讀取的圖像和標簽訓練它。
// T這里是一個完整的PCA變換
//如果你只想保留10個主成分,使用如下代碼
// cv::createEigenFaceRecognizer(10);
//
// 如果你還希望使用置信度閾值來初始化,使用以下語句:
// cv::createEigenFaceRecognizer(10, 123.0);
//
// 如果你使用所有特徵並且使用一個閾值,使用以下語句:
// cv::createEigenFaceRecognizer(0, 123.0);
//
Ptr<FaceRecognizer> model = createEigenFaceRecognizer();
model->train(images, labels);
// 下面對測試圖像進行預測,predictedLabel是預測標簽結果
int predictedLabel = model->predict(testSample);
//
// 還有一種調用方式,可以獲取結果同時得到閾值:
// 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;
// 這里是如何獲取特徵臉模型的特徵值的例子,使用了getMat方法:
Mat eigenvalues = model->getMat("eigenvalues");
// 同樣可以獲取特徵向量:
Mat W = model->getMat("eigenvectors");
// 得到訓練圖像的均值向量
Mat mean = model->getMat("mean");
// 現實還是保存:
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)));
}
// 現實還是保存特徵臉:
for (int i =0; i < min(10, W.cols); i++) {
string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i));
cout << msg << endl;
// 得到第 #i個特徵
Mat ev = W.col(i).clone();
//把它變成原始大小,為了把數據顯示歸一化到0~255.
Mat grayscale = norm_0_255(ev.reshape(1, height));
// 使用偽彩色來顯示結果,為了更好的感受.
Mat cgrayscale;
applyColorMap(grayscale, cgrayscale, COLORMAP_JET);
// 顯示或者保存:
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));
}
}
// 在一些預測過程中,顯示還是保存重建後的圖像:
for(int num_components =10; num_components <300; num_components+=15) {
// 從模型中的特徵向量截取一部分
Mat evs = Mat(W, Range::all(), Range(0, num_components));
Mat projection = subspaceProject(evs, mean, images[0].reshape(1,1));
Mat reconstruction = subspaceReconstruct(evs, mean, projection);
// 歸一化結果,為了顯示:
reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows));
// 顯示或者保存:
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);
}
}
// 如果我們不是存放到文件中,就顯示他,這里使用了暫定等待鍵盤輸入:
if(argc==2) {
waitKey(0);
}
return0;
}
⑵ 用OpenCV開發人臉識別軟體,用java好還是用C/C++好
我去年就用opencv開發的android手機端的關於人臉識別的增強現實應用。我可以很明確的告訴你,java的opencv頂多調用攝像頭用,圖像處理都用c++的opencv。對於opencv的開發,不管從開發效率還是執行效率,絕對是c++。java版的opencv想都不要想。