特征提取之LBP特征
局部二值模式(Local Binary Pattern,LBP)是一种描述图像纹理特征的算子,它具有旋转和灰度不变性。一般不将LBP图谱作为特征向量用于分类识别,而是采用LBP特征值谱的统计直方图作为特征向量用于分类识别。
1.LBP特征算子
1.1原始LBP
原始LBP是在3*3的窗口内,以窗口中心元素为阈值,比较周围8个像素,若大于中心像素点,则标记为1,否则为0。然后这8个点就可以产生一个8位的二进制数(共有2^8=256种),转换为10进制数后,这个值就用来代替像素中心值。
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void LBP(IplImage* src, IplImage* dst)//原始LBP
{
int width = src->width;
int height = src->height;
for (int j = 1; j<width - 1; j++)
{
for (int i = 1; i<height - 1; i++)
{
uchar neighborhood[8] = { 0 }; //uchar(8位无符号整形数据,范围0~255)
neighborhood[7] = CV_IMAGE_ELEM(src, uchar, i - 1, j - 1); //获取特定点的像素
neighborhood[6] = CV_IMAGE_ELEM(src, uchar, i - 1, j);
neighborhood[5] = CV_IMAGE_ELEM(src, uchar, i - 1, j + 1);
neighborhood[4] = CV_IMAGE_ELEM(src, uchar, i, j - 1);
neighborhood[3] = CV_IMAGE_ELEM(src, uchar, i, j + 1);
neighborhood[2] = CV_IMAGE_ELEM(src, uchar, i + 1, j - 1);
neighborhood[1] = CV_IMAGE_ELEM(src, uchar, i + 1, j);
neighborhood[0] = CV_IMAGE_ELEM(src, uchar, i + 1, j + 1);
uchar center = CV_IMAGE_ELEM(src, uchar, i, j); //获取中心点的像素值
uchar temp = 0;
for (int k = 0; k<8; k++)
{
temp += (neighborhood[k] >= center) << k; //如果大于中心值则为1,否则为0
}
CV_IMAGE_ELEM(dst, uchar, i, j) = temp; //将最后的LBP值赋值给中心像素
}
}
}
int main()
{
IplImage* img = cvLoadImage("D:/img/1.jpg", 0); //IplImage* 图像底层指针
IplImage* dst = cvCreateImage(cvGetSize(img), 8, 1);
LBP(img, dst);
Mat dst1(dst, 0); //IplImage类型转换Mat类型
namedWindow("dst");
imshow("dst", dst1);
waitKey(0);
return 0;
}
1.2圆形LBP算子
由于原始LBP只是覆盖了一个固定半径范围内的小区域,不能满足不同尺寸和频率纹理的需要。对此,将3*3领域扩展到任意领域,用圆形领域代替正方形领域。
1.3旋转不变LBP
旋转不变LBP即是在8种不同LBP模式下(按位平移,最高位移动到最低位),取其最小值。
1.4均匀LBP(等价模式)
由于一个LBP算子可以产生不同的二进制模式,过多的模式种类对于纹理的识别、分类和信息的存取是不利的。均匀LBP就是一个二进制序列从0到1或者从1到0的变化不超过2次。将二进制序列首尾相连,总共有59种(变化次数为0的有2种,1的有0种,2的有56种,其他为1种)。因为发现计算出来的大部分值在58种之中,可达到90%以上,所以把值分为了59个,58个不超过2次的为一类,另外其他的为一类。这样就从原来的256维降到了58维。
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int getHopCount(uchar i)
{
int a[8] = { 0 };
int k = 7;
int cnt = 0;
while (i)
{
a[k] = i & 1;
i >>= 1;
--k;
}
for (int k = 0; k<8; ++k)
{
if (a[k] != a[k + 1 == 8 ? 0 : k + 1])
{
++cnt;
}
}
return cnt;
}
void lbp59table(uchar* table)
{
memset(table, 0, 256);
uchar temp = 1;
for (int i = 0; i<256; ++i)
{
if (getHopCount(i) <= 2)
{
table[i] = temp;
temp++;
}
}
}
void ULBP(IplImage* src, IplImage* dst)//均匀LBP
{
int width = src->width;
int height = src->height;
uchar table[256];
lbp59table(table);
for (int j = 1; j<width - 1; j++)
{
for (int i = 1; i<height - 1; i++)
{
uchar neighborhood[8] = { 0 };
neighborhood[7] = CV_IMAGE_ELEM(src, uchar, i - 1, j - 1);
neighborhood[6] = CV_IMAGE_ELEM(src, uchar, i - 1, j);
neighborhood[5] = CV_IMAGE_ELEM(src, uchar, i - 1, j + 1);
neighborhood[4] = CV_IMAGE_ELEM(src, uchar, i, j + 1);
neighborhood[3] = CV_IMAGE_ELEM(src, uchar, i + 1, j + 1);
neighborhood[2] = CV_IMAGE_ELEM(src, uchar, i + 1, j);
neighborhood[1] = CV_IMAGE_ELEM(src, uchar, i + 1, j - 1);
neighborhood[0] = CV_IMAGE_ELEM(src, uchar, i, j - 1);
uchar center = CV_IMAGE_ELEM(src, uchar, i, j);
uchar temp = 0;
for (int k = 0; k<8; k++)
{
temp += (neighborhood[k] >= center) << k;
}
CV_IMAGE_ELEM(dst, uchar, i, j) = table[temp];
}
}
}
int main()
{
IplImage* img = cvLoadImage("D:/img/1.jpg", 0); //IplImage* 图像底层指针
IplImage* dst = cvCreateImage(cvGetSize(img), 8, 1);
ULBP(img, dst);
Mat dst1(dst, 0); //IplImage类型转换Mat类型
namedWindow("dst");
imshow("dst", dst1);
waitKey(0);
return 0;
}
2.对LBP特征向量进行提取的步骤
(1)将检测窗口划分为16*16的小区域(cell)。
(2)对于每个cell中的一个像素,计算得到它的LBP值。
(3)计算每个cell的直方图,即每个LBP值出现的频率,然后归一化。
(4)统计每个cell的直方图,连接成一个向量。