LBP特徵 旋轉不變 標準型 圓型 及其在人臉識別中的應用含程式碼
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;
}