task02

地址:https://github.com/datawhalechina/team-learning/blob/master/03%20%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89%E5%9F%BA%E7%A1%80%EF%BC%9A%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86%EF%BC%88%E4%B8%8B%EF%BC%89/Task02%20LBP%E7%89%B9%E5%BE%81%E6%8F%8F%E8%BF%B0%E7%AE%97%E5%AD%90.md

2.1 簡介

LBP指局部二值模式(Local Binary Pattern)瞬逊,是一種用來描述圖像局部特征的算子显歧,具有灰度不變性和旋轉(zhuǎn)不變性等顯著優(yōu)點。LBP常應(yīng)用于人臉識別和目標檢測中确镊,在OpenCV中有使用LBP特征進行人臉識別的接口灌具,也有用LBP特征訓(xùn)練目標檢測分類器的方法猿规,OpenCV實現(xiàn)了LBP特征的計算嘁扼,但沒有提供一個單獨的計算LBP特征的接口糟秘。也就是說OpenCV中使用了LBP算法,但是沒有提供函數(shù)接口旨巷。

2.2 學(xué)習目標

  • 了解人臉檢測相關(guān)流程
  • 理解LBP算法相關(guān)原理
  • 掌握基于OpenCV的LBP算法實現(xiàn)

2.3 算法理論介紹

2.3.1 LBP原理介紹

LBP特征用圖像的局部領(lǐng)域的聯(lián)合分布T 來描述圖像的紋理特征巨缘,如果假設(shè)局部鄰域中像素個數(shù)為P(P >1),那么紋理特征的聯(lián)合分布T 可以表述成:

T=t\left(g_{c}, g_{0}, \ldots, g_{p-1}\right) \quad p=0, \ldots, P-1\tag{2-1}

其中采呐, g_c 表示相應(yīng)局部鄰域的中心像素的灰度值若锁, g_p 表示以中心像素圓心,以R為半徑的圓上的像素的灰度值。

假設(shè)中心像素和局部鄰域像素相互獨立斧吐,那么這里可以將上面定義式寫成如下形式:

\begin{aligned} T &=t\left(g_{c}, g_{0}-g_{c}, \ldots, g_{p-1}-g_{c}\right) \quad p=0, \ldots, P-1 \ & \approx t\left(g_{c}\right) t\left(g_{0}-g_{c}, \ldots, g_{p-1}-g_{c}\right) \end{aligned}\tag{2-2}

其中t(g_c)決定了局部區(qū)域的整體亮度又固,對于紋理特征,可以忽略這一項煤率,最終得到:

T \approx t\left(g_{0}-g_{c}, \ldots, g_{p-1}-g_{c}\right) \quad p=0, \ldots, P-1\tag{2-3}

上式說明仰冠,將紋理特征定義為鄰域像素和中心像素的差的聯(lián)合分布函數(shù),因為g_p ? g_c是基本不受亮度均值影響的蝶糯,所以從上式可以看出洋只,此時統(tǒng)計量T 是一個跟亮度均值,即灰度級無關(guān)的值昼捍。

最后定義特征函數(shù)如下:

\begin{array}{l} T \approx t\left(s\left(g_{0}-g_{c}\right), \ldots, s\left(g_{p-1}-g_{c}\right)\right) p=0, \ldots, P-1 \\\\ s(x)=\left\{\begin{array}{l} 1, x \geq 0 \\\\ 0, x<0 \end{array}\right. \end{array}\tag{2-4}

定義灰度級不變LBP為:

L B P_{P, R}=\sum_{p=0}^{P-1} s\left(g_{p}-g_{c}\right) 2^{p}\tag{2-5}

即二進制編碼公式识虚。

通俗解釋:

原始的LBP算子定義在像素33的鄰域內(nèi),以鄰域中心像素為閾值端三,相鄰的8個像素的灰度值與鄰域中心的像素值進行比較,若周圍像素大于中心像素值鹃彻,則該像素點的位置被標記為1郊闯,否則為0。這樣蛛株,33鄰域內(nèi)的8個點經(jīng)過比較可產(chǎn)生8為二進制數(shù)团赁,將這8位二進制數(shù)依次排列形成一個二進制數(shù)字,這個二進制數(shù)字就是中心像素的LBP值谨履,LBP值共有28種可能欢摄,因此LBP值有256種可能。中心像素的LBP值反映了該像素周圍區(qū)域的紋理信息笋粟。

注意:計算LBP特征的圖像必須是灰度圖怀挠,如果是彩色圖析蝴,需要先轉(zhuǎn)換成灰度圖


image.png

圖 2.3.1 LBP計算示意圖

2.3.2 圓形LBP算子

基本的 LBP算子的最大缺陷在于它只覆蓋了一個固定半徑范圍內(nèi)的小區(qū)域,這顯然不能滿足不同尺寸和頻率紋理的需要绿淋。為了適應(yīng)不同尺度的紋理特征闷畸,并達到灰度級和旋轉(zhuǎn)不變性的要求,Ojala等對 LBP算子進行了改進吞滞,將 3×3鄰域擴展到任意鄰域佑菩,并用圓形鄰域代替了正方形鄰域,改進后的 LBP算子允許在半徑為 R的圓形鄰域內(nèi)有任意多個像素點裁赠。從而得到了諸如半徑為R的圓形區(qū)域內(nèi)含有P個采樣點的LBP算子殿漠,表示為LBP^{R}_P

image.png

圖 2.3.2 圓形LBP示意圖

對于給定中心點(x_c,y_c),其鄰域像素位置為(x_p,y_p)佩捞,p∈P绞幌,其采樣點(x_p,y_p)用如下公式計算:

\begin{array}{l} x_{p}=x_{c}+\operatorname{Rcos}\left(\frac{2 \pi p}{P}\right) \ y_{p}=y_{c}+\operatorname{Rsin}\left(\frac{2 \pi p}{P}\right) \end{array}\tag{2-6}

R是采樣半徑,p是第p個采樣點失尖,P是采樣數(shù)目啊奄。如果近鄰點不在整數(shù)位置上,就需要進行插值運算掀潮,可以參考這篇博客 OpenCV框架下的插值算法

3.3.3 LBP旋轉(zhuǎn)不變性及等價模式
??LPB特征是灰度不變菇夸,但不是旋轉(zhuǎn)不變的,同一幅圖像仪吧,進行旋轉(zhuǎn)以后庄新,其特征將會有很大的差別,影響匹配的精度薯鼠。Ojala在LBP算法上择诈,進行改進,實現(xiàn)了具有旋轉(zhuǎn)不變性的LPB的特征出皇。

實現(xiàn)方法:不斷旋轉(zhuǎn)圓形鄰域得到一系列初始定義的LPB值羞芍,取最小值作為該鄰域的值。

L B P_{P R}^{ri}=\min \left(R O R\left(L B P_{P, R}^{ri}, i\right) | i=0,1, \ldots, P-1\right)\tag{2-7}

其中L B P_{P R}^{ri}表示具有旋轉(zhuǎn)不變性的LBP特征郊艘。ROR(x, i)為旋轉(zhuǎn)函數(shù)荷科,表示將x右循環(huán)i位。

image.png

圖 2.3.3 求取旋轉(zhuǎn)不變的LPB特征示意圖

等價模式:

一個LBP算子可以產(chǎn)生不同的二進制模式纱注,對于LBP^{R}_p將會產(chǎn)生2^p種模式畏浆。比如7*7鄰域內(nèi)有2^{36}種模式。如此多的二值模式對于信息的提取和識別都是不利的狞贱。

Ojala等認為刻获,在實際圖像中,絕大多數(shù)LPB模式最多只包含兩次從1到0或從0到1的跳變瞎嬉。

等價模式:當某個局部二進制模式所對應(yīng)的循環(huán)二進制數(shù)從0到1或從1到0最多有兩次跳變時蝎毡,該局部二進制模式所對應(yīng)的二進制就稱為一個等價模式厚柳。

比如:00000000,11111111顶掉,11110010草娜,10111111都是等價模式。

檢查某種模式是否是等價模式: U\left(G_{p}\right)=\left|s\left(g_{p_{-1}}-g_{c}\right)-s\left(g_{0}-g_{c}\right)\right|+\sum_{p=1}^{P_{-1}}\left|s\left(g_{p}-g_{c}\right)-s\left(g_{P-1}-g_{c}\right)\right|\tag{2-8}

將其和其移動一位后的二進制模式按位相減痒筒。并絕對值求和宰闰。若U\left(G_{p}\right) 小于等于2,則為等價模式簿透。

混合模式:除了等價模式之外的稱為混合模式移袍。

改進后的LPB模式數(shù)由2 ^{p}(p為鄰域集內(nèi)的采集點數(shù) ) 降維為p*(p-1)+2 。維數(shù)減少老充,可以降低高頻噪聲的影響葡盗。Ojala認為等價模式占總模式中的絕大數(shù)。圖2.4 ( a ), ( b ), ( c )等價模式分別占88%啡浊,93%和76%觅够。

image.png

圖 2.3.4

可以通過低通濾波的方法來增強等價模式所占的比例。圖2.4( c )經(jīng)過高斯濾波后巷嚣,其等價模式所占比可以增加到90%喘先。

2.3.4 人臉檢測流程

人臉檢測過程采用多尺度滑窗搜索方式,每個尺度通過一定步長截取大小為20x20的窗口廷粒,然后將窗口放到分類器中進行是不是人臉的判決窘拯,如果是人臉則該窗口通過所有分類器;反之坝茎,會在某一級分類器被排除涤姊。


image.png

圖 2.3.5 人臉檢測流程圖

2.4 基于OpenCV的實現(xiàn)

python

#coding:utf-8
import cv2 as cv

# 讀取原始圖像
img= cv.imread('*.png')
#face_detect = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

face_detect = cv.CascadeClassifier("lbpcascade_frontalface_improved.xml")
# 檢測人臉
# 灰度處理
gray = cv.cvtColor(img, code=cv.COLOR_BGR2GRAY)

# 檢查人臉 按照1.1倍放到 周圍最小像素為5
face_zone = face_detect.detectMultiScale(gray, scaleFactor = 2, minNeighbors = 2) # maxSize = (55,55)
print ('識別人臉的信息:\n',face_zone)

# 繪制矩形和圓形檢測人臉
for x, y, w, h in face_zone:
    # 繪制矩形人臉區(qū)域
    cv.rectangle(img, pt1 = (x, y), pt2 = (x+w, y+h), color = [0,0,255], thickness=2)
    # 繪制圓形人臉區(qū)域 radius表示半徑
    cv.circle(img, center = (x + w//2, y + h//2), radius = w//2, color = [0,255,0], thickness = 2)

# 設(shè)置圖片可以手動調(diào)節(jié)大小
cv.namedWindow("Easmount-CSDN", 0)

# 顯示圖片
cv.imshow("Easmount-CSDN", img)

# 等待顯示 設(shè)置任意鍵退出程序
cv.waitKey(0)
cv.destroyAllWindows()

原圖:


image.png

檢測結(jié)果:


image.png

c++

uchar GetMinBinary(uchar *binary)
{
    // 計算8個二進制
    uchar LBPValue[8] = { 0 };
    for (int i = 0; i <= 7; ++i)
    {
        LBPValue[0] += binary[i] << (7 - i);
        LBPValue[1] += binary[(i + 7) % 8] << (7 - i);
        LBPValue[2] += binary[(i + 6) % 8] << (7 - i);
        LBPValue[3] += binary[(i + 5) % 8] << (7 - i);
        LBPValue[4] += binary[(i + 4) % 8] << (7 - i);
        LBPValue[5] += binary[(i + 3) % 8] << (7 - i);
        LBPValue[6] += binary[(i + 2) % 8] << (7 - i);
        LBPValue[7] += binary[(i + 1) % 8] << (7 - i);
    }
    // 選擇最小的
    uchar minValue = LBPValue[0];
    for (int i = 1; i <= 7; ++i)
    {
        if (LBPValue[i] < minValue)
        {
            minValue = LBPValue[i];
        }
    }

    return minValue;
}

//計算9種等價模式
int ComputeValue9(int value58)
{
    int value9 = 0;
    switch (value58)
    {
    case 1:
        value9 = 1;
        break;
    case 2:
        value9 = 2;
        break;
    case 4:
        value9 = 3;
        break;
    case 7:
        value9 = 4;
        break;
    case 11:
        value9 = 5;
        break;
    case 16:
        value9 = 6;
        break;
    case 22:
        value9 = 7;
        break;
    case 29:
        value9 = 8;
        break;
    case 58:
        value9 = 9;
        break;
    }
    return value9;
}

//灰度不變常規(guī)LBP(256)
void NormalLBPImage(const Mat &srcImage, Mat &LBPImage)
{
    CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
    LBPImage.create(srcImage.size(), srcImage.type());


    Mat extendedImage;
    copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);

    // 計算LBP特征圖
    int heightOfExtendedImage = extendedImage.rows;
    int widthOfExtendedImage = extendedImage.cols;
    int widthOfLBP = LBPImage.cols;
    uchar *rowOfExtendedImage = extendedImage.data + widthOfExtendedImage + 1;
    uchar *rowOfLBPImage = LBPImage.data;
    for (int y = 1; y <= heightOfExtendedImage - 2; ++y, rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBP)
    {
        // 列
        uchar *colOfExtendedImage = rowOfExtendedImage;
        uchar *colOfLBPImage = rowOfLBPImage;
        for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
        {
            // 計算LBP值
            int LBPValue = 0;
            if (colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0])
                LBPValue += 128;
            if (colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0])
                LBPValue += 64;
            if (colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0])
                LBPValue += 32;
            if (colOfExtendedImage[0 + 1] >= colOfExtendedImage[0])
                LBPValue += 16;
            if (colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0])
                LBPValue += 8;
            if (colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0])
                LBPValue += 4;
            if (colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0])
                LBPValue += 2;
            if (colOfExtendedImage[0 - 1] >= colOfExtendedImage[0])
                LBPValue += 1;
            colOfLBPImage[0] = LBPValue;
        }
    }
}
// 等價灰度不變LBP(58)
void UniformNormalLBPImage(const Mat &srcImage, Mat &LBPImage)// 計算等價模式LBP特征圖
{
    // 參數(shù)檢查嗤放,內(nèi)存分配
    CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
    LBPImage.create(srcImage.size(), srcImage.type());

    // 計算LBP圖
    // 擴充原圖像邊界思喊,便于邊界處理
    Mat extendedImage;
    copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);

    // 構(gòu)建LBP 等價模式查找表
    //int table[256];
    //BuildUniformPatternTable(table);

    // LUT(256種每一種模式對應(yīng)的等價模式)
    static const int table[256] = { 1, 2, 3, 4, 5, 0, 6, 7, 8, 0, 0, 0, 9, 0, 10, 11, 12, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 14, 0, 15, 16, 17, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 20, 0, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25,
        0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 27, 0, 28, 29, 30, 31, 0, 32, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0
        , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 36, 37, 38, 0, 39, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42
        , 43, 44, 0, 45, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 47, 48, 49, 0, 50, 0, 0, 0, 51, 52, 53, 0, 54, 55, 56, 57, 58 };

    // 計算LBP
    int heightOfExtendedImage = extendedImage.rows;
    int widthOfExtendedImage = extendedImage.cols;
    int widthOfLBP = LBPImage.cols;
    uchar *rowOfExtendedImage = extendedImage.data + widthOfExtendedImage + 1;
    uchar *rowOfLBPImage = LBPImage.data;
    for (int y = 1; y <= heightOfExtendedImage - 2; ++y, rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBP)
    {
        // 列
        uchar *colOfExtendedImage = rowOfExtendedImage;
        uchar *colOfLBPImage = rowOfLBPImage;
        for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
        {
            // 計算LBP值
            int LBPValue = 0;
            if (colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0])
                LBPValue += 128;
            if (colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0])
                LBPValue += 64;
            if (colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0])
                LBPValue += 32;
            if (colOfExtendedImage[0 + 1] >= colOfExtendedImage[0])
                LBPValue += 16;
            if (colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0])
                LBPValue += 8;
            if (colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0])
                LBPValue += 4;
            if (colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0])
                LBPValue += 2;
            if (colOfExtendedImage[0 - 1] >= colOfExtendedImage[0])
                LBPValue += 1;

            colOfLBPImage[0] = table[LBPValue];
        }
    }
}

// 等價旋轉(zhuǎn)不變LBP(9)
void UniformRotInvLBPImage(const Mat &srcImage, Mat &LBPImage)
{
    // 參數(shù)檢查,內(nèi)存分配
    CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
    LBPImage.create(srcImage.size(), srcImage.type());

    // 擴充圖像次酌,處理邊界情況
    Mat extendedImage;
    copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);

    // 構(gòu)建LBP 等價模式查找表
    //int table[256];
    //BuildUniformPatternTable(table);

    // 通過查找表
    static const int table[256] = { 1, 2, 3, 4, 5, 0, 6, 7, 8, 0, 0, 0, 9, 0, 10, 11, 12, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 14, 0, 15, 16, 17, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 20, 0, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25,
        0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 27, 0, 28, 29, 30, 31, 0, 32, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0
        , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 36, 37, 38, 0, 39, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42
        , 43, 44, 0, 45, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 47, 48, 49, 0, 50, 0, 0, 0, 51, 52, 53, 0, 54, 55, 56, 57, 58 };

    uchar binary[8] = { 0 };// 記錄每個像素的LBP值
    int heigthOfExtendedImage = extendedImage.rows;
    int widthOfExtendedImage = extendedImage.cols;
    int widthOfLBPImage = LBPImage.cols;

    uchar *rowOfExtendedImage = extendedImage.data + widthOfExtendedImage + 1;
    uchar *rowOfLBPImage = LBPImage.data;
    for (int y = 1; y <= heigthOfExtendedImage - 2; ++y, rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBPImage)
    {
        // 列
        uchar *colOfExtendedImage = rowOfExtendedImage;
        uchar *colOfLBPImage = rowOfLBPImage;
        for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
        {
            // 計算旋轉(zhuǎn)不變LBP(最小的二進制模式)
            binary[0] = colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0] ? 1 : 0;
            binary[1] = colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0] ? 1 : 0;
            binary[2] = colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0] ? 1 : 0;
            binary[3] = colOfExtendedImage[0 + 1] >= colOfExtendedImage[0] ? 1 : 0;
            binary[4] = colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0] ? 1 : 0;
            binary[5] = colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0] ? 1 : 0;
            binary[6] = colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0] ? 1 : 0;
            binary[7] = colOfExtendedImage[0 - 1] >= colOfExtendedImage[0] ? 1 : 0;
            int minValue = GetMinBinary(binary);
            // 計算58種等價模式LBP
            int value58 = table[minValue];
            // 計算9種等價模式
            colOfLBPImage[0] = ComputeValue9(value58);
        }
    }
}
//灰度不變常規(guī)LBP(256)特征
void NormalLBPFeature(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
    // 參數(shù)檢查恨课,內(nèi)存分配
    CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
    Mat LBPImage;
    NormalLBPImage(srcImage, LBPImage);
    // 計算cell個數(shù)
    int widthOfCell = cellSize.width;
    int heightOfCell = cellSize.height;
    int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的個數(shù)
    int numberOfCell_Y = srcImage.rows / heightOfCell;

    // 特征向量的個數(shù)
    int numberOfDimension = 256 * numberOfCell_X*numberOfCell_Y;
    featureVector.create(1, numberOfDimension, CV_32FC1);
    featureVector.setTo(Scalar(0));

    // 計算LBP特征向量
    int stepOfCell = srcImage.cols;
    int pixelCount = cellSize.width*cellSize.height;
    float *dataOfFeatureVector = (float *)featureVector.data;

    // cell的特征向量在最終特征向量中的起始位置
    int index = -256;
    for (int y = 0; y <= numberOfCell_Y - 1; ++y)
    {
        for (int x = 0; x <= numberOfCell_X - 1; ++x)
        {
            index += 256;
            // 計算每個cell的LBP直方圖
            Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
            uchar *rowOfCell = cell.data;
            for (int y_Cell = 0; y_Cell <= cell.rows - 1; ++y_Cell, rowOfCell += stepOfCell)
            {
                uchar *colOfCell = rowOfCell;
                for (int x_Cell = 0; x_Cell <= cell.cols - 1; ++x_Cell, ++colOfCell)
                {
                    ++dataOfFeatureVector[index + colOfCell[0]];
                }
            }

            // 一定要歸一化!否則分類器計算誤差很大
            for (int i = 0; i <= 255; ++i)
                dataOfFeatureVector[index + i] /= pixelCount;
        }
    }
}
// 等價灰度不變LBP(58)特征
void UniformNormalLBPFeature(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
    // 參數(shù)檢查和措,內(nèi)存分配
    CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);

    Mat LBPImage;
    UniformNormalLBPImage(srcImage, LBPImage);

    // 計算cell個數(shù)
    int widthOfCell = cellSize.width;
    int heightOfCell = cellSize.height;
    int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的個數(shù)
    int numberOfCell_Y = srcImage.rows / heightOfCell;

    // 特征向量的個數(shù)
    int numberOfDimension = 58 * numberOfCell_X*numberOfCell_Y;
    featureVector.create(1, numberOfDimension, CV_32FC1);
    featureVector.setTo(Scalar(0));

    // 計算LBP特征向量
    int stepOfCell = srcImage.cols;
    int index = -58;// cell的特征向量在最終特征向量中的起始位置
    float *dataOfFeatureVector = (float *)featureVector.data;
    for (int y = 0; y <= numberOfCell_Y - 1; ++y)
    {
        for (int x = 0; x <= numberOfCell_X - 1; ++x)
        {
            index += 58;

            // 計算每個cell的LBP直方圖
            Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
            uchar *rowOfCell = cell.data;
            int sum = 0; // 每個cell的等價模式總數(shù)
            for (int y_Cell = 0; y_Cell <= cell.rows - 1; ++y_Cell, rowOfCell += stepOfCell)
            {
                uchar *colOfCell = rowOfCell;
                for (int x_Cell = 0; x_Cell <= cell.cols - 1; ++x_Cell, ++colOfCell)
                {
                    if (colOfCell[0] != 0)
                    {
                        // 在直方圖中轉(zhuǎn)化為0~57庄呈,所以是colOfCell[0] - 1
                        ++dataOfFeatureVector[index + colOfCell[0] - 1];
                        ++sum;
                    }
                }
            }
            // 一定要歸一化蜕煌!否則分類器計算誤差很大
            for (int i = 0; i <= 57; ++i)
                dataOfFeatureVector[index + i] /= sum;
        }
    }
}
// 等價旋轉(zhuǎn)不變LBP(9)特征
void UniformRotInvLBPFeature(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
    // 參數(shù)檢查派阱,內(nèi)存分配
    CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);

    Mat LBPImage;
    UniformRotInvLBPImage(srcImage, LBPImage);

    // 計算cell個數(shù)
    int widthOfCell = cellSize.width;
    int heightOfCell = cellSize.height;
    int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的個數(shù)
    int numberOfCell_Y = srcImage.rows / heightOfCell;

    // 特征向量的個數(shù)
    int numberOfDimension = 9 * numberOfCell_X*numberOfCell_Y;
    featureVector.create(1, numberOfDimension, CV_32FC1);
    featureVector.setTo(Scalar(0));

    // 計算LBP特征向量
    int stepOfCell = srcImage.cols;
    int index = -9;// cell的特征向量在最終特征向量中的起始位置
    float *dataOfFeatureVector = (float *)featureVector.data;
    for (int y = 0; y <= numberOfCell_Y - 1; ++y)
    {
        for (int x = 0; x <= numberOfCell_X - 1; ++x)
        {
            index += 9;

            // 計算每個cell的LBP直方圖
            Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
            uchar *rowOfCell = cell.data;
            int sum = 0; // 每個cell的等價模式總數(shù)
            for (int y_Cell = 0; y_Cell <= cell.rows - 1; ++y_Cell, rowOfCell += stepOfCell)
            {
                uchar *colOfCell = rowOfCell;
                for (int x_Cell = 0; x_Cell <= cell.cols - 1; ++x_Cell, ++colOfCell)
                {
                    if (colOfCell[0] != 0)
                    {
                        // 在直方圖中轉(zhuǎn)化為0~8,所以是colOfCell[0] - 1
                        ++dataOfFeatureVector[index + colOfCell[0] - 1];
                        ++sum;
                    }
                }
            }
            // 直方圖歸一化
            for (int i = 0; i <= 8; ++i)
                dataOfFeatureVector[index + i] /= sum;
        }
    }
}
image.png

圖 2.4.1 原圖

image.png

圖 2.4.2 灰度不變常規(guī)LBP

image.png

圖 2.4.3 等價灰度不變LBP

image.png

圖 2.4.4 等價旋轉(zhuǎn)不變LBP

2.5 總結(jié)

LBP曾廣泛應(yīng)用于人臉檢測及人臉識別應(yīng)用中斜纪,但在深度學(xué)習與卷積神將網(wǎng)絡(luò)迅猛發(fā)展的今天贫母,以LBP為特征的檢測及識別算法并不具有競爭力文兑,但是作為學(xué)習案例還是很有借鑒意義的,后續(xù)也會陸續(xù)寫一些基于深度學(xué)習的人臉檢測腺劣、人臉識別算法的博客绿贞,可以繼續(xù)關(guān)注。


Task02 LBP特征描述算子-人臉檢測 END ——By:Aaron

博客:Aaron的博客 GitHub:Aaron_Sandy

關(guān)于Datawhale:

Datawhale是一個專注于數(shù)據(jù)科學(xué)與AI領(lǐng)域的開源組織橘原,匯集了眾多領(lǐng)域院校和知名企業(yè)的優(yōu)秀學(xué)習者籍铁,聚合了一群有開源精神和探索精神的團隊成員。Datawhale以“for the learner趾断,和學(xué)習者一起成長”為愿景拒名,鼓勵真實地展現(xiàn)自我、開放包容芋酌、互信互助增显、敢于試錯和勇于擔當。同時Datawhale 用開源的理念去探索開源內(nèi)容脐帝、開源學(xué)習和開源方案同云,賦能人才培養(yǎng),助力人才成長堵腹,建立起人與人炸站,人與知識,人與企業(yè)和人與未來的聯(lián)結(jié)秸滴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末武契,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子荡含,更是在濱河造成了極大的恐慌咒唆,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件释液,死亡現(xiàn)場離奇詭異全释,居然都是意外死亡,警方通過查閱死者的電腦和手機误债,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門浸船,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人寝蹈,你說我怎么就攤上這事李命。” “怎么了箫老?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵封字,是天一觀的道長。 經(jīng)常有香客問我,道長阔籽,這世上最難降的妖魔是什么流妻? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮笆制,結(jié)果婚禮上绅这,老公的妹妹穿的比我還像新娘。我一直安慰自己在辆,他們只是感情好证薇,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著匆篓,像睡著了一般棕叫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奕删,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天俺泣,我揣著相機與錄音,去河邊找鬼完残。 笑死伏钠,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谨设。 我是一名探鬼主播熟掂,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扎拣!你這毒婦竟也來了赴肚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤二蓝,失蹤者是張志新(化名)和其女友劉穎誉券,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刊愚,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡踊跟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸥诽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片商玫。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牡借,靈堂內(nèi)的尸體忽然破棺而出拳昌,到底是詐尸還是另有隱情,我是刑警寧澤钠龙,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布炬藤,位于F島的核電站扁远,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏刻像。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一并闲、第九天 我趴在偏房一處隱蔽的房頂上張望细睡。 院中可真熱鬧,春花似錦帝火、人聲如沸溜徙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蠢壹。三九已至,卻和暖如春九巡,著一層夾襖步出監(jiān)牢的瞬間图贸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工冕广, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留疏日,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓撒汉,卻偏偏與公主長得像沟优,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子睬辐,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359