從OpenCV2.4開始,加入了新的類FaceRecognizer,我們可使用它便捷地進行人臉辨認實驗。其源代碼可以在OpenCV中的opencvmodulescontribdocfacerecsrc下找到。
目前支持的算法有:
Eigenfaces特點臉createEigenFaceRecognizer()
Fisherfaces createFisherFaceRecognizer()
Local Binary Patterns Histograms局部2值直方圖 createLBPHFaceRecognizer()
自動人臉辨認就是如何從1幅圖象中提取成心義的特點,把它們放入1種有用的表示方式,然后對他們進行1些分類。
特點臉方法描寫了1個全面的方法來辨認人臉:臉部圖象是1個點,這個點是從高維圖象空間找到它在低維空間的表示,這樣分類變得很簡單。低維子空間低維是使用主元分析(Principal Component Analysis,PCA)找到的,它可以找具有最大方差的那個軸。雖然這樣的轉換是從最好重建角度斟酌的,但是他沒有把標簽問題斟酌進去。想象1個情況,如果變化是基于外部來源,比如光照。軸的最大方差不1定包括任何有鑒別性的信息,因此此時的分類是不可能的。因此,1個使用線性鑒別(Linear Discriminant Analysis,LDA)的特定類投影方法被提出來解決人臉辨認問題。其中1個基本的想法就是,使類內方差最小的同時,使類外方差最大。
最近幾年來,各種局部特點提取方法出現。為了不輸入的圖象的高維數據,僅僅使用的局部特點描寫圖象的方法被提出,提取的特點(很有希望的)對局部遮擋、光照變化、小樣本等情況更強健。有關局部特點提取的方法有蓋伯小波(Gabor Waelets),離散傅立葉變換(Discrete Cosinus Transform,DCT),局部2值模式(Local Binary Patterns,LBP)。使用甚么方法來提取時域空間的局部特點照舊是1個開放性的研究問題,由于空間信息是潛伏有用的信息。
由于Eigenfaces和Fisherfaces兩種方法當引入新的人臉數據時需要重新進行訓練,所以這里我側重介紹LBP特點的有關內容。
Eigenfaces和Fisherfaces使用整體方法來進行人臉辨認[gm:直接使用所有的像素]。你把你的數據當作圖象空間的高維向量。我們都知道高維數據是糟的,所以1個低維子空間被肯定,對信息保存可能很好。Eigenfaces是最大化總的散度,這樣可能致使,當方差由外部條件產生時,最大方差的主成份不合適用來分類。所以為使用1些鑒別分析,我們使用了LDA方法來優化。Fisherfaces方法可以很好的運作,最少在我們假定的模型的有限情況下。
現實生活是不完善的。你沒法保證在你的圖象中光照條件是完善的,或說1個人的10張照片。所以,如果每人僅僅只有1張照片呢?我們的子空間的協方差估計方法可能完全毛病,所以辨認也可能毛病。
1些研究專注于圖象局部特點的提取。主張是我們不把全部圖象看成1個高維向量,僅僅用局部特點來描寫1個物體。通過這類方式提取特點,你將取得1個低維隱式。1個好主張!但是你很快發現這類圖象表示方法不單單遭受光照變化。你想一想圖象中的尺度變化、形變、旋轉―我們的局部表示方式最少對這些情況比較穩健。正如SIFT,LBP方法在2D紋理分析及第足輕重。LBP的基本思想是對圖象的像素和它局部周圍像素進行對照后的結果進行求和。把這個像素作為中心,對相鄰像素進行閾值比較。如果中心像素的亮度大于等于他的相鄰像素,把他標記為1,否則標記為0。你會用2進制數字來表示每一個像素,比如11001111。因此,由于周圍相鄰8個像素,你終究可能獲得2^8個可能組合,被稱為局部2值模式,有時被稱為LBP碼。第1個在文獻中描寫的LBP算籽實際使用的是3*3的鄰域。
1個更加正式的LBP操作可以被定義為:
其中(xc,yc)是中心像素,亮度是ic;而in是相鄰像素的亮度。s是1個符號函數。
這類描寫方法使得你可以很好的捕捉到圖象中的細節。實際上,研究者們可以用它在紋理分類上得到最早進的水平。正如剛才描寫的方法被提出后,固定的近鄰區域對尺度變化的編碼失效。所以,使用1個變量的擴大方法是使用可變半徑的圓對近鄰像素進行編碼,這樣可以捕捉到以下的近鄰:
對1個給定的點(xc,yc),他的近鄰點(xp,yp),p∈P可以由以下計算:
其中,R是圓的半徑,而P是樣本點的個數。
這個操作是對原始LBP算子的擴大,所以有時被稱為擴大LBP(又稱為圓形LBP)。如果1個在圓上的點不在圖象坐標上,我們使用他的內插點。計算機科學有1堆聰明的插值方法,而OpenCV使用雙線性插值。
LBP算子,對灰度的單調變化很穩健。我們可以看得手工改變后的圖象的LBP圖象。
那末剩下來的就是如何合并空間信息用于人臉辨認模型。對LBP圖象成m個塊,每一個塊提取直方圖。通過連接局部特直方圖(而不是合并)然后就可以得到空間增強的特點向量。這些直方圖被稱為局部2值模式直方圖。
class LBPH : public FaceRecognizer
{
private:
int _grid_x;
int _grid_y;
int _radius;
int _neighbors;
double _threshold;
vector<Mat> _histograms;
Mat _labels;
// Computes a LBPH model with images in src and
// corresponding labels in labels, possibly preserving
// old model data.
void train(InputArrayOfArrays src, InputArray labels, bool preserveData);
public:
using FaceRecognizer::save;
using FaceRecognizer::load;
// Initializes this LBPH Model. The current implementation is rather fixed
// as it uses the Extended Local Binary Patterns per default.
//
// radius, neighbors are used in the local binary patterns creation.
// grid_x, grid_y control the grid size of the spatial histograms.
LBPH(int radius_=1, int neighbors_=8,
int gridx=8, int gridy=8,
double threshold = DBL_MAX) :
_grid_x(gridx),
_grid_y(gridy),
_radius(radius_),
_neighbors(neighbors_),
_threshold(threshold) {}
// Initializes and computes this LBPH Model. The current implementation is
// rather fixed as it uses the Extended Local Binary Patterns per default.
//
// (radius=1), (neighbors=8) are used in the local binary patterns creation.
// (grid_x=8), (grid_y=8) controls the grid size of the spatial histograms.
LBPH(InputArrayOfArrays src,
InputArray labels,
int radius_=1, int neighbors_=8,
int gridx=8, int gridy=8,
double threshold = DBL_MAX) :
_grid_x(gridx),
_grid_y(gridy),
_radius(radius_),
_neighbors(neighbors_),
_threshold(threshold) {
train(src, labels);
}
~LBPH() { }
// Computes a LBPH model with images in src and
// corresponding labels in labels.
void train(InputArrayOfArrays src, InputArray labels);
// Updates this LBPH model with images in src and
// corresponding labels in labels.
void update(InputArrayOfArrays src, InputArray labels);
// Predicts the label of a query image in src.
int predict(InputArray src) const;
// Predicts the label and confidence for a given sample.
void predict(InputArray _src, int &label, double &dist) const;
// See FaceRecognizer::load.
void load(const FileStorage& fs);
// See FaceRecognizer::save.
void save(FileStorage& fs) const;
// Getter functions.
int neighbors() const { return _neighbors; }
int radius() const { return _radius; }
int grid_x() const { return _grid_x; }
int grid_y() const { return _grid_y; }
AlgorithmInfo* info() const;
};
//聲明
Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius=1, int neighbors=8, int grid_x=8, int grid_y=8, double threshold=DBL_MAX);
//定義
Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius, int neighbors,
int grid_x, int grid_y, double threshold)
{
return new LBPH(radius, neighbors, grid_x, grid_y, threshold);
}
參數說明:
* radius :該參數用于構建圓LBP特點。
* neighbors :該參數是構建圓LBP特點所需要的近鄰像素的個數,經常使用是8個采樣點。采樣點越多,計算代價越大。
* grid_x : 該參數是水平方向上劃分的格子塊個數,常規是8個。區塊越多,終究構建結果的特點向量的維度越高。
* grid_y : 該參數是垂直方向上劃分的格子塊個數,常規是8個。
* threshold : 該閾值用于預測。如果最近鄰的距離大于該閾值,預測的方法返回⑴。
下面給出了LBPH訓練函數train的源碼,再進行分析。
void LBPH::train(InputArrayOfArrays _in_src, InputArray _in_labels, bool preserveData) {
if(_in_src.kind() != _InputArray::STD_VECTOR_MAT && _in_src.kind() != _InputArray::STD_VECTOR_VECTOR) {
string error_message = "The images are expected as InputArray::STD_VECTOR_MAT (a std::vector<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >).";
CV_Error(CV_StsBadArg, error_message);
}
if(_in_src.total() == 0) {
string error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
CV_Error(CV_StsUnsupportedFormat, error_message);
} else if(_in_labels.getMat().type() != CV_32SC1) {
string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _in_labels.type());
CV_Error(CV_StsUnsupportedFormat, error_message);
}
// get the vector of matrices
vector<Mat> src;
_in_src.getMatVector(src);
// get the label matrix
Mat labels = _in_labels.getMat();
// check if data is well- aligned
if(labels.total() != src.size()) {
string error_message = format("The number of samples (src) must equal the number of labels (labels). Was len(samples)=%d, len(labels)=%d.", src.size(), _labels.total());
CV_Error(CV_StsBadArg, error_message);
}
// if this model should be trained without preserving old data, delete old model data
if(!preserveData) {
_labels.release();
_histograms.clear();
}
// append labels to _labels matrix
for(size_t labelIdx = 0; labelIdx < labels.total(); labelIdx++) {
_labels.push_back(labels.at<int>((int)labelIdx));
}
// store the spatial histograms of the original data
for(size_t sampleIdx = 0; sampleIdx < src.size(); sampleIdx++) {
// calculate lbp image
Mat lbp_image = elbp(src[sampleIdx], _radius, _neighbors);
// get spatial histogram from this lbp image
Mat p = spatial_histogram(
lbp_image, /* lbp_image */
static_cast<int>(std::pow(2.0, static_cast<double>(_neighbors))), /* number of possible patterns */
_grid_x, /* grid size x */
_grid_y, /* grid size y */
true);
// add to templates
_histograms.push_back(p);
}
}
訓練進程分為以下幾個進程:
- 首先進行必要的毛病檢查,得到人臉圖象向量和標簽向量
- 計算lbp圖象
- 根據lbp圖象得到空間直方圖
- 將空間直方圖矩陣納入到私有變量_histograms向量中
生成lbp空間直方圖的進程:
* elbp函數用于生成lbp圖象。
* spatial_histogram函數用于將lbp圖象分塊,對每個區塊進行直方圖統計。
下面給出了LBPH預測函數predict的源碼,再進行分析。
void LBPH::predict(InputArray _src, int &minClass, double &minDist) const {
if(_histograms.empty()) {
// throw error if no data (or simply return ⑴?)
string error_message = "This LBPH model is not computed yet. Did you call the train method?";
CV_Error(CV_StsBadArg, error_message);
}
Mat src = _src.getMat();
// get the spatial histogram from input image
Mat lbp_image = elbp(src, _radius, _neighbors);
Mat query = spatial_histogram(
lbp_image, /* lbp_image */
static_cast<int>(std::pow(2.0, static_cast<double>(_neighbors))), /* number of possible patterns */
_grid_x, /* grid size x */
_grid_y, /* grid size y */
true /* normed histograms */);
// find 1-nearest neighbor
minDist = DBL_MAX;
minClass = -1;
for(int sampleIdx = 0; sampleIdx < _histograms.size(); sampleIdx++) {
double dist = compareHist(_histograms[sampleIdx], query, CV_COMP_CHISQR);
if((dist < minDist) && (dist < _threshold)) {
minDist = dist;
minClass = _labels.at<int>(sampleIdx);
}
}
}
預測進程就比較簡單了,首先將待查詢點圖象進行lbp編碼并生成空間直方圖,然后線性暴力的計算直方圖的距離,終究輸出距離最小的預測種別。
compareHist函數
通過cv::compareHist函數來評估兩個直方圖有多么不同、或多么類似,返回丈量距離。
類似度衡量的辦法目前支持4種:
下一篇 Android從源碼框架思路開始