LBP指局部二值模式,英文全称:Local Binary Pattern,是一种用来描述图像局部特征的,LBP特征具有灰度不变性和旋转不变性等显著优点。同时是一种描述图像特征像素点与各个像素点之间的灰度关系的局部特征的非参数算法,同时也是一张高效的纹理描述算法。
纹理是物体表面的自然特性,它描述图像像素点与图像领域之间的灰度空间的分布关系,不会因光照强弱而改变图像的视觉变化。由于LBP特征计算简单、效果较好,因此LBP特征在计算机视觉的许多领域都得到了广泛的应用,LBP特征比较出名的应用是用在人脸识别和目标检测中。
=========================================================================
最原始的LBP算法一般被定义为一个3×3的纹理单元,如图1,其中间的像素点灰度值为Pc,周围相邻的8个像素点灰度值分别为P0,P1,P2,P3,P4,P5,P6,P7,以该纹理单位的中心像素点的灰度值Pc为阈值,若其余相邻的8个像素点的灰度值大于中心像素点灰度值的阈值,则该像素点的编码值为1,小于阈值的时候取编码值为0。
为了得到各个像素点的编码值,则为每个S(Pi-Pc)分配权值,然后将编码值连接成一个二进制值,然后再转化成一个十进制的值,然后用现在的十进制值代替原来中心点的阈值,再对这个十进制进行计算,计算出的结果就是LBP算法的值。
对于一个3×3的纹理单元,LBP的数学计算过程为:
其中为算子中心元素的坐标,p表示周围元素的个数。例如,下图中经过对图片进行LBP编码复制后,Binary = 00010011,于是转变为十进制后局部二值模式取值为19 。
通过以上LBP算法的计算例子可以看出LBP值仅由中心像素点灰度值和相邻八个像素点的灰度值决定,LBP特征即表示了其中心像素点的特征,其值的计算还需要和周围的像素点计算完成,所以,LBP特征与周围有很大的关系,它包含了图像点,又包含了边缘和局部的特征分布信息。
代码演示
#include"stdafx.h"
#include
#include
#include "math.h"
#include
#include
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("F:/photo/i.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
const char* output_tt = "LBP Result";
namedWindow("input image", WINDOW_AUTOSIZE);
namedWindow(output_tt, WINDOW_AUTOSIZE);
imshow("input image", src);
// convert to gray
cvtColor(src, gray_src, COLOR_BGR2GRAY);
int width = gray_src.cols;
int height = gray_src.rows;
// 基本LBP演示
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(row, col);
uchar code = 0;
code |= (gray_src.at(row - 1, col - 1) > c) << 7;
code |= (gray_src.at(row - 1, col) > c) << 6;
code |= (gray_src.at(row - 1, col + 1) > c) << 5;
code |= (gray_src.at(row, col + 1) > c) << 4;
code |= (gray_src.at(row + 1, col + 1) > c) << 3;
code |= (gray_src.at(row + 1, col) > c) << 2;
code |= (gray_src.at(row + 1, col - 1) > c) << 1;
code |= (gray_src.at(row, col - 1) > c) << 0;
lbpImage.at(row - 1, col - 1) = code;
}
}
imshow(output_tt, lbpImage);
图像效果
原始LBP算法的缺陷:
LBP的整体思想非常简单,计算复杂度很低,反映的特征较好,但是有两个明显问题:
从上面的计算我们可以看出,基本的LBP算法的计算仅包含其相邻的八个像素点,半径小,覆盖范围很小,这种方式的表达能力相对较小。
=========================================================================
基本的 LBP 算子的最大缺陷在于它只覆盖了一个固定半径范围内的小区域,这显然不能满足不同尺寸和频率纹理的需要。当图像的尺度发生变化时,LBP特征信息将会发生错误,LBP特征将不能正确的反映像素点周围的纹理信息。
为了适应不同尺度的纹理特征,并达到灰度和旋转不变性的要求,Ojala 等人对 LBP 算子进行了改进,将 3×3 邻域扩展到任意邻域,改进后的 LBP 算子允许在半径为 R 的圆形邻域内包含多个像素点,在半径为R的区域中包含,改进后的LBP算法通常用符号LBP(p,b)表达,R为圆形半径,P代表在该圆形范畴内的P个不同像素点,然而,圆形LBP算子依然不具有旋转不变性特征。
中心点位置可以用如下数学公式进行计算:
由于计算的值可能不是整数,即计算出来的点不在图像上,我们使用计算出来的点的插值点。目的的插值方法有很多,Opencv使用的是双线性插值,双线性插值的公式如下:
圆形LBP算子的数学表达式如下:
其中,P表示采样算子中的元素个数,R表示采样算子的半径 。
=========================================================================
对于一个选定的P个元素的算子,LBP算法将产生个不同的输出值,比如P=8时,值就有256个,这256个模式中每拿出一个都围绕中心点转动,那么在多次转动的过程中,会产生多种不同的结果,而这些结果中值最小的那个模式就是选择不变的二值模式了。
LBP旋转不变模式的数学表达式为:
其中,P表示采样算子中的元素个数,R表示采样算子的半径,指的是对LBP算子的元素进行ri次循环右移,得到不同的模式值。
在上面的图中说明的是,图中的这8种模式,都有相同的旋转不变模式,即00001111,取值为所有模中的最小的值。那么对256种模式都做这种旋转,最终将特征值最小的一个特征模式作为中心像素点的LBP特征,这样就达到了的效果。
LBP特征旋转不变算子的缺陷:
实际应用中发现LBProt并不具有很好地辨别力,因为随着采样点数的增加,二进制模式会急剧增多,会使得数据量过大,直方图过于稀疏,不能很好地反映图像特征。
=========================================================================
一个LBP算子可以产生不同的二进制模式,对于半径为R的圆形区域内含有P个采样点的LBP算子将会产生种模式。如此多的二值模式无论对于纹理的提取还是对于纹理的识别、分类及信息的存取都是不利的。为了解决二进制模式过多的问题,提高统计性,Ojala提出了采用一种“等价模式”来对LBP算子的模式种类进行降维。
等价模式LBP特征认为当某个LBP所对应的循环二进制数从0到1或从1到0最多有两次跳变时,该LBP所对应的二进制就称为一个等价模式类。如00000000(0次跳变),00000111(只含一次从0到1的跳变),10001111(先由1跳到0,再由0跳到1,共两次跳变)都是等价模式类。除等价模式类以外的模式都归为另一类,称为混合模式类,例如10010111(共四次跳变)
当跳变次数设置为2时,等价LBP特征的数学表达式为:
其中U定义为等价量度(Uniformity measure U("pattern"))为二进制串中的跳变次数。用数学表达式可以表示为:
通过这样的改进,二进制模式的种类大大减少,而不会丢失任何信息。模式数量由原来的种减少为 P ( P-1)+2种,其中P表示邻域集内的采样点数。对于3×3邻域的算子来说,二进制模式由原始的种减少为58种,这使得特征向量的维数更少,并且可以减少高频噪声带来的影响。
跳变次数 | 模式个数 |
U=0 | 2 |
U=1 | 0 |
U=2 | 56 |
U=3 | 0 |
U=4 | 140 |
U=5 | 0 |
U=6 | 56 |
U=7 | 0 |
U=8 | 2 |
那么小于2次的共58种LBP模式,将其编码为1-58;其余的混合模式类统一编码为0,如果用灰度值图像展示等价LBP会发现这种LBP特征值图像偏暗,因为大部分不重要的特征像素都被编码为0=黑色了。等价LBP将原来指数个模式变为了P(P-1)+2多项式个模式,大大减少了内存占用,并且特征维度的降低可以减少高频噪声带来的影响。
=========================================================================
在完全局部二值模式(CLBP)中,局部区域可以由中心像素和局部差分的符号来表示,与传统LBP不同,CLBP具有3个描述子。分别是分别是全局对比度CLBP_C,正负二值模式CLBP_S和幅度二值模式CLBP_M。
其中,中心像素通过全局阈值形成二值编码,称为CLBP_C, 表示某一点的中心像素,中心像素代表局部灰度级,也包含了局部灰度判别信息, 表示图像像素的平均灰度值,公式如下:
把邻域像素和中心像素的差值分成符号分量和梯度分量,符号分量所包含的纹理特征远高于梯度分量。局部差分符号和幅度变换可以将图像局部纹理结构分解成为两种互补成分,即正负二值模式CLBP_ S,幅度二值模式 CLBP_ M。所述的三种二值编码可以任意组合构成最终的CLBP 直方图,能实现比传统的 LBP 更有效的旋转不变分类能力。
通常,给定一个中心像素和其他 P 个邻域值,则局部差向量表示为,其中,用数学表达式可以表示为:
CLBP_S描述子的公式和传统的LBP描述子相似:
CLBP_M的数学表达式为:
其中,c 是自适应阈值,由整个图像中 的均值来表示,与LBP旋转不变模式算法一样,同样可以实现旋转不变性。文中的符号分量CLBP_S实际上就是传统LBP,两者编码方式也是相同的(这里的-1相当于传统LBP中的0)。
在纹理分类中,CLBP 可以有两种方式组合,即串联和联合方式。 CLBP_S/M/C是三维的联合直方图,而 CLBP_M_S/C是由联合直方图 CLBP_S/C与 CLBP_M 串联而成的。
---------------------------------------------------------------------------------------------------------------------------------
2.改进后的ELBP特征
针对局部区域块提出了两种互补的特征类型,像素强度和像素差异。像素强度又分为邻域像素强度和中心像素强度,像素差异分为径向差异和角度差异,下面分别是各种差异的数学化表达:
其中,,可以发现NI-LBP其实就是邻域像素与其均值的比较并进行二进制编码,与传统LBP的差异就在于阈值u的选择不同。
其中,CI-LBP和LBP_C一样,是整幅图的均值。
其中,,表示的是径向距离相差的和两点的像素灰度值的差。
其中,,,表示的是半径为r且角度相差的两个像素灰度值的差。在统一LBP模式下,AD-LBP所提供的纹理信息较少,为了降低维度提高运算速率,最后,使用串并联的方式将直方图融合。
代码实现
// ELBP 演示
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(current_radius) * cos(2.0 * CV_PI*n / static_cast(numNeighbors));
float y = static_cast(current_radius) * -sin(2.0 * CV_PI*n / static_cast(numNeighbors));
int fx = static_cast(floor(x));
int fy = static_cast(floor(y));
int cx = static_cast(ceil(x));
int cy = static_cast(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(row + fy, col + fx) + w2* gray_src.at(row + fy, col + cx) +
w3* gray_src.at(row + cy, col + fx) + w4* gray_src.at(row + cy, col + cx);
elbpImage.at(row - current_radius, col - current_radius) +=
((t > gray_src.at(row, col)) && (abs(t - gray_src.at(row, col)) > std::numeric_limits::epsilon())) << n;
}
}
}
imshow("ELBP Result", elbpImage);
return;
}
图像效果
radius=3
radius=7
radius=15
========================================================================