1. 程式人生 > >openCV學習筆記(十四) —— 人臉識別演算法(3/3)—— 區域性二值模式LBP

openCV學習筆記(十四) —— 人臉識別演算法(3/3)—— 區域性二值模式LBP

LBP(Local Binary Patterns,區域性二值模式)是提取區域性特徵作為判別依據的。LBP方法顯著的優點是對光照不敏感,但是依然沒有解決姿態和表情的問題。不過相比於特徵臉方法,LBP的識別率已經有了很大的提升。在[1]的文章裡,有些人臉庫的識別率已經達到了98%+。

LBP運算元介紹

在介紹LBPH之前先要了解LBP運算元的基本原理。LBP是Local Binary Pattern的簡稱,即區域性二值模式。它是一種有效的紋理描述運算元,具有旋轉不變性和灰度不變性等顯著特點。

基本的LBP運算元

如圖所示,最早的LBP運算元定義為在3 * 3的視窗,以視窗中心畫素點為閾值,將相鄰8個畫素的灰度值與中心畫素比較,若周圍畫素值大於中心值則該畫素位置標記為1,否則為0。這樣每個8鄰域就會產生一個8位的數,再按照其位置賦予不同權重求和一個整數就得到了該視窗的LBP值。 

圓形LBP運算元(擴充套件LBP運算元)

opencv內使用的圓形LBP運算元。基本的LBP運算元最大缺陷在於它只覆蓋了一個固定半徑範圍內的小區域,這顯然不能滿足不同尺寸和頻率紋理的要求。為了適應不同尺度紋理特徵,Ojala等對LBP運算元進行了改進,將3 * 3鄰域擴充套件到了任意領域,並用圓形代替了正方形,改進後的LBP運算元允許半徑為R的圓形鄰域內有任意多畫素點。

常見的圓形LBP運算元如圖所示,由於使用圓形來作為視窗,不能保證所有畫素點都落在圓內,因此需要使用插值演算法對沒有完全落在畫素位置點的點進行計算灰度值,opencv使用的就是雙線性插值演算法。以中心點C(Xc,Yc)為圓心,R為半徑作為LBP運算元的視窗,C周圍等距離分佈著P個點,則鄰域內某點A(Xa,Ya)的座標可以表示為

Xa = Xc + R * cos(2 * pi * i)/P;
Ya = Yc - R * sin(2 * pi * i)/P;
i = 0,1,2...P;

LBP等價模式

一個LBP運算元可以產生多種二進位制模式隨著鄰域內取樣點增加,二進位制模式種類急劇增加,如3 * 3鄰域內有8個取樣點,即有2^8=256中模式;5 * 5鄰域內有20個取樣點,就有2^20=1048576種二進位制模式。這麼多的二進位制模式對紋理的提取或者識別都提出了挑戰。為了解決這個問題,等價模式(Uniform Pattern)被提出用來對LBP運算元的模式種類進行降維。等價模式的定義為:當某個區域性二進位制模式所對應的迴圈二進位制數從0到1或從1到0最多隻有2次跳變。不符合這個定義的都歸為混合模式。00000000、00000111、10001111都是等價模式。

等價模式大大減少了二進位制模式種類同時不會丟失資訊。模式的數量可以由原來的2^n減少為n*(n-1) + 2種,n表示了鄰域內的取樣點數目。等價模式代表了影象的邊緣等關鍵模式,如下圖所示,白點表示1,黑點表示0.等價模式類佔總模式的絕大多數。 

旋轉不變的LBP運算元

上面介紹的LBP運算元是灰度不變的,但是是旋轉改變的。Maenpaa等人LBP運算元進行了拓展,將圓形鄰域進行旋轉得到的LBP值,並取其最小值作為LBP值,使得新的LBP運算元具有旋轉不變性。旋轉不變的LBP值求解過程如下圖所示,最終得到的LBP值為15。 

LBP運算元的特點

一般分析紋理的方法包括統計分析法和結構分析法。前者是基於影象灰度值的分佈和相互關係,來找出反應這些關係的特徵。後者是分析紋理的結構,從中獲取結構特徵。任何紋理都同時包含了統計特徵和結構特徵,因此單一的某種分析方法都不能取得滿意的結果。LBP方法可以看成結合了兩種方法,一方面LBP具有原始紋理和佈局規則,因此具有結構特點;同時LBP又可以看出影象經過非線性濾波之後的統計。因此,LBP方法能廣泛應用於各種紋理影象識別。

opencv中的LBPH

opencv中的LBPH人臉識別的大致過程是:

  1. 將訓練集中人臉圖調整為一維矩陣,並將調整後的人臉影象全部合成一個矩陣A;
  2. 選則檢測視窗對矩陣A用LBP運算元處理: 
    1. 計算檢測視窗(圓形)上每一點的畫素值;
    2. 對沒有完全落在畫素位置點的點進行雙線性插值;
    3. 若檢測視窗的畫素值大於中心點畫素值進行對比,則標記為1,否則標記為0。進而得到全部LBP矩陣;
  3. 根據LBP矩陣計算每個檢測視窗的空間直方圖,最終得到一個一維矩陣,即訓練集的特徵向量;
  4. 當輸入測試人臉圖片識別時,也需要計算其LBP矩陣然後根據空間直方圖獲取特徵向量;
  5. 然後通過比較測試人臉的特徵向量和訓練集的特徵向量,識別結果就是訓練集中與測試人臉“最近”的。

opencv中的LBPHFaceRecognizer類實現了實現人臉識別。其中void train(InputArrayOfArrays _in_src, InputArray _in_labels)函式和int predict(InputArray src) const函式都會通過Mat elbp(InputArray src, int radius, int neighbors)函式使用LBP運算元:

//計算擴充套件的LBP
static Mat elbp(InputArray src, int radius, int neighbors) 
{
    Mat dst;
    elbp(src, dst, radius, neighbors);
    return dst;
}

而接著通過void elbp(InputArray src, OutputArray dst, int radius, int neighbors)函式根據輸入影象進行分類:

static void elbp(InputArray src, OutputArray dst, int radius, int neighbors)
{
    int type = src.type();
    switch (type) {
    case CV_8SC1:   elbp_<char>(src,dst, radius, neighbors); break;
    case CV_8UC1:   elbp_<unsigned char>(src, dst, radius, neighbors); break;
    case CV_16SC1:  elbp_<short>(src,dst, radius, neighbors); break;
    case CV_16UC1:  elbp_<unsigned short>(src,dst, radius, neighbors); break;
    case CV_32SC1:  elbp_<int>(src,dst, radius, neighbors); break;
    case CV_32FC1:  elbp_<float>(src,dst, radius, neighbors); break;
    case CV_64FC1:  elbp_<double>(src,dst, radius, neighbors); break;
    default:
        String error_msg = format("Using Original Local Binary Patterns for feature extraction only works on single-channel images (given %d). Please pass the image data as a grayscale image!", type);
        CV_Error(Error::StsNotImplemented, error_msg);
        break;
    }
}

elbp_函式實現了擴充套件的LBP:

template <typename _Tp> static
inline void elbp_(InputArray _src, OutputArray _dst, int radius, int neighbors) {
    //獲取源矩陣
    Mat src = _src.getMat();
    // 為結果分配空間,注意結果比源矩陣外圍少了兩圈
    _dst.create(src.rows-2*radius, src.cols-2*radius, CV_32SC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    for(int n=0; n<neighbors; n++) {
        // 取樣點
        float x = static_cast<float>(radius * cos(2.0*CV_PI*n/static_cast<float>(neighbors)));
        float y = static_cast<float>(-radius * sin(2.0*CV_PI*n/static_cast<float>(neighbors)));
        // 
        int fx = static_cast<int>(floor(x));    //向下取樣
        int fy = static_cast<int>(floor(y));
        int cx = static_cast<int>(ceil(x));     //向上取樣
        int cy = static_cast<int>(ceil(y));
        // 小數部分
        float ty = y - fy;
        float tx = x - fx;
        // 為雙線性插值設定比重,離哪個座標近比重大
        float w1 = (1 - tx) * (1 - ty);
        float w2 =      tx  * (1 - ty);
        float w3 = (1 - tx) *      ty;
        float w4 =      tx  *      ty;
        // 進行插值
        for(int i=radius; i < src.rows-radius;i++) {
            for(int j=radius;j < src.cols-radius;j++) {
                // 計算插值
                float t = static_cast<float>(w1*src.at<_Tp>(i+fy,j+fx) + w2*src.at<_Tp>(i+fy,j+cx) + w3*src.at<_Tp>(i+cy,j+fx) + w4*src.at<_Tp>(i+cy,j+cx));
                // floating point precision, so check some machine-dependent epsilon
                dst.at<int>(i-radius,j-radius) += ((t > src.at<_Tp>(i,j)) || (std::abs(t-src.at<_Tp>(i,j)) < std::numeric_limits<float>::epsilon())) << n;
            }
        }
    }
}

通過計算好的LBP矩陣,計算其空間直方圖分佈,得到影象的特徵向量:

static Mat spatial_histogram(InputArray _src, int numPatterns,
                             int grid_x, int grid_y, bool /*normed*/)
{
    Mat src = _src.getMat();
    // calculate LBP patch size
    int width = src.cols/grid_x;
    int height = src.rows/grid_y;
    // allocate memory for the spatial histogram
    Mat result = Mat::zeros(grid_x * grid_y, numPatterns, CV_32FC1);
    // return matrix with zeros if no data was given
    if(src.empty())
        return result.reshape(1,1);
    // initial result_row
    int resultRowIdx = 0;
    // iterate through grid
    for(int i = 0; i < grid_y; i++) {
        for(int j = 0; j < grid_x; j++) {
            Mat src_cell = Mat(src, Range(i*height,(i+1)*height), Range(j*width,(j+1)*width));
            Mat cell_hist = histc(src_cell, 0, (numPatterns-1), true);
            // copy to the result matrix
            Mat result_row = result.row(resultRowIdx);
            cell_hist.reshape(1,1).convertTo(result_row, CV_32FC1);
            // increase row count in result matrix
            resultRowIdx++;
        }
    }
    // return result as reshaped feature vector
    return result.reshape(1,1);
}

參考