LBP
指局部二值模式,英文全称:Local Binary Pattern
,是一种用来描述图像局部特征的算子,LBP
特征具有灰度不变性和旋转不变性等显著优点。LBP
常应用于人脸识别和目标检测中,在OpenCV
中有使用LBP
特征进行人脸识别的接口,也有用LBP
特征训练目标检测分类器的方法,OpenCV
实现了LBP
特征的计算,但没有提供一个单独的计算LBP
特征的接口。也就是说OpenCV
中使用了LBP
算法,但是没有提供函数接口喽!
LBP
是一种图像纹理特征提取算法,是一种局部特征,是照片分类和人脸检索研究中采用较多的特征提取算法之一。在图像物体识别领域,常用的特征描述子包括:HOG、SIFT、SURT、Wavelet、Gabor、DCT等,具体选择哪种算子需要根据目标对象决定。人脸识别中,LBP
和Gabor
是效果较好的两组特征。gabor
效果比LBP
效果鲁棒,但是LBP
运算速度快,编译在嵌入式等平台运行。一般如果条件允许,二者会进行结合,包括定义结合特征(比如LGBP,LGXP等),特征级融合和决策级融合。
特征提取是希望将资料中重要的资讯提取出来,去除杂讯的影响,以提升分类的准确率。不同的特征提取方法有不同的效果,LBP的特性是较能排除光照变化且运算速度快。LBP的核心观念是计算每个像素和周围像素间”相对”的关系。通常光照对图中的物件带来的影响是全局的,也就是说照片中的物体的明暗程度,都往同一个方向改变,可能是变亮或变暗,只是改变的幅度会因为距离光源的远近而有所不同。所以基本上局部相邻(Local)的像素间,受光照影响后数值也许会改变,但是相对大小不会改变。
图像识别在输入图像之后,先提取特征,然后进行分类。在特征提取的设计会直接影响图像辨识的好坏,不同数据所需要的特征也不尽相同,故此步骤通常是算法的瓶颈。
深度学习在训练过程中让深度神经自动从训练数据中学习最适合的特征获取方法,让算法应用更加广泛,准确率也获得大幅提升。但是深度学习模型训练时间较长,对训练资料的品质好坏即数量多寡非常敏感,以及预测计算时间较长等。
注意:一点不懂图像算法,都是大神们的解释,这里摘录下来,作为自己回顾的部分。
原始的LBP算子定义在像素3*3的邻域内,以邻域中心像素为阈值,相邻的8个像素的灰度值与邻域中心的像素值进行比较,若周围像素大于中心像素值,则该像素点的位置被标记为1,否则为0。这样,3*3邻域内的8个点经过比较可产生8为二进制数,将这8位二进制数依次排列形成一个二进制数字,这个二进制数字就是中心像素的LBP值,LBP值共有 28 中可能,因此LBP值有256种可能。中心像素的LBP值反映了该像素周围区域的纹理信息。
注意:计算LBP特征的图像必须是灰度图,如果是彩色图,需要先转换成灰度图
- 原始LBP特征计算代码:
//原始LBP特征计算
template
void getOriginLBPFeature(InputArray _src,OutputArray _dst)
{
Mat src = _src.getMat();
_dst.create(src.rows-2,src.cols-2,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
for(int i=1;i
{
for(int j=1;j
{
_tp center = src.at<_tp>(i,j);
unsigned char lbpCode = 0;
lbpCode |= (src.at<_tp>(i-1,j-1) > center) << 7;
lbpCode |= (src.at<_tp>(i-1,j ) > center) << 6;
lbpCode |= (src.at<_tp>(i-1,j+1) > center) << 5;
lbpCode |= (src.at<_tp>(i ,j+1) > center) << 4;
lbpCode |= (src.at<_tp>(i+1,j+1) > center) << 3;
lbpCode |= (src.at<_tp>(i+1,j ) > center) << 2;
lbpCode |= (src.at<_tp>(i+1,j-1) > center) << 1;
lbpCode |= (src.at<_tp>(i ,j-1) > center) << 0;
dst.at(i-1,j-1) = lbpCode;
}
}
}
在这份代码中,dst
的数组大小是(src.rows-2,src.cols-2)
,这是因为src
的外边缘像素无法计算8位的LBP值,也就是说出去外边缘像素的才能计算8位LBP值,而8位LBP值实际就是 28 中表示,也就是256,转化为灰度值图像的话,就是一幅大小为(src.rows-2,src.cols-2)
的灰度图。
在原始的LBP特征提出后,研究人员对LBP特征进行了很多改进,因此产生了许多LBP的改进版本。
由于原始LBP特征使用的是固定邻域内的灰度值,因此当图像的尺度(尺度是什么?)发生变化时,LBP特征的编码将会发生错误,LBP特征将不能正确返回像素点周围的纹理信息,因此研究人员对其进行了改进。基本的LBP算子的最大缺陷在于它只覆盖一个固定半径范围内的小区域,这显然不能满足不同尺寸和频率纹理的需要(比较笨,知道的概念太少,不理解。。。)。为了适应不同尺度的纹理特征,并达到灰度和旋转不变性的要求,Ojala 等对 LBP 算子进行了改进,将 3×3 邻域扩展到任意邻域,并用圆形邻域代替了正方形邻域,改进后的 LBP 算子允许在半径为 R 的圆形邻域内有任意多个像素点。从而得到了诸如半径为R的圆形区域内含有P个采样点的LBP算子:
这副图像中,第一幅图像的圆半径认为1,采样的点为8个;第二幅图像的圆半径为2,采样的点为16个;第三副图像的圆半径为2,采样的点为8个。
这种LBP特征叫做Extended LBP,也叫Circular LBP。使用可变半径的圆对近邻像素进行编码,可以得到如下的近邻:
对于给定中心点 (xc,yc) ,其邻域像素位置为 (xp,yp) , p∈P ,其采样点 (xp,yp) 用如下公式计算:
R是采样半径,p是第p个采样点,P是采样数目。由于计算的值可能不是整数,即计算的点不再图像上,我们使用计算出来的点的插值点。OpenCV使用的是双线性插值(讲真,数学太差),双线性插值如下:
参考计算机视觉学习初识LBP算法
通过LBP特征的定义,LBP特征对光照变化是鲁棒的,效果如图:
//圆形LBP特征计算,这种方法适于理解,但在效率上存在问题,声明时默认neighbors=8
template <typename _tp>
void getCircularLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
Mat src = _src.getMat();
//LBP特征图像的行数和列数的计算要准确
_dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
//循环处理每个像素
for(int i=radius;ifor(int j=radius;j//获得中心像素点的灰度值
_tp center = src.at<_tp>(i,j);
unsigned char lbpCode = 0;
for(int k=0;k//根据公式计算第k个采样点的坐标,这个地方可以优化,不必每次都进行计算radius*cos,radius*sin
float x = i + static_cast<float>(radius * \
cos(2.0 * CV_PI * k / neighbors));
float y = j - static_cast<float>(radius * \
sin(2.0 * CV_PI * k / neighbors));
//根据取整结果进行双线性插值,得到第k个采样点的灰度值
//1.分别对x,y进行上下取整
int x1 = static_cast<int>(floor(x));
int x2 = static_cast<int>(ceil(x));
int y1 = static_cast<int>(floor(y));
int y2 = static_cast<int>(ceil(y));
//2.计算四个点(x1,y1),(x1,y2),(x2,y1),(x2,y2)的权重
//下面的权重计算方式有个问题,如果四个点都相等,则权重全为0,计算出来的插值为0
//float w1 = (x2-x)*(y2-y); //(x1,y1)
//float w2 = (x2-x)*(y-y1); //(x1,y2)
//float w3 = (x-x1)*(y2-y); //(x2,y1)
//float w4 = (x-x1)*(y-y1); //(x2,y2)
//将坐标映射到0-1之间
float tx = x - x1;
float ty = y - y1;
//根据0-1之间的x,y的权重计算公式计算权重
float w1 = (1-tx) * (1-ty);
float w2 = tx * (1-ty);
float w3 = (1-tx) * ty;
float w4 = tx * ty;
//3.根据双线性插值公式计算第k个采样点的灰度值
float neighbor = src.at<_tp>(x1,y1) * w1 + src.at<_tp>(x1,y2) *w2 \
+ src.at<_tp>(x2,y1) * w3 +src.at<_tp>(x2,y2) *w4;
//通过比较获得LBP值,并按顺序排列起来
lbpCode |= (neighbor>center) <<(neighbors-k-1);
}
dst.at(i-radius,j-radius) = lbpCode;
}
}
}
//圆形LBP特征计算,效率优化版本,声明时默认neighbors=8
template <typename _tp>
void getCircularLBPFeatureOptimization(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
Mat src = _src.getMat();
//LBP特征图像的行数和列数的计算要准确
_dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
for(int k=0;k//计算采样点对于中心点坐标的偏移量rx,ry
float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
//为双线性插值做准备
//对采样点偏移量分别进行上下取整
int x1 = static_cast<int>(floor(rx));
int x2 = static_cast<int>(ceil(rx));
int y1 = static_cast<int>(floor(ry));
int y2 = static_cast<int>(ceil(ry));
//将坐标偏移量映射到0-1之间
float tx = rx - x1;
float ty = ry - y1;
//根据0-1之间的x,y的权重计算公式计算权重,权重与坐标具体位置无关,与坐标间的差值有关
float w1 = (1-tx) * (1-ty);
float w2 = tx * (1-ty);
float w3 = (1-tx) * ty;
float w4 = tx * ty;
//循环处理每个像素
for(int i=radius;ifor(int j=radius;j//获得中心像素点的灰度值
_tp center = src.at<_tp>(i,j);
//根据双线性插值公式计算第k个采样点的灰度值
float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
+ src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
//LBP特征图像的每个邻居的LBP值累加,累加通过与操作完成,对应的LBP值通过移位取得
dst.at(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
}
}
}
}
因为不太理解C++的模板这些东西,对LBP特征原理及代码实现的博主所使用的高级技术表示佩服,果然还有许多许多需要学习的东西。但是大概过程:
1. 根据半径的大小,设置LBP特征图像的行数和列数
2. 根据neighbors
和radius
计算采样点,因为采样点的坐标可能不是整数,需要使用双线性插值来确定采样点
3. 中心点和采样点进行比较,得到对应中心点的LBP值,然后记录到目标数组中,最后得到的就是LBP值的灰度图了。
测试结果:
第一幅图是原图;
第二幅图是原始的LBP算法得到的LBP特征图;
第三幅图是radius = 3,neighbors = 8
的CircularLBP
特征图;
第四幅图是radius = 1,neighbors = 8
的CircularLBP
特征图。
实验结果:半径越小,图像纹理越精细。
第一幅图是原图;
第二幅图是原始的LBP算法得到的LBP特征图;
第三幅图是radius = 3,neighbors = 8
的CircularLBP
特征图;
第四幅图是radius = 3,neighbors = 4
的CircularLBP
特征图。
实验结果:刚开始以为第四幅图是黑的,但是仔细看还有有轮廓的,也就是相同半径下,领域数目越小,图像亮度越低,想想 24 之后16种灰度值,当然会显得很暗,区分度也会很差。
上面的LBP特征具有灰度不变性,但是还不具备旋转不变性,因此研究人员又在上面的基础上进行了扩展,提出了具有旋转不变性的LBP。
首先,不断的旋转圆形邻域内的LBP特征,根据选择得到一系列的LBP特征值,从这些LBP特征值选择LBP特征值最小的作为中心像素点的LBP特征。
如图,通过对得到的LBP特征进行旋转得到一系列的LBP特征值,最终将特征值最小的一个特征模式作为中心像素点的LBP特征。
//旋转不变圆形LBP特征计算,声明时默认neighbors=8
template <typename _tp>
void getRotationInvariantLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
Mat src = _src.getMat();
//LBP特征图像的行数和列数的计算要准确
_dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
for(int k=0;k//计算采样点对于中心点坐标的偏移量rx,ry
float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
//为双线性插值做准备
//对采样点偏移量分别进行上下取整
int x1 = static_cast<int>(floor(rx));
int x2 = static_cast<int>(ceil(rx));
int y1 = static_cast<int>(floor(ry));
int y2 = static_cast<int>(ceil(ry));
//将坐标偏移量映射到0-1之间
float tx = rx - x1;
float ty = ry - y1;
//根据0-1之间的x,y的权重计算公式计算权重,权重与坐标具体位置无关,与坐标间的差值有关
float w1 = (1-tx) * (1-ty);
float w2 = tx * (1-ty);
float w3 = (1-tx) * ty;
float w4 = tx * ty;
//循环处理每个像素
for(int i=radius;ifor(int j=radius;j//获得中心像素点的灰度值
_tp center = src.at<_tp>(i,j);
//根据双线性插值公式计算第k个采样点的灰度值
float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
+ src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
//LBP特征图像的每个邻居的LBP值累加,累加通过与操作完成,对应的LBP值通过移位取得
dst.at(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
}
}
}
//进行旋转不变处理
for(int i=0;ifor(int j=0;junsigned char currentValue = dst.at(i,j);
unsigned char minValue = currentValue;
for(int k=1;k//循环左移
unsigned char temp = (currentValue>>(neighbors-k)) | (currentValue<if(temp < minValue)
{
minValue = temp;
}
}
dst.at(i,j) = minValue;
}
}
}
测试结果:
第一幅图是原图;
第二幅图是原始的LBP算法得到的LBP特征图;
第三幅图是radius = 3,neighbors = 8
的CircularLBP
特征图;
第四幅图是radius = 3,neighbors = 8
的具有旋转不变性CircularLBP
特征图。
过程就是:
1. 根据半径的大小,设置LBP特征图像的行数和列数
2. 根据neighbors
和radius
计算采样点,因为采样点的坐标可能不是整数,需要使用双线性插值来确定采样点
3. 中心点和采样点进行比较,得到对应中心点的LBP值。
4. 中心点对应的邻域进行旋转,可以获得相对于中心点的最小的LBP值,这样作为图像的LBP特征。不管如何旋转,LBP值都是恒定的,也就是具有了旋转不变性。
总结来说,旋转不变性的LBP特征图亮度应该比较低,因为选取的是最小的LBP值。
人脸检测比较出名的是Haar+Adaboost
方法,目前Opencv中也支持LBP+Adaboost
和HOG+Adaboost
方法进行目标检测,据SnailTyan大神说,LBP+Adaboost
方法用在目标检测中的效果比Haar特征、HOG特征都要好,而且LBP特征的训练速度比Haar和HOG都要快很多。(虽然并不知道Haar、HOG、Adaboost是什么。。。)
据大神说,人脸识别中LBPH的使用主要是用来进行直方图的比较,通过直方图的比较来判断目标的类别。在OpenCV中基于LBP的人脸识别的实现中使用的LBP特征是Extendes LBP
,即圆形LBP特征。
LBP特征原理及代码实现
LBP (Local Binary Pattern) 是目前流行的模式识别、人脸识别算法吗?
计算机视觉学习初识LBP算法