LBP特征检测
LBP特征
LBP(Local Binary Pattern,局部二值模式)是一種用來描述圖像局部紋理特征的算子;它具有旋轉(zhuǎn)不變性和灰度不變性等顯著的優(yōu)點,用于紋理特征提取。而且,提取的特征是圖像的局部的紋理特征;
?
1、LBP特征的描述
原始的LBP算子定義為在3*3的窗口內(nèi),以窗口中心像素為閾值,將相鄰的8個像素的灰度值與其進(jìn)行比較,若周圍像素值大于中心像素值,則該像素點的位置被標(biāo)記為1,否則為0。這樣,3*3鄰域內(nèi)的8個點經(jīng)比較可產(chǎn)生8位二進(jìn)制數(shù)(通常轉(zhuǎn)換為十進(jìn)制數(shù)即LBP碼,共256種),即得到該窗口中心像素點的LBP值,并用這個值來反映該區(qū)域的紋理信息。如下圖所示:
LBP的改進(jìn)版本:
原始的LBP提出后,研究人員不斷對其提出了各種改進(jìn)和優(yōu)化。
(1)圓形LBP算子:
基本的 LBP算子的最大缺陷在于它只覆蓋了一個固定半徑范圍內(nèi)的小區(qū)域,這顯然不能滿足不同尺寸和頻率紋理的需要。為了適應(yīng)不同尺度的紋理特征,并達(dá)到灰度和旋轉(zhuǎn)不變性的要求,Ojala等對 LBP 算子進(jìn)行了改進(jìn),將 3×3鄰域擴(kuò)展到任意鄰域,并用圓形鄰域代替了正方形鄰域,改進(jìn)后的 LBP 算子允許在半徑為 R 的圓形鄰域內(nèi)有任意多個像素點。從而得到了諸如半徑為R的圓形區(qū)域內(nèi)含有P個采樣點的LBP算子;
(2)LBP旋轉(zhuǎn)不變模式
從 LBP 的定義可以看出,LBP 算子是灰度不變的,但卻不是旋轉(zhuǎn)不變的。圖像的旋轉(zhuǎn)就會得到不同的 LBP值。
Maenpaa等人又將 LBP算子進(jìn)行了擴(kuò)展,提出了具有旋轉(zhuǎn)不變性的 LBP 算子,即不斷旋轉(zhuǎn)圓形鄰域得到一系列初始定義的 LBP值,取其最小值作為該鄰域的 LBP 值。
圖 2.5 給出了求取旋轉(zhuǎn)不變的 LBP 的過程示意圖,圖中算子下方的數(shù)字表示該算子對應(yīng)的 LBP值,圖中所示的 8 種 LBP模式,經(jīng)過旋轉(zhuǎn)不變的處理,最終得到的具有旋轉(zhuǎn)不變性的 LBP值為 15。也就是說,圖中的 8種 LBP 模式對應(yīng)的旋轉(zhuǎn)不變的 LBP模式都是00001111。
(3)LBP等價模式
一個LBP算子可以產(chǎn)生不同的二進(jìn)制模式,對于半徑為R的圓形區(qū)域內(nèi)含有P個采樣點的LBP算子將會產(chǎn)生P2種模式。很顯然,隨著鄰域集內(nèi)采樣點數(shù)的增加,二進(jìn)制模式的種類是急劇增加的。例如:5×5鄰域內(nèi)20個采樣點,有220=1,048,576種二進(jìn)制模式。如此多的二值模式無論對于紋理的提取還是對于紋理的識別、分類及信息的存取都是不利的。同時,過多的模式種類對于紋理的表達(dá)是不利的。例如,將LBP算子用于紋理分類或人臉識別時,常采用LBP模式的統(tǒng)計直方圖來表達(dá)圖像的信息,而較多的模式種類將使得數(shù)據(jù)量過大,且直方圖過于稀疏。因此,需要對原始的LBP模式進(jìn)行降維,使得數(shù)據(jù)量減少的情況下能最好的代表圖像的信息。
為了解決二進(jìn)制模式過多的問題,提高統(tǒng)計性,Ojala提出了采用一種“等價模式”(Uniform Pattern)來對LBP算子的模式種類進(jìn)行降維。Ojala等認(rèn)為,在實際圖像中,絕大多數(shù)LBP模式最多只包含兩次從1到0或從0到1的跳變。因此,Ojala將“等價模式”定義為:當(dāng)某個LBP所對應(yīng)的循環(huán)二進(jìn)制數(shù)從0到1或從1到0最多有兩次跳變時,該LBP所對應(yīng)的二進(jìn)制就稱為一個等價模式類。如00000000(0次跳變),00000111(只含一次從0到1的跳變),10001111(先由1跳到0,再由0跳到1,共兩次跳變)都是等價模式類。除等價模式類以外的模式都?xì)w為另一類,稱為混合模式類,例如10010111(共四次跳變)(這是我的個人理解,不知道對不對)。
通過這樣的改進(jìn),二進(jìn)制模式的種類大大減少,而不會丟失任何信息。模式數(shù)量由原來的2P種減少為 P ( P-1)+2種,其中P表示鄰域集內(nèi)的采樣點數(shù)。對于3×3鄰域內(nèi)8個采樣點來說,二進(jìn)制模式由原始的256種減少為58種,這使得特征向量的維數(shù)更少,并且可以減少高頻噪聲帶來的影響。
?
2、LBP特征用于檢測的原理
顯而易見的是,上述提取的LBP算子在每個像素點都可以得到一個LBP“編碼”,那么,對一幅圖像(記錄的是每個像素點的灰度值)提取其原始的LBP算子之后,得到的原始LBP特征依然是“一幅圖片”(記錄的是每個像素點的LBP值)。
LBP的應(yīng)用中,如紋理分類、人臉分析等,一般都不將LBP圖譜作為特征向量用于分類識別,而是采用LBP特征譜的統(tǒng)計直方圖作為特征向量用于分類識別。
因為,從上面的分析我們可以看出,這個“特征”跟位置信息是緊密相關(guān)的。直接對兩幅圖片提取這種“特征”,并進(jìn)行判別分析的話,會因為“位置沒有對準(zhǔn)”而產(chǎn)生很大的誤差。后來,研究人員發(fā)現(xiàn),可以將一幅圖片劃分為若干的子區(qū)域,對每個子區(qū)域內(nèi)的每個像素點都提取LBP特征,然后,在每個子區(qū)域內(nèi)建立LBP特征的統(tǒng)計直方圖。如此一來,每個子區(qū)域,就可以用一個統(tǒng)計直方圖來進(jìn)行描述;整個圖片就由若干個統(tǒng)計直方圖組成;
例如:一幅100*100像素大小的圖片,劃分為10*10=100個子區(qū)域(可以通過多種方式來劃分區(qū)域),每個子區(qū)域的大小為10*10像素;在每個子區(qū)域內(nèi)的每個像素點,提取其LBP特征,然后,建立統(tǒng)計直方圖;這樣,這幅圖片就有10*10個子區(qū)域,也就有了10*10個統(tǒng)計直方圖,利用這10*10個統(tǒng)計直方圖,就可以描述這幅圖片了。之后,我們利用各種相似性度量函數(shù),就可以判斷兩幅圖像之間的相似性了;
?
3、對LBP特征向量進(jìn)行提取的步驟
(1)首先將檢測窗口劃分為16×16的小區(qū)域(cell);
(2)對于每個cell中的一個像素,將相鄰的8個像素的灰度值與其進(jìn)行比較,若周圍像素值大于中心像素值,則該像素點的位置被標(biāo)記為1,否則為0。這樣,3*3鄰域內(nèi)的8個點經(jīng)比較可產(chǎn)生8位二進(jìn)制數(shù),即得到該窗口中心像素點的LBP值;
(3)然后計算每個cell的直方圖,即每個數(shù)字(假定是十進(jìn)制數(shù)LBP值)出現(xiàn)的頻率;然后對該直方圖進(jìn)行歸一化處理。
(4)最后將得到的每個cell的統(tǒng)計直方圖進(jìn)行連接成為一個特征向量,也就是整幅圖的LBP紋理特征向量;
然后便可利用SVM或者其他機(jī)器學(xué)習(xí)算法進(jìn)行分類了。
?
LBP默認(rèn)3*3算子 代碼示例:
#include <opencv2/opencv.hpp> #include <iostream>using namespace cv; using namespace std;Mat src, gray_src; int current_radius = 3; int max_count = 20; void ELBP_Demo(int, void*); int main(int argc, char** argv) {src = imread("D:/cv400/data/lena.jpg");if (src.empty()) {printf("could not load image...\n");return -1;}imshow("input image", src);// convert to graycvtColor(src, gray_src, COLOR_BGR2GRAY);int width = gray_src.cols;int height = gray_src.rows;// 基本LBP演示,注意圖片大小:3*3的算子,除去邊框Mat lbpImage = Mat::zeros(gray_src.rows - 2, gray_src.cols - 2, CV_8UC1);for (int row = 1; row < height - 1; row++) {for (int col = 1; col < width - 1; col++) {uchar c = gray_src.at<uchar>(row, col);uchar code = 0;code |= (gray_src.at<uchar>(row - 1, col - 1) > c) << 7;code |= (gray_src.at<uchar>(row - 1, col) > c) << 6;code |= (gray_src.at<uchar>(row - 1, col + 1) > c) << 5;code |= (gray_src.at<uchar>(row, col + 1) > c) << 4;code |= (gray_src.at<uchar>(row + 1, col + 1) > c) << 3;code |= (gray_src.at<uchar>(row + 1, col) > c) << 2;code |= (gray_src.at<uchar>(row + 1, col - 1) > c) << 1;code |= (gray_src.at<uchar>(row, col - 1) > c) << 0;lbpImage.at<uchar>(row - 1, col - 1) = code;}}imshow("LBP Result", lbpImage);waitKey(0);return 0; }運行截圖:
?
LBP自定義算子大小,代碼示例:
#include <opencv2/opencv.hpp> #include <iostream> #include "math.h"using namespace cv; using namespace std;Mat src, gray_src; int current_radius = 3; //算子大小 int max_count = 20; void ELBP_Demo(int, void*); int main(int argc, char** argv) {src = imread("D:/cv400/data/lena.jpg");if (src.empty()) {printf("could not load image...\n");return -1;}imshow("input image", src);// convert to graycvtColor(src, gray_src, COLOR_BGR2GRAY);int width = gray_src.cols;int height = gray_src.rows;// 自定義LBP算子大小namedWindow("ELBP Result", WINDOW_AUTOSIZE);createTrackbar("ELBP Radius:", "ELBP Result", ¤t_radius, max_count, ELBP_Demo);ELBP_Demo(0, 0);waitKey(0);return 0; }void ELBP_Demo(int, void*) {int offset = current_radius * 2;Mat elbpImage = Mat::zeros(gray_src.rows - offset, gray_src.cols - offset, CV_8UC1);int width = gray_src.cols;int height = gray_src.rows;int numNeighbors = 8;for (int n = 0; n < numNeighbors; n++) {float x = static_cast<float>(current_radius) * cos(2.0 * CV_PI*n / static_cast<float>(numNeighbors));float y = static_cast<float>(current_radius) * -sin(2.0 * CV_PI*n / static_cast<float>(numNeighbors));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 row = current_radius; row < (height - current_radius); row++){for (int col = current_radius; col < (width - current_radius); col++){float t = w1* gray_src.at<uchar>(row + fy, col + fx) + w2* gray_src.at<uchar>(row + fy, col + cx) +w3* gray_src.at<uchar>(row + cy, col + fx) + w4* gray_src.at<uchar>(row + cy, col + cx);elbpImage.at<uchar>(row - current_radius, col - current_radius) +=((t > gray_src.at<uchar>(row, col)) && (abs(t - gray_src.at<uchar>(row, col)) > std::numeric_limits<float>::epsilon())) << n;}}}imshow("ELBP Result", elbpImage);return; }?運行截圖:
總結(jié)
- 上一篇: MIKE水动力笔记3_岸线及水深数据之根
- 下一篇: 仪表盘故障图像识别_一目了然:汽车仪表盘