LBP特徵原理及代碼實現

1、LBP特徵的背景介紹

LBP指局部二值模式,英文全稱:Local Binary Pattern,是一種用來描述圖像局部特徵的算子,LBP特徵具備灰度不變性和旋轉不變性等顯著優勢。它是由T. Ojala, M.Pietikäinen, 和 D. Harwood [1][2]在1994年提出,因爲LBP特徵計算簡單、效果較好,所以LBP特徵在計算機視覺的許多領域都獲得了普遍的應用,LBP特徵比較出名的應用是用在人臉識別和目標檢測中,在計算機視覺開源庫Opencv中有使用LBP特徵進行人臉識別的接口,也有用LBP特徵訓練目標檢測分類器的方法,Opencv實現了LBP特徵的計算,但沒有提供一個單獨的計算LBP特徵的接口。ios

2、LBP特徵的原理

一、原始LBP特徵描述及計算方法

原始的LBP算子定義在像素3*3的鄰域內,以鄰域中心像素爲閾值,相鄰的8個像素的灰度值與鄰域中心的像素值進行比較,若周圍像素大於中心像素值,則該像素點的位置被標記爲1,不然爲0。這樣,3*3鄰域內的8個點通過比較可產生8位二進制數,將這8位二進制數依次排列造成一個二進制數字,這個二進制數字就是中心像素的LBP值,LBP值共有 28 種可能,所以LBP值有256種。中心像素的LBP值反映了該像素周圍區域的紋理信息。
備註:計算LBP特徵的圖像必須是灰度圖,若是是彩色圖,須要先轉換成灰度圖。
上述過程用圖像表示爲:
這裏寫圖片描述
這裏寫圖片描述
將上述過程用公式表示爲:
這裏寫圖片描述
(xc,yc) 爲中心像素的座標,p爲鄰域的第p個像素, ip 爲鄰域像素的灰度值, ic 爲中心像素的灰度值, s(x) 爲符號函數web

原始LBP特徵計算代碼(Opencv下):windows

//原始LBP特徵計算
template <typename _tp>
void getOriginLBPFeature(InputArray _src,OutputArray _dst)
{
    Mat src = _src.getMat();
    _dst.create(src.rows-2,src.cols-2,CV_8UC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    for(int i=1;i<src.rows-1;i++)
    {
        for(int j=1;j<src.cols-1;j++)
        {
            _tp center = src.at<_tp>(i,j);
            unsigned char lbpCode = 0;
            lbpCode |= (src.at<_tp>(i-1,j-1) > center) << 7;
            lbpCode |= (src.at<_tp>(i-1,j  ) > center) << 6;
            lbpCode |= (src.at<_tp>(i-1,j+1) > center) << 5;
            lbpCode |= (src.at<_tp>(i  ,j+1) > center) << 4;
            lbpCode |= (src.at<_tp>(i+1,j+1) > center) << 3;
            lbpCode |= (src.at<_tp>(i+1,j  ) > center) << 2;
            lbpCode |= (src.at<_tp>(i+1,j-1) > center) << 1;
            lbpCode |= (src.at<_tp>(i  ,j-1) > center) << 0;
            dst.at<uchar>(i-1,j-1) = lbpCode;
        }
    }
}

測試結果:
這裏寫圖片描述數組

二、LBP特徵的改進版本

在原始的LBP特徵提出之後,研究人員對LBP特徵進行了不少的改進,所以產生了許多LBP的改進版本。app

2.1 圓形LBP特徵(Circular LBP or Extended LBP)

       因爲原始LBP特徵使用的是固定鄰域內的灰度值,所以當圖像的尺度發生變化時,LBP特徵的編碼將會發生錯誤,LBP特徵將不能正確的反映像素點周圍的紋理信息,所以研究人員對其進行了改進[3]。基本的 LBP 算子的最大缺陷在於它只覆蓋了一個固定半徑範圍內的小區域,這顯然不能知足不一樣尺寸和頻率紋理的須要。爲了適應不一樣尺度的紋理特徵,並達到灰度和旋轉不變性的要求,Ojala 等對 LBP 算子進行了改進,將 3×3 鄰域擴展到任意鄰域,並用圓形鄰域代替了正方形鄰域,改進後的 LBP 算子容許在半徑爲 R 的圓形鄰域內有任意多個像素點。從而獲得了諸如半徑爲R的圓形區域內含有P個採樣點的LBP算子:
這裏寫圖片描述
這種LBP特徵叫作Extended LBP,也叫Circular LBP。使用可變半徑的圓對近鄰像素進行編碼,能夠獲得以下的近鄰:
這裏寫圖片描述
對於給定中心點 (xc,yc) ,其鄰域像素位置爲 (xp,yp) pP ,其採樣點 (xp,yp) 用以下公式計算:機器學習

這裏寫圖片描述
R是採樣半徑,p是第p個採樣點,P是採樣數目。因爲計算的值可能不是整數,即計算出來的點不在圖像上,咱們使用計算出來的點的插值點。目的的插值方法有不少,Opencv使用的是雙線性插值,雙線性插值的公式以下:
這裏寫圖片描述
經過LBP特徵的定義能夠看出,LBP特徵對光照變化是魯棒的,其效果以下圖所示:
這裏寫圖片描述svg

//圓形LBP特徵計算,這種方法適於理解,但在效率上存在問題,聲明時默認neighbors=8
template <typename _tp>
void getCircularLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
    Mat src = _src.getMat();
    //LBP特徵圖像的行數和列數的計算要準確
    _dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    //循環處理每一個像素
    for(int i=radius;i<src.rows-radius;i++)
    {
        for(int j=radius;j<src.cols-radius;j++)
        {
            //得到中心像素點的灰度值
            _tp center = src.at<_tp>(i,j);
            unsigned char lbpCode = 0;
            for(int k=0;k<neighbors;k++)
            {
                //根據公式計算第k個採樣點的座標,這個地方能夠優化,沒必要每次都進行計算radius*cos,radius*sin
                float x = i + static_cast<float>(radius * \
                    cos(2.0 * CV_PI * k / neighbors));
                float y = j - static_cast<float>(radius * \
                    sin(2.0 * CV_PI * k / neighbors));
                //根據取整結果進行雙線性插值,獲得第k個採樣點的灰度值

                //1.分別對x,y進行上下取整
                int x1 = static_cast<int>(floor(x));
                int x2 = static_cast<int>(ceil(x));
                int y1 = static_cast<int>(floor(y));
                int y2 = static_cast<int>(ceil(y));

                //2.計算四個點(x1,y1),(x1,y2),(x2,y1),(x2,y2)的權重
                //下面的權重計算方式有個問題,若是四個點都相等,則權重全爲0,計算出來的插值爲0
                //float w1 = (x2-x)*(y2-y); //(x1,y1)
                //float w2 = (x2-x)*(y-y1); //(x1,y2)
                //float w3 = (x-x1)*(y2-y); //(x2,y1)
                //float w4 = (x-x1)*(y-y1); //(x2,y2)

                //將座標映射到0-1之間
                float tx = x - x1;
                float ty = y - y1;
                //根據0-1之間的x,y的權重計算公式計算權重
                float w1 = (1-tx) * (1-ty);
                float w2 =    tx  * (1-ty);
                float w3 = (1-tx) *    ty;
                float w4 =    tx  *    ty;
                //3.根據雙線性插值公式計算第k個採樣點的灰度值
                float neighbor = src.at<_tp>(x1,y1) * w1 + src.at<_tp>(x1,y2) *w2 \
                    + src.at<_tp>(x2,y1) * w3 +src.at<_tp>(x2,y2) *w4;
                //經過比較得到LBP值,並按順序排列起來
                lbpCode |= (neighbor>center) <<(neighbors-k-1);
            }
            dst.at<uchar>(i-radius,j-radius) = lbpCode;
        }
    }
}
//圓形LBP特徵計算,效率優化版本,聲明時默認neighbors=8
template <typename _tp>
void getCircularLBPFeatureOptimization(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
    Mat src = _src.getMat();
    //LBP特徵圖像的行數和列數的計算要準確
    _dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    for(int k=0;k<neighbors;k++)
    {
        //計算採樣點對於中心點座標的偏移量rx,ry
        float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
        float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
        //爲雙線性插值作準備
        //對採樣點偏移量分別進行上下取整
        int x1 = static_cast<int>(floor(rx));
        int x2 = static_cast<int>(ceil(rx));
        int y1 = static_cast<int>(floor(ry));
        int y2 = static_cast<int>(ceil(ry));
        //將座標偏移量映射到0-1之間
        float tx = rx - x1;
        float ty = ry - y1;
        //根據0-1之間的x,y的權重計算公式計算權重,權重與座標具體位置無關,與座標間的差值有關
        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++)
            {
                //得到中心像素點的灰度值
                _tp center = src.at<_tp>(i,j);
                //根據雙線性插值公式計算第k個採樣點的灰度值
                float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
                    + src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
                //LBP特徵圖像的每一個鄰居的LBP值累加,累加經過與操做完成,對應的LBP值經過移位取得
                dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
            }
        }
    }
}

測試結果:
radius = 3,neighbors = 8
這裏寫圖片描述
第三幅圖像爲radius = 3,neighbors = 8,第四幅圖像爲radius = 1,neighbors = 8,從實驗結果能夠看出,半徑越小,圖像紋理越精細
這裏寫圖片描述
第三幅圖像爲radius = 3,neighbors = 8,第四幅圖像爲radius = 3,neighbors = 4,從實驗結果能夠看出,鄰域數目越小,圖像亮度越低,合理,所以4位的灰度值很小
因爲我代碼的問題,不能使neighbors >8,可改進
這裏寫圖片描述函數

2.2 旋轉不變LBP特徵

       從上面能夠看出,上面的LBP特徵具備灰度不變性,但還不具有旋轉不變性,所以研究人員又在上面的基礎上進行了擴展,提出了具備旋轉不變性的LBP特徵。
首先不斷的旋轉圓形鄰域內的LBP特徵,根據選擇獲得一系列的LBP特徵值,從這些LBP特徵值選擇LBP特徵值最小的做爲中心像素點的LBP特徵。具體作法以下圖所示:
這裏寫圖片描述
如圖,經過對獲得的LBP特徵進行旋轉,獲得一系列的LBP特徵值,最終將特徵值最小的一個特徵模式做爲中心像素點的LBP特徵。性能

//旋轉不變圓形LBP特徵計算,聲明時默認neighbors=8
template <typename _tp>
void getRotationInvariantLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
    Mat src = _src.getMat();
    //LBP特徵圖像的行數和列數的計算要準確
    _dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    for(int k=0;k<neighbors;k++)
    {
        //計算採樣點對於中心點座標的偏移量rx,ry
        float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
        float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
        //爲雙線性插值作準備
        //對採樣點偏移量分別進行上下取整
        int x1 = static_cast<int>(floor(rx));
        int x2 = static_cast<int>(ceil(rx));
        int y1 = static_cast<int>(floor(ry));
        int y2 = static_cast<int>(ceil(ry));
        //將座標偏移量映射到0-1之間
        float tx = rx - x1;
        float ty = ry - y1;
        //根據0-1之間的x,y的權重計算公式計算權重,權重與座標具體位置無關,與座標間的差值有關
        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++)
            {
                //得到中心像素點的灰度值
                _tp center = src.at<_tp>(i,j);
                //根據雙線性插值公式計算第k個採樣點的灰度值
                float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
                    + src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
                //LBP特徵圖像的每一個鄰居的LBP值累加,累加經過與操做完成,對應的LBP值經過移位取得
                dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
            }
        }
    }
    //進行旋轉不變處理
    for(int i=0;i<dst.rows;i++)
    {
        for(int j=0;j<dst.cols;j++)
        {
            unsigned char currentValue = dst.at<uchar>(i,j);
            unsigned char minValue = currentValue;
            for(int k=1;k<neighbors;k++)
            {
    //循環左移
                unsigned char temp = (currentValue>>(neighbors-k)) | (currentValue<<k);
                if(temp < minValue)
                {
                    minValue = temp;
                }
            }
            dst.at<uchar>(i,j) = minValue;
        }
    }
}

測試結果:
radius = 3,neighbors = 8,最後一幅是旋轉不變LBP特徵
這裏寫圖片描述學習

2.3 Uniform Pattern LBP特徵

       Uniform Pattern,也被稱爲等價模式或均勻模式,因爲一個LBP特徵有多種不一樣的二進制形式,對於半徑爲R的圓形區域內含有P個採樣點的LBP算子將會產生 2P 種模式。很顯然,隨着鄰域集內採樣點數的增長,二進制模式的種類是以指數形式增長的。例如:5×5鄰域內20個採樣點,有 220 =1,048,576種二進制模式。這麼多的二進制模式不利於紋理的提取、分類、識別及存取。例如,將LBP算子用於紋理分類或人臉識別時,常採用LBP模式的統計直方圖來表達圖像的信息,而較多的模式種類將使得數據量過大,且直方圖過於稀疏。所以,須要對原始的LBP模式進行降維,使得數據量減小的狀況下能最好的表示圖像的信息。
    爲了解決二進制模式過多的問題,提升統計性,Ojala提出了採用一種「等價模式」(Uniform Pattern)來對LBP算子的模式種類進行降維。Ojala等認爲,在實際圖像中,絕大多數LBP模式最多隻包含兩次從1到0或從0到1的跳變。所以,Ojala將「等價模式」定義爲:當某個LBP所對應的循環二進制數從0到1或從1到0最多有兩次跳變時,該LBP所對應的二進制就稱爲一個等價模式類。如00000000(0次跳變),00000111(只含一次從0到1的跳變),10001111(先由1跳到0,再由0跳到1,共兩次跳變)都是等價模式類。除等價模式類之外的模式都歸爲另外一類,稱爲混合模式類,例如10010111(共四次跳變)。經過這樣的改進,二進制模式的種類大大減小,而不會丟失任何信息。模式數量由原來的 2P 種減小爲 P ( P-1)+2種,其中P表示鄰域集內的採樣點數。對於3×3鄰域內8個採樣點來講,二進制模式由原始的256種減小爲58種,即:它把值分爲59類,58個uniform pattern爲一類,其它的全部值爲第59類。這樣直方圖從原來的256維變成59維。這使得特徵向量的維數更少,而且能夠減小高頻噪聲帶來的影響。
    具體實現:採樣點數目爲8個,即LBP特徵值有 28 種,共256個值,正好對應灰度圖像的0-255,所以原始的LBP特徵圖像是一幅正常的灰度圖像,而等價模式LBP特徵,根據0-1跳變次數,將這256個LBP特徵值分爲了59類,從跳變次數上劃分:跳變0次—2個,跳變1次—0個,跳變2次—56個,跳變3次—0個,跳變4次—140個,跳變5次—0個,跳變6次—56個,跳變7次—0個,跳變8次—2個。共9種跳變狀況,將這256個值進行分配,跳變小於2次的爲等價模式類,共58個,他們對應的值按照從小到大分別編碼爲1—58,即它們在LBP特徵圖像中的灰度值爲1—58,而除了等價模式類以外的混合模式類被編碼爲0,即它們在LBP特徵中的灰度值爲0,所以等價模式LBP特徵圖像總體偏暗。

//等價模式LBP特徵計算
template <typename _tp>
void getUniformPatternLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
    Mat src = _src.getMat();
    //LBP特徵圖像的行數和列數的計算要準確
    _dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    //LBP特徵值對應圖像灰度編碼表,直接默認採樣點爲8位
    uchar temp = 1;
    uchar table[256] = {0};
    for(int i=0;i<256;i++)
    {
        if(getHopTimes(i)<3)
        {
            table[i] = temp;
            temp++;
        }
    }
    //是否進行UniformPattern編碼的標誌
    bool flag = false;
    //計算LBP特徵圖
    for(int k=0;k<neighbors;k++)
    {
        if(k==neighbors-1)
        {
            flag = true;
        }
        //計算採樣點對於中心點座標的偏移量rx,ry
        float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
        float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
        //爲雙線性插值作準備
        //對採樣點偏移量分別進行上下取整
        int x1 = static_cast<int>(floor(rx));
        int x2 = static_cast<int>(ceil(rx));
        int y1 = static_cast<int>(floor(ry));
        int y2 = static_cast<int>(ceil(ry));
        //將座標偏移量映射到0-1之間
        float tx = rx - x1;
        float ty = ry - y1;
        //根據0-1之間的x,y的權重計算公式計算權重,權重與座標具體位置無關,與座標間的差值有關
        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++)
            {
                //得到中心像素點的灰度值
                _tp center = src.at<_tp>(i,j);
                //根據雙線性插值公式計算第k個採樣點的灰度值
                float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
                    + src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
                //LBP特徵圖像的每一個鄰居的LBP值累加,累加經過與操做完成,對應的LBP值經過移位取得
                dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
                //進行LBP特徵的UniformPattern編碼
                if(flag)
                {
                    dst.at<uchar>(i-radius,j-radius) = table[dst.at<uchar>(i-radius,j-radius)];
                }
            }
        }
    }
}
//計算跳變次數
int getHopTimes(int n)
{
    int count = 0;
    bitset<8> binaryCode = n;
    for(int i=0;i<8;i++)
    {
        if(binaryCode[i] != binaryCode[(i+1)%8])
        {
            count++;
        }
    }
    return count;
}

測試結果:
radius = 3,neighbors = 8,最後一幅是等價模式LBP特徵
這裏寫圖片描述

2.4 MB-LBP特徵

MB-LBP特徵,全稱爲Multiscale Block LBP,來源於論文[9],中科院的人發明的,在Traincascade級聯目標訓練檢測中的LBP特徵使用的就是MB-LBP。
MB-LBP的原理:
這裏寫圖片描述
將圖像分紅一個個小塊(Block),每一個小塊再分爲一個個的小區域(相似於HOG中的cell),小區域內的灰度平均值做爲當前小區域的灰度值,與周圍小區域灰度進行比較造成LBP特徵,生成的特徵稱爲MB-LBP,Block大小爲3*3,則小區域的大小爲1,就是原始的LBP特徵,上圖的Block大小爲9*9,小區域的大小爲3*3。
不一樣Block提取的MB-LBP特徵如圖所示:
這裏寫圖片描述
計算MB-LBP代碼:

//MB-LBP特徵的計算
void getMultiScaleBlockLBPFeature(InputArray _src,OutputArray _dst,int scale)
{
    Mat src = _src.getMat();
    Mat dst = _dst.getMat();
    //定義並計算積分圖像
    int cellSize = scale / 3;
    int offset = cellSize / 2;
    Mat cellImage(src.rows-2*offset,src.cols-2*offset,CV_8UC1);
    for(int i=offset;i<src.rows-offset;i++)
    {
        for(int j=offset;j<src.cols-offset;j++)
        {
            int temp = 0;
            for(int m=-offset;m<offset+1;m++)
            {
                for(int n=-offset;n<offset+1;n++)
                {
                    temp += src.at<uchar>(i+n,j+m);
                }
            }
            temp /= (cellSize*cellSize);
            cellImage.at<uchar>(i-cellSize/2,j-cellSize/2) = uchar(temp); 
        }
    }
    getOriginLBPFeature<uchar>(cellImage,dst);
}

效果圖:
Block=3,即原始的LBP特徵
這裏寫圖片描述
Block=9
這裏寫圖片描述
Block=15
這裏寫圖片描述
    到此爲止,尚未結束,做者對獲得LBP特徵又進行了均值模式編碼,經過對獲得的特徵圖求直方圖,獲得了LBP特徵值0-255之間(0-255即直方圖中的bin)的特徵數量,經過對bin中的數值進行排序,經過權衡,將排序在前63位的特徵值看做是等價模式類,其餘的爲混合模式類,總共64類,做者在論文中稱之爲SEMB-LBP(Statistically Effective MB-LBP )。相似於等價模式LBP,等價模式的LBP的等價模式類爲58種,混合模式類1種,共59種。兩者除了等價模式類的數量不一樣以外,主要區別在於:對等價模式類的定義不一樣,等價模式LBP是根據0-1的跳變次數定義的,而SEMB-LBP是經過對直方圖排序獲得的。固然下一步要作的就是將SEMB-LBP變爲LBPH進行使用。
計算SEMB-LBP的代碼

//求SEMB-LBP
void SEMB_LBPFeature(InputArray _src,OutputArray _dst,int scale)
{
    Mat dst=_dst.getMat();
    Mat MB_LBPImage;
    getMultiScaleBlockLBPFeature(_src,MB_LBPImage,scale);
    //imshow("dst",dst);
    Mat histMat;
    int histSize = 256;
    float range[] = {float(0),float(255)};
    const float* ranges = {range};
    //計算LBP特徵值0-255的直方圖
    calcHist(&MB_LBPImage,1,0,Mat(),histMat,1,&histSize,&ranges,true,false);
    histMat.reshape(1,1);
    vector<float> histVector(histMat.rows*histMat.cols);
    uchar table[256];
    memset(table,64,256);
    if(histMat.isContinuous())
    {
        //histVector = (int *)(histMat.data);
        //將直方圖histMat變爲vector向量histVector
        histVector.assign((float*)histMat.datastart,(float*)histMat.dataend);
        vector<float> histVectorCopy(histVector);
        //對histVector進行排序,即對LBP特徵值的數量進行排序,降序排列
        sort(histVector.begin(),histVector.end(),greater<float>());
        for(int i=0;i<63;i++)
        {
            for(int j=0;j<histVectorCopy.size();j++)
            {
                if(histVectorCopy[j]==histVector[i])
                {
                    //獲得相似於Uniform的編碼表
                    table[j]=i;
                }
            }
        }
    }
    dst = MB_LBPImage;
    //根據編碼表獲得SEMB-LBP
    for(int i=0;i<dst.rows;i++)
    {
        for(int j=0;j<dst.cols;j++)
        {
            dst.at<uchar>(i,j) = table[dst.at<uchar>(i,j)];
        }
    }
}

測試結果:
第二幅爲對MB-LBP進行編碼獲得的SEMB-LBP圖像
這裏寫圖片描述
總結:MB-LBP有點相似於先將圖像進行平滑處理,而後再求LBP特徵。而SEMB-LBP是在MB-LBP進行編碼後的圖像。相似於等價模式LBP,先求LBP特徵,再用等價模式進行編碼。當Scale=3時,MB-LBP和SEMB-LBP就是LBP和等價模式LBP。想具體瞭解須要去看論文,固然要本身實現纔會理解的更透徹。

3、LBPH——圖像的LBP特徵向量

    LBPH,Local Binary Patterns Histograms,即LBP特徵的統計直方圖,LBPH將LBP特徵與圖像的空間信息結合在一塊兒。這種表示方法由Ahonen等人在論文[3]中提出,他們將LBP特徵圖像分紅m個局部塊,並提取每一個局部塊的直方圖,而後將這些直方圖依次鏈接在一塊兒造成LBP特徵的統計直方圖,即LBPH。
一幅圖像具體的計算LBPH的過程(以Opencv中的人臉識別爲例):
1. 計算圖像的LBP特徵圖像,在上面已經講過了。
2. 將LBP特徵圖像進行分塊,Opencv中默認將LBP特徵圖像分紅8行8列64塊區域
3. 計算每塊區域特徵圖像的直方圖cell_LBPH,將直方圖進行歸一化,直方圖大小爲1*numPatterns
4. 將上面計算的每塊區域特徵圖像的直方圖按分塊的空間順序依次排列成一行,造成LBP特徵向量,大小爲1*(numPatterns*64)
5. 用機器學習的方法對LBP特徵向量進行訓練,用來檢測和識別目標
舉例說明LBPH的維度:
採樣點爲8個,若是用的是原始的LBP或Extended LBP特徵,其LBP特徵值的模式爲256種,則一幅圖像的LBP特徵向量維度爲:64*256=16384維,
而若是使用的UniformPatternLBP特徵,其LBP值的模式爲59種,其特徵向量維度爲:64*59=3776維,能夠看出,使用等價模式特徵,其特徵向量的維度大大減小,
這意味着使用機器學習方法進行學習的時間將大大減小,而性能上沒有受到很大影響。
Opencv的人臉識別使用的是Extended LBP

計算LBPH的代碼以下:

//計算LBP特徵圖像的直方圖LBPH
Mat getLBPH(InputArray _src,int numPatterns,int grid_x,int grid_y,bool normed)
{
    Mat src = _src.getMat();
    int width = src.cols / grid_x;
    int height = src.rows / grid_y;
    //定義LBPH的行和列,grid_x*grid_y表示將圖像分割成這麼些塊,numPatterns表示LBP值的模式種類
    Mat result = Mat::zeros(grid_x * grid_y,numPatterns,CV_32FC1);
    if(src.empty())
    {
        return result.reshape(1,1);
    }
    int resultRowIndex = 0;
    //對圖像進行分割,分割成grid_x*grid_y塊,grid_x,grid_y默認爲8
    for(int i=0;i<grid_x;i++)
    {
        for(int j=0;j<grid_y;j++)
        {
            //圖像分塊
            Mat src_cell = Mat(src,Range(i*height,(i+1)*height),Range(j*width,(j+1)*width));
            //計算直方圖
            Mat hist_cell = getLocalRegionLBPH(src_cell,0,(numPattern-1),true);
            //將直方圖放到result中
            Mat rowResult = result.row(resultRowIndex);
            hist_cell.reshape(1,1).convertTo(rowResult,CV_32FC1);
            resultRowIndex++;
        }
    }
    return result.reshape(1,1);
}
//計算一個LBP特徵圖像塊的直方圖
Mat getLocalRegionLBPH(const Mat& src,int minValue,int maxValue,bool normed)
{
    //定義存儲直方圖的矩陣
    Mat result;
    //計算獲得直方圖bin的數目,直方圖數組的大小
    int histSize = maxValue - minValue + 1;
    //定義直方圖每一維的bin的變化範圍
    float range[] = { static_cast<float>(minValue),static_cast<float>(maxValue + 1) };
    //定義直方圖全部bin的變化範圍
    const float* ranges = { range };
    //計算直方圖,src是要計算直方圖的圖像,1是要計算直方圖的圖像數目,0是計算直方圖所用的圖像的通道序號,從0索引
    //Mat()是要用的掩模,result爲輸出的直方圖,1爲輸出的直方圖的維度,histSize直方圖在每一維的變化範圍
    //ranges,全部直方圖的變化範圍(起點和終點)
    calcHist(&src,1,0,Mat(),result,1,&histSize,&ranges,true,false);
    //歸一化
    if(normed)
    {
        result /= (int)src.total();
    }
    //結果表示成只有1行的矩陣
    return result.reshape(1,1);
}

總結:上面的LBP特徵都是較經典的LBP特徵,除此以外,LBP特徵還有大量的變種,如TLBP(中心像素與周圍全部像素比較,而不是根據採樣點的數目),DLBP(編碼標準四個方向的灰度變化,每一個方向上用2比特編碼),MLBP(將中心像素值替換成採樣點像素的平均值),MB-LBP(上面有介紹),VLBP(沒太看懂),RGB-LBP(RGB圖像分別計算LBP,而後鏈接在一塊兒)等,具體的須要本身去研究,可參考維基百科

4、LBP特徵的匹配與使用

一、LBP特徵用在目標檢測中

人臉檢測比較出名的是Haar+Adaboost方法,其實目前的Opencv也支持LBP+Adaboost和HOG+Adaboost方法進行目標檢測,從目前個人使用效果來看,LBP+Adaboost方法用在目標檢測中的效果比Haar特徵、HOG特徵都要好(HOG特徵用的很少,主要是Haar和LBP),並且LBP特徵的訓練速度比Haar和HOG都要快不少。在LBP+Adaboost中,LBP特徵主要是用做輸入的訓練數據(特徵),使用的LBP特徵應該是DLBP(維基百科上說的,待考證,沒太看明白Cascade中LBP特徵的計算方式),具體用法須要看源碼。Opencv的TrainCascade中使用的LBP特徵是MB-LBP。
老外的對Opencv級聯檢測中使用的LBP的解釋(很是好,本身讀,就不翻譯了),在看這個以前最好是運行過TrainCascade來訓練目標檢測的分類器,並使用過LBP特徵訓練,調節過參數[8]:

OpenCV ships with a tool called traincascade that trains LBP, Haar and HOG. Specifically for face detection they even ship the 3000-image dataset of 24x24 pixel faces, in the format needed bytraincascade.

In my experience, of the three types traincascade supports, LBP takes the least time to train, taking on the order of hours rather than days for Haar.

A quick overview of its training process is that for the given number of stages (a decent choice is 20), it attempts to find features that reject as many non-faces as possible while not rejecting the faces. The balance between rejecting non-faces and keeping faces is controlled by the mininum hit rate (OpenCV chose 99.5%) and false alarm rate (OpenCV chose 50%). The specific meta-algorithm used for crafting OpenCV's own LBP cascade is Gentle AdaBoost (GAB).

The variant of LBP implemented in OpenCV is described here:

Shengcai Liao, Xiangxin Zhu, Zhen Lei, Lun Zhang and Stan Z. Li. Learning Multi-scale Block Local Binary Patterns for Face Recognition. International Conference on Biometrics (ICB), 2007, pp. 828-837.

What it amounts to in practice in OpenCV with default parameters is:

OpenCV LBP Cascade Runtime Overview

The detector examines 24x24 windows within the image looking for a face. Stepping from Stage 1 to 20 of the cascade classifier, if it can show that the current 24x24 window is likely not a face, it rejects it and moves over the window by one or two pixels over to the next position; Otherwise it proceeds to the next stage.

During each stage, 3-10 or so LBP features are examined. Every LBP feature has an offset within the window and a size, and the area it covers is fully contained within the current window. Evaluating an LBP feature at a given position can result in either a pass or fail. Depending on whether an LBP feature succeeds or fails, a positive or negative weight particular to that feature is added to an accumulator.

Once all of a stage's LBP features are evaluated, the accumulator's value is compared to the stage threshold. A stage fails if the accumulator is below the threshold, and passes if it is above. Again, if a stage fails, the cascade is exited and the window moves to the next position.

LBP feature evaluation is relatively simple. At that feature's offset within the window, nine rectangles are laid out in a 3x3 configuration. These nine rectangles are all the same size for a particular LBP feature, ranging from 1x1 to 8x8.

The sum of all the pixels in the nine rectangles are computed, in other words their integral. Then, the central rectangle's integral is compared to that of its eight neighbours. The result of these eight comparisons is eight bits (1 or 0), which are assembled in an 8-bit LBP.

This 8-bit bitvector is used as an index into a 2^8 == 256-bit LUT, computed by the training process and particular to each LBP feature, that determines whether the LBP feature passed or failed.

二、 LBP用在人臉識別中

LBP在人臉識別中比較出名,從源碼上來看,人臉識別中LBPH的使用主要是用來進行直方圖的比較,經過直方圖的比較來判斷目標的類別。在Opencv的基於LBP的人臉識別的實現中使用的LBP特徵是Extendes LBP,即圓形LBP特徵。參考的論文爲文獻[10]。
LBPH訓練主要是提取輸入的圖像的LBPH保存,當進行識別時,遍歷保存的LBPH,找到輸入圖像與訓練圖像方差最小的LBPH,將其對應的類別做爲識別的類別輸出。
用LBPH進行訓練和識別的代碼。

#include<iostream>
#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<opencv2\contrib\contrib.hpp>

using namespace std;
using namespace cv;

int main(int argc,char* argv[])
{
    vector<Mat> images;
    vector<int> labels;
    char buff[10];
    for(int i=1;i<8;i++)
    {
        sprintf(buff,"0%d.tif",i);
        Mat image = imread(buff);
        Mat grayImage;
        cvtColor(image,grayImage,COLOR_BGR2GRAY);
        images.push_back(grayImage);
        labels.push_back(1);
    }
    for(int i=8;i<12;i++)
    {
        sprintf(buff,"0%d.tif",i);
        Mat image = imread(buff);
        Mat grayImage;
        cvtColor(image,grayImage,COLOR_BGR2GRAY);
        images.push_back(grayImage);
        labels.push_back(2);
    }
    Ptr<FaceRecognizer> p = createLBPHFaceRecognizer();
    p->train(images,labels);
    Mat test= imread("12.tif");
    Mat grayImage;
    cvtColor(test,grayImage,COLOR_BGR2GRAY);
    int result = p->predict(grayImage);
    cout<<result<<endl;
    system("pause");
    return 0;
}

測試結果:
這裏寫圖片描述

參考資料

[1] T. Ojala, M. Pietikäinen, and D. Harwood (1994), 「Performance evaluation of texture measures with classification based on Kullback discrimination of distributions」, Proceedings of the 12th IAPR International Conference on Pattern Recognition (ICPR 1994), vol. 1, pp. 582 - 585.
[2] T. Ojala, M. Pietikäinen, and D. Harwood (1996), 「A Comparative Study of Texture Measures with Classification Based on Feature Distributions」, Pattern Recognition, vol. 29, pp. 51-59.
[3] Ahonen, T., Hadid, A., and Pietikainen, M. Face Recognition with Local Binary Patterns. Computer Vision- ECCV 2004 (2004), 469–481.
[4] http://blog.csdn.net/xidianzhimeng/article/details/19634573
[5] opencv參考手冊,Opencv源碼
[6] http://blog.csdn.net/zouxy09/article/details/7929531
[7] http://blog.csdn.net/songzitea/article/details/17686135
[8] http://stackoverflow.com/questions/20085833/face-detection-algorithms-with-minimal-training-time/20086402#20086402 [9] Shengcai Liao, Xiangxin Zhu, Zhen Lei, Lun Zhang and Stan Z. Li. Learning Multi-scale Block Local Binary Patterns for Face Recognition. International Conference on Biometrics (ICB), 2007, pp. 828-837. [10] Ahonen T, Hadid A. and Pietikäinen M. 「Face description with local binary patterns: Application to face recognition.」 IEEE Transactions on Pattern Analysis and Machine Intelligence, 28(12):2037-2041.