LBP(Local Binary Patterns,局部二值模式)是一種能夠有效地度量和提取圖像局部紋理信息的算子,具有旋轉不變性和灰度不變性等顯著的優點。它是人臉識別中一種提取特徵的重要方法,具有對光照不敏感的特性,但是對姿態和表情的魯棒性不強。
1、背景及理論基礎
人臉識別是指將一個需要識別的人臉和人臉庫中的某個人臉對應起來(類似於指紋識別),目的是完成識別功能,該術語需要和人臉檢測進行區分,人臉檢測是在一張圖片中把人臉定位出來,完成的是搜尋的功能。從OpenCV2.4開始,加入了新的類FaceRecognizer,該類用於人臉識別,使用它可以方便地進行相關識別實驗。
2、原始LBP
原始的LBP算子定義爲在3*3的窗口內,以窗口中心像素爲閾值,將相鄰的8個像素的灰度值與其進行比較,若周圍像素值大於或等於中心像素值,則該像素點的位置被標記爲1,否則爲0。這樣,3*3鄰域內的8個點經比較可產生8位二進制數(通常轉換爲十進制數即LBP碼,共256種),即得到該窗口中心像素點的LBP值,並用這個值來反映該區域的紋理特徵。
3、圓形LBP
基本的 LBP算子的最大缺陷在於它只覆蓋了一個固定半徑範圍內的小區域,這顯然不能滿足不同尺寸和頻率紋理的需要。爲了適應不同尺度的紋理特徵,Ojala等對LBP算子進行了改進,將3×3鄰域擴展到任意鄰域,並用圓形鄰域代替了正方形鄰域,改進後的LBP算子允許在半徑爲R的圓形鄰域內有任意多個像素點,從而得到了諸如半徑爲R的圓形區域內含有P個採樣點的LBP算子,OpenCV中正是使用圓形LBP算子,下圖示意了圓形LBP算子:
4、旋轉不變模式
從LBP的定義可以看出,LBP算子是灰度不變的,但卻不是旋轉不變的,圖像的旋轉就會得到不同的LBP值。Maenpaa等人又將LBP算子進行了擴展,提出了具有旋轉不變性的LBP算子,即不斷旋轉圓形鄰域得到一系列初始定義的LBP值,取其最小值作爲該鄰域的LBP值。下圖給出了求取旋轉不變LBP的過程示意圖,圖中算子下方的數字表示該算子對應的LBP值,圖中所示的8種LBP模式,經過旋轉不變的處理,最終得到的具有旋轉不變性的LBP值爲15。也就是說,圖中的8種LBP模式對應的旋轉不變的LBP碼值都是00001111。
一共36個旋轉不變的LBP編碼模式,如下圖所示:
5、LBP等價模式
原始的LBP算子,隨着鄰域內採樣點數的增加,二進制模式的種類是急劇增加的。
對於半徑爲R的圓形區域內含有P個採樣點的LBP算子將會產P^2中模式,如5X5領域內20個採樣點,有2^20=104857種二進制模式。過多的二值模式對於特徵的提取以及信息的存取都是不利的。例如,將LBP算子用於人臉識別時,常採用的LBP模式的統計直方圖來表達人臉信息,而較多的模式種類將使得數據量過大,且直方圖過於稀疏。因此,需要對原始LBP模式進行降維,使得數據量減少的情況下能最好的代表圖像的信息。
旋轉LBP模式同樣存在缺陷,大量的實驗證明LBP模式的36種情況在一幅圖像中分佈出現的頻率差異較大,得到的效果不是很好。因此人們提出了uniform LBP。
“等價模式”被定義爲:當某個LBP所對應的循環二進制數從0到1或者從1到0最多有兩次跳變時,該LBP所對應的二進制就稱爲一個等價模式。在實際圖像中,計算出來的大部分值都在等價模式之中,可達百分之90%以上。uniformLBP模式的個數爲P(P-1)+2,P爲領域像素點個數。對於8個採樣點,uniform形式有58種輸出, 其他的所有值爲第59類,這樣直方圖從原來的256維降到了59維,並且可以減少高頻噪聲帶來的影響。
uniform形式的58種LBP模式如下圖所示:
6、LBP在人臉識別中的應用
將LBP用於人臉識別時,一般不將LBP圖作爲特徵用於識別,而是統計LBP特徵圖的直方圖作爲特徵向量用於分類識別。
但是如果直接統計兩張完整圖片的LBP直方圖進行分類識別的話,一但人臉位置沒有對準,識別效果會很差。所以,LBP人臉識別的一般做法是將人臉圖像進行分塊,對每塊子圖像進行LBP直方圖統計,並將所以塊的直方圖首尾相連組成一個向量,這個向量即是人臉的特徵描述。通過比較兩張人臉圖像的統計直方圖特徵向量的相似度,即可實現人臉識別。
人臉分塊示意圖:
統計直方圖特徵向量相似度的計算公式:
對LBP特徵向量進行提取的一般步驟:
1. 首先將一張圖片分成若干個子塊圖片區域(cell)
2. 對於每個cell中的一個像素,將相鄰的8個像素的灰度值與其進行比較,若周圍像素值大於中心像素值,則該像素點的位置被標記爲1,否則爲0。這樣,3*3鄰域內的8個點經比較可產生8位二進制數,即得到該窗口中心像素點的LBP值
3. 然後計算每個cell的直方圖,即每個數字(假定是十進制數LBP值)出現的頻率;然後對該直方圖進行歸一化處理
4. 最後將得到的每個cell的統計直方圖進行連接成爲一個特徵向量,也就是整幅圖的LBP紋理特徵向量
5. 通過一定的方法比較兩張圖片的LBP特徵向量的相似度來實現人臉識別
#include<opencv2/highgui/highgui.hpp>
using namespace cv;
//原始LBP
Mat LBP(Mat img)
{
Mat result;
result.create(img.rows - 2, img.cols - 2, img.type());
result.setTo(0);
for (int i = 1; i<img.rows - 1; i++)
{
for (int j = 1; j<img.cols - 1; j++)
{
uchar center = img.at<uchar>(i, j);
uchar code = 0;
code |= (img.at<uchar>(i - 1, j - 1) >= center) << 7;
code |= (img.at<uchar>(i - 1, j) >= center) << 6;
code |= (img.at<uchar>(i - 1, j + 1) >= center) << 5;
code |= (img.at<uchar>(i, j + 1) >= center) << 4;
code |= (img.at<uchar>(i + 1, j + 1) >= center) << 3;
code |= (img.at<uchar>(i + 1, j) >= center) << 2;
code |= (img.at<uchar>(i + 1, j - 1) >= center) << 1;
code |= (img.at<uchar>(i, j - 1) >= center) << 0;
result.at<uchar>(i - 1, j - 1) = code;
}
}
return result;
}
//圓形LBP
Mat ELBP(Mat img, int radius, int neighbors)
{
Mat result;
result.create(img.rows - radius * 2, img.cols - radius * 2, img.type());
result.setTo(0);
for (int n = 0; n<neighbors; n++)
{
// sample points
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)));
// relative indices
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));
// fractional part
float ty = y - fy;
float tx = x - fx;
// set interpolation weights
float w1 = (1 - tx) * (1 - ty);
float w2 = tx * (1 - ty);
float w3 = (1 - tx) * ty;
float w4 = tx * ty;
// iterate through your data
for (int i = radius; i < img.rows - radius; i++)
{
for (int j = radius; j < img.cols - radius; j++)
{
// calculate interpolated value
float t = static_cast<float>(w1*img.at<uchar>(i + fy, j + fx) + w2*img.at<uchar>(i + fy, j + cx) + w3*img.at<uchar>(i + cy, j + fx) + w4*img.at<uchar>(i + cy, j + cx));
// floating point precision, so check some machine-dependent epsilon
result.at<uchar>(i - radius, j - radius) += ((t > img.at<uchar>(i, j)) || (std::abs(t - img.at<uchar>(i, j)) < std::numeric_limits<float>::epsilon())) << n;
}
}
}
return result;
}
//八位二進制跳變次數
int getHopCount(uchar i)
{
uchar a[8] = { 0 };
int cnt = 0;
int k = 7;
while (k)
{
a[k] = i & 1;
i = i >> 1;
--k;
}
for (int k = 0; k<7; k++)
{
if (a[k] != a[k + 1])
++cnt;
}
if (a[0] != a[7])
++cnt;
return cnt;
}
//旋轉不變LBP
Mat RILBP(Mat img)
{
uchar RITable[256];
int temp;
int val;
Mat result;
result.create(img.rows - 2, img.cols - 2, img.type());
result.setTo(0);
for (int i = 0; i<256; i++)
{
val = i;
for (int j = 0; j<7; j++)
{
temp = i >> 1;
if (val>temp)
{
val = temp;
}
}
RITable[i] = val;
}
for (int i = 1; i<img.rows - 1; i++)
{
for (int j = 1; j<img.cols - 1; j++)
{
uchar center = img.at<uchar>(i, j);
uchar code = 0;
code |= (img.at<uchar>(i - 1, j - 1) >= center) << 7;
code |= (img.at<uchar>(i - 1, j) >= center) << 6;
code |= (img.at<uchar>(i - 1, j + 1) >= center) << 5;
code |= (img.at<uchar>(i, j + 1) >= center) << 4;
code |= (img.at<uchar>(i + 1, j + 1) >= center) << 3;
code |= (img.at<uchar>(i + 1, j) >= center) << 2;
code |= (img.at<uchar>(i + 1, j - 1) >= center) << 1;
code |= (img.at<uchar>(i, j - 1) >= center) << 0;
result.at<uchar>(i - 1, j - 1) = RITable[code];
}
}
return result;
}
//UniformLBP
Mat UniformLBP(Mat img)
{
uchar UTable[256];
memset(UTable, 0, 256 * sizeof(uchar));
uchar temp = 1;
for (int i = 0; i<256; i++)
{
if (getHopCount(i) <= 2)
{
UTable[i] = temp;
++temp;
}
}
Mat result;
result.create(img.rows - 2, img.cols - 2, img.type());
result.setTo(0);
for (int i = 1; i<img.rows - 1; i++)
{
for (int j = 1; j<img.cols - 1; j++)
{
uchar center = img.at<uchar>(i, j);
uchar code = 0;
code |= (img.at<uchar>(i - 1, j - 1) >= center) << 7;
code |= (img.at<uchar>(i - 1, j) >= center) << 6;
code |= (img.at<uchar>(i - 1, j + 1) >= center) << 5;
code |= (img.at<uchar>(i, j + 1) >= center) << 4;
code |= (img.at<uchar>(i + 1, j + 1) >= center) << 3;
code |= (img.at<uchar>(i + 1, j) >= center) << 2;
code |= (img.at<uchar>(i + 1, j - 1) >= center) << 1;
code |= (img.at<uchar>(i, j - 1) >= center) << 0;
result.at<uchar>(i - 1, j - 1) = UTable[code];
}
}
return result;
}
int main()
{
Mat src = imread("1.jpg", 0);
Mat dst = LBP(src);
Mat edst = ELBP(src, 1, 8);
Mat pic = RILBP(src);
Mat img = UniformLBP(src);
imshow("原始圖片", src);
imshow("原始LBP", dst);
imshow("圓形LBP", edst);
imshow("旋轉不變LBP", pic);
imshow("UniformLBP", img);
waitKey();
return 0;
}