【計算機(jī)視覺】OpenCV人臉識別facerec源碼分析2——LBPH概述

人臉識別

從OpenCV2.4開始霹抛,加入了新的類FaceRecognizer,我們可以使用它便捷地進(jìn)行人臉識別實驗援岩。其源代碼可以在OpenCV中的opencv\modules\contrib\doc\facerec\src下找到有勾。
目前支持的算法有:

Eigenfaces特征臉createEigenFaceRecognizer()
Fisherfaces createFisherFaceRecognizer()
Local Binary Patterns Histograms局部二值直方圖 createLBPHFaceRecognizer()

自動人臉識別就是如何從一幅圖像中提取有意義的特征,把它們放入一種有用的表示方式蛙吏,然后對他們進(jìn)行一些分類。
特征臉方法描述了一個全面的方法來識別人臉:面部圖像是一個點,這個點是從高維圖像空間找到它在低維空間的表示捉偏,這樣分類變得很簡單。低維子空間低維是使用主元分析(Principal Component Analysis,PCA)找到的泻红,它可以找擁有最大方差的那個軸夭禽。雖然這樣的轉(zhuǎn)換是從最佳重建角度考慮的,但是他沒有把標(biāo)簽問題考慮進(jìn)去谊路。想象一個情況驻粟,如果變化是基于外部來源,比如光照凶异。軸的最大方差不一定包含任何有鑒別性的信息蜀撑,因此此時的分類是不可能的。因此剩彬,一個使用線性鑒別(Linear Discriminant Analysis,LDA)的特定類投影方法被提出來解決人臉識別問題酷麦。其中一個基本的想法就是,使類內(nèi)方差最小的同時喉恋,使類外方差最大沃饶。
近年來,各種局部特征提取方法出現(xiàn)轻黑。為了避免輸入的圖像的高維數(shù)據(jù)糊肤,僅僅使用的局部特征描述圖像的方法被提出,提取的特征(很有希望的)對于局部遮擋氓鄙、光照變化馆揉、小樣本等情況更強健。有關(guān)局部特征提取的方法有蓋伯小波(Gabor Waelets)升酣,離散傅立葉變換(Discrete Cosinus Transform,DCT)舷暮,局部二值模式(Local Binary Patterns,LBP)。使用什么方法來提取時域空間的局部特征依舊是一個開放性的研究問題噩茄,因為空間信息是潛在有用的信息下面。

局部二值模式直方圖Local Binary Patterns Histograms

由于Eigenfaces和Fisherfaces兩種方法當(dāng)引入新的人臉數(shù)據(jù)時需要重新進(jìn)行訓(xùn)練,所以這里我著重介紹LBP特征的有關(guān)內(nèi)容绩聘。

Eigenfaces和Fisherfaces使用整體方法來進(jìn)行人臉識別[gm:直接使用所有的像素]沥割。你把你的數(shù)據(jù)當(dāng)作圖像空間的高維向量。我們都知道高維數(shù)據(jù)是糟糕的凿菩,所以一個低維子空間被確定驯遇,對于信息保存可能很好。Eigenfaces是最大化總的散度蓄髓,這樣可能導(dǎo)致叉庐,當(dāng)方差由外部條件產(chǎn)生時,最大方差的主成分不適合用來分類会喝。所以為使用一些鑒別分析陡叠,我們使用了LDA方法來優(yōu)化。Fisherfaces方法可以很好的運作肢执,至少在我們假設(shè)的模型的有限情況下枉阵。
現(xiàn)實生活是不完美的。你無法保證在你的圖像中光照條件是完美的预茄,或者說1個人的10張照片兴溜。所以,如果每人僅僅只有一張照片呢耻陕?我們的子空間的協(xié)方差估計方法可能完全錯誤拙徽,所以識別也可能錯誤。
一些研究專注于圖像局部特征的提取诗宣。主意是我們不把整個圖像看成一個高維向量膘怕,僅僅用局部特征來描述一個物體。通過這種方式提取特征召庞,你將獲得一個低維隱式岛心。一個好主意!但是你很快發(fā)現(xiàn)這種圖像表示方法不僅僅遭受光照變化篮灼。你想想圖像中的尺度變化忘古、形變、旋轉(zhuǎn)—我們的局部表示方式起碼對這些情況比較穩(wěn)健诅诱。正如SIFT髓堪,LBP方法在2D紋理分析中舉足輕重。LBP的基本思想是對圖像的像素和它局部周圍像素進(jìn)行對比后的結(jié)果進(jìn)行求和。把這個像素作為中心旦袋,對相鄰像素進(jìn)行閾值比較骤菠。如果中心像素的亮度大于等于他的相鄰像素它改,把他標(biāo)記為1疤孕,否則標(biāo)記為0。你會用二進(jìn)制數(shù)字來表示每個像素央拖,比如11001111祭阀。因此,由于周圍相鄰8個像素鲜戒,你最終可能獲取2^8個可能組合专控,被稱為局部二值模式,有時被稱為LBP碼遏餐。第一個在文獻(xiàn)中描述的LBP算子實際使用的是3*3的鄰域伦腐。


算法描述

一個更加正式的LBP操作可以被定義為:



其中(xc,yc)是中心像素,亮度是ic失都;而in是相鄰像素的亮度柏蘑。s是一個符號函數(shù)。
這種描述方法使得你可以很好的捕捉到圖像中的細(xì)節(jié)粹庞。實際上咳焚,研究者們可以用它在紋理分類上得到最先進(jìn)的水平。正如剛才描述的方法被提出后庞溜,固定的近鄰區(qū)域?qū)τ诔叨茸兓木幋a失效革半。所以,使用一個變量的擴(kuò)展方法是使用可變半徑的圓對近鄰像素進(jìn)行編碼流码,這樣可以捕捉到如下的近鄰:



對一個給定的點(xc,yc)又官,他的近鄰點(xp,yp),p∈P可以由如下計算:

其中,R是圓的半徑漫试,而P是樣本點的個數(shù)赏胚。

這個操作是對原始LBP算子的擴(kuò)展,所以有時被稱為擴(kuò)展LBP(又稱為圓形LBP)商虐。如果一個在圓上的點不在圖像坐標(biāo)上觉阅,我們使用他的內(nèi)插點。計算機(jī)科學(xué)有一堆聰明的插值方法秘车,而OpenCV使用雙線性插值典勇。


LBP算子,對于灰度的單調(diào)變化很穩(wěn)健叮趴。我們可以看到手工改變后的圖像的LBP圖像割笙。


那么剩下來的就是如何合并空間信息用于人臉識別模型。對LBP圖像成m個塊,每個塊提取直方圖伤溉。通過連接局部特直方圖(而不是合并)然后就能得到空間增強的特征向量般码。這些直方圖被稱為局部二值模式直方圖。

源碼分析

LBPH類聲明

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;
};

構(gòu)建LBPH實例

//聲明
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);
}

參數(shù)說明:

  • radius :該參數(shù)用于構(gòu)建圓LBP特征乱顾。
  • neighbors :該參數(shù)是構(gòu)建圓LBP特征所需要的近鄰像素的個數(shù)板祝,常用是8個采樣點。采樣點越多走净,計算代價越大券时。
  • grid_x : 該參數(shù)是水平方向上劃分的格子塊個數(shù),常規(guī)是8個伏伯。區(qū)塊越多橘洞,最終構(gòu)建結(jié)果的特征向量的維度越高。
  • grid_y : 該參數(shù)是垂直方向上劃分的格子塊個數(shù)说搅,常規(guī)是8個炸枣。
  • threshold : 該閾值用于預(yù)測。如果最近鄰的距離大于該閾值弄唧,預(yù)測的方法返回-1适肠。

LBPH的訓(xùn)練過程

下面給出了LBPH訓(xùn)練函數(shù)train的源碼,再進(jìn)行分析套才。

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);
    }
}

訓(xùn)練過程分為以下幾個過程:

  1. 首先進(jìn)行必要的錯誤檢查迂猴,得到人臉圖像向量和標(biāo)簽向量
  2. 計算lbp圖像
  3. 根據(jù)lbp圖像得到空間直方圖
  4. 將空間直方圖矩陣納入到私有變量_histograms向量中

生成lbp空間直方圖的過程:

  • elbp函數(shù)用于生成lbp圖像。
  • spatial_histogram函數(shù)用于將lbp圖像分塊背伴,對每一個區(qū)塊進(jìn)行直方圖統(tǒng)計沸毁。

LBPH的預(yù)測過程

下面給出了LBPH預(yù)測函數(shù)predict的源碼,再進(jìn)行分析傻寂。

void LBPH::predict(InputArray _src, int &minClass, double &minDist) const {
    if(_histograms.empty()) {
        // throw error if no data (or simply return -1?)
        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);
        }
    }
}

預(yù)測過程就比較簡單了息尺,首先將待查詢點圖像進(jìn)行l(wèi)bp編碼并生成空間直方圖,然后線性暴力的計算直方圖的距離疾掰,最終輸出距離最小的預(yù)測類別搂誉。

compareHist函數(shù)
通過cv::compareHist函數(shù)來評估兩個直方圖有多么不同、或者多么相似静檬,返回測量距離炭懊。
相似度衡量的辦法目前支持4種:
– CV_COMP_CORREL Correlation相關(guān)系數(shù),相同為1拂檩,相似度范圍為[ 1, 0 )
– CV_COMP_CHISQR Chi-Square卡方侮腹,相同為0,相似度范圍為[ 0, +inf )
– CV_COMP_INTERSECT Intersection直方圖交,數(shù)越大越相似稻励,父阻,相似度范圍為[ 0, +inf )
– CV_COMP_BHATTACHARYYA Bhattacharyya distance做常態(tài)分別比對的Bhattacharyya 距離,相同為0,,相似度范圍為[ 0, +inf )

save和load函數(shù)

OpenCV中有一套自己的處理文件存儲的類加矛,可以以key-value的形式存取相應(yīng)的參數(shù)履婉。

void LBPH::load(const FileStorage& fs) {
    fs["radius"] >> _radius;
    fs["neighbors"] >> _neighbors;
    fs["grid_x"] >> _grid_x;
    fs["grid_y"] >> _grid_y;
    //read matrices
    readFileNodeList(fs["histograms"], _histograms);
   fs["labels"] >> _labels;
}

// See cv::FaceRecognizer::save.
void LBPH::save(FileStorage& fs) const {
    fs << "radius" << _radius;
    fs << "neighbors" << _neighbors;
    fs << "grid_x" << _grid_x;
    fs << "grid_y" << _grid_y;
    // write matrices
    writeFileNodeList(fs, "histograms", _histograms);
    fs << "labels" << _labels;
}

轉(zhuǎn)載請注明作者Jason Ding及其出處
Github主頁(http://jasonding1354.github.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
簡書主頁(http://www.reibang.com/users/2bd9b48f6ea8/latest_articles)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斟览,隨后出現(xiàn)的幾起案子毁腿,更是在濱河造成了極大的恐慌,老刑警劉巖趣惠,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狸棍,死亡現(xiàn)場離奇詭異身害,居然都是意外死亡味悄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門塌鸯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侍瑟,“玉大人,你說我怎么就攤上這事丙猬≌茄眨” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵茧球,是天一觀的道長庭瑰。 經(jīng)常有香客問我,道長抢埋,這世上最難降的妖魔是什么弹灭? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮揪垄,結(jié)果婚禮上穷吮,老公的妹妹穿的比我還像新娘。我一直安慰自己饥努,他們只是感情好捡鱼,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酷愧,像睡著了一般驾诈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溶浴,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天乍迄,我揣著相機(jī)與錄音,去河邊找鬼戳葵。 笑死就乓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播生蚁,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼噩翠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了邦投?” 一聲冷哼從身側(cè)響起伤锚,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎志衣,沒想到半個月后屯援,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡念脯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年狞洋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绿店。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡吉懊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出假勿,到底是詐尸還是另有隱情借嗽,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布转培,位于F島的核電站恶导,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏浸须。R本人自食惡果不足惜惨寿,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羽戒。 院中可真熱鬧缤沦,春花似錦、人聲如沸易稠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽驶社。三九已至企量,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間亡电,已是汗流浹背届巩。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留份乒,地道東北人恕汇。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓腕唧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瘾英。 傳聞我的和親對象是個殘疾皇子枣接,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 這些年計算機(jī)視覺識別和搜索這個領(lǐng)域非常熱鬧,后期出現(xiàn)了很多的創(chuàng)業(yè)公司缺谴,大公司也在這方面也花了很多力氣在做但惶。做視覺搜...
    方弟閱讀 6,492評論 6 24
  • LBP簡介 LBP(Local Binary Pattern,局部二值模式)是一種用來描述圖像局部紋理特征的算子湿蛔;...
    JasonDing閱讀 5,111評論 1 4
  • 鑒于中文語境下膀曾,學(xué)習(xí) OpenCV 的資料其實稀少,不是主要講解已經(jīng)過時de 1.x 版內(nèi)容《學(xué)習(xí) OpenCV...
    YimianDai閱讀 6,929評論 2 34
  • 特征提取是計算機(jī)視覺和圖像處理中的一個概念阳啥。它指的是使用計算機(jī)提取圖像信息添谊,決定每個圖像的點是否屬于一個圖像特征。...
    ChrisJO閱讀 2,412評論 1 10
  • 特征提取是計算機(jī)視覺和圖像處理中的一個概念苫纤。它指的是使用計算機(jī)提取圖像信息碉钠,決定每個圖像的點是否屬于一個圖像特征纲缓。...
    mogu醬閱讀 2,085評論 1 11