LBP特征检测
LBP特征
LBP(Local Binary Pattern, 局部二值模式)是一种用于表征图像局部纹理特性的算子;该方法具有对旋转与亮度变化高度鲁棒性的特点,并且在纹理特性提取方面表现突出。此外,在计算过程中生成的空间邻域统计特性能有效反映图像细节信息;
1、LBP特征的描述
传统的LBP算子定义为在一个3×3的窗口中,默认以窗口中心像素灰度值作为基准,并对周围的8个像素进行比较处理。具体而言,在这种情况下,默认会将周围8个像素中的每个与中心点进行对比:如果相邻像素点的空间位置所对应的灰度值高于中心点,则将该相邻位置标记为1;反之则标记为0。经过这样的比较处理后,在这个包含9个点(其中心+8邻域)的小方块内会生成一个包含8位二进制信息的模式(通常将其转换为十进制表示即得到LBP码)。如图所示:

LBP的改进版本:
原始的LBP提出后,研究人员不断对其提出了各种改进和优化。
(1)圆形LBP算子:
基本的 LBP 算子的主要缺点是局限在一个固定半径的小区域内,在这种情况下显然无法满足不同尺寸和频率纹理的需求。为了适应这些复杂且多样的纹理特征并同时达到灰度与旋转不变性的要求,Ojala 等人通过改进使 LBP 算子能够适应不同尺度的纹理特征,并达到了灰度与旋转不变性这一重要特性。他们将传统的 3×3 邻域扩展到了任意形状的邻域,并使用圆形邻域替代了原来的正方形邻域,这样就形成了一个能够在半径为 R 的圆形区域内包含 P 个采样点的新 LBP 算子

(2)LBP旋转不变模式
基于 LBP 定义可知, LBP 算子具备灰度不变性, 但不具备旋转不变性。当图像发生旋转时会产生不同的 LBP值
研究者们基于LBP算子进行了拓展工作
图 2.5 展示了求取具有旋转不变性的 LBP 过程示意图,在图中下方标注的数字表示对应的 LBP 值;通过旋转不变性处理后得到的具有旋转不变性的 LBP 值为 15;具体而言,在图中所示的 8 种 LBP 模式中,其对应的旋转不变性 LBP 模式均为 00001111。

(3)LBP等价模式
一个LBP运算器能够生成多样化的二进制模板。当在一个半径为R并包含有P个采样点的圆形区域内应用LBP运算器时,则会产生P²种不同的二进制模板。显而易见地来说,在邻域集内采样点的数量增加会导致可区分特征的数量显著提升。例如在一个5×5大小的小邻域内放置20个采样点时…
为了应对二进制特征数量过多所带来的问题,并提升统计效率,Ojala提出了一种称为"均匀分布式"(Uniform Pattern)的方法来降低LBP算子的特征维度。研究者普遍认为在大多数实际应用中,真实场景中的图像数据通常会呈现出有限的变化特性,即在循环二进制表示中,每个LBP特征序列至多包含两次相邻位的变化。为此,Ojala等人将其标准化定义如下:对于一个给定的LBP特征序列,如果其对应的循环二进制表示中最长连续相同字符之间的跳跃次数不超过两次,则该特征序列就被视为属于"均匀分布式"类别。例如:全零序列(即全是' ̂')具有零次跳跃变化;仅包含一次' ̂'增加的一阶序列(如' ̂̂̂̂̂̂̂')同样满足标准;而像' ̂̂̂̂ '这样的序列则经历了两次跳跃变化(一次由' '降到' ',另一次由' '升至' ')因此属于该类别;其余则被归类为"非均匀分布式"类别
经过这种改进措施后,在考虑信息完整性的同时,在考虑信息完整性的同时(注:此处应删除多余的括号),二进制模式的数量显著下降(注:此处应删除多余的括号)。具体而言,在3×3邻域中取样8个点时(注:此处应删除多余的括号),将从原本的256种缩减至58种不同的组合形式(注:此处应删除多余的括号)。这种优化策略不仅降低了特征向量的空间维度(注:此处应删除多余的括号),并且有效抑制了高频噪声对数据的影响(注:此处应删除多余的括号)。
2、LBP特征用于检测的原理
显然,在提取过程中,在每一个像素点上都可获得一个LBP编码。因此,在经过提取其原始LBP算子后,在所得出的原始LBP特征中依然对应着一张图像(即该图像由各个像素点对应的LBP值构成)。

在LBP应用的范畴内,例如纹理分类和人脸分析等领域中,并未将LBP图谱直接用作分类识别的特征向量;相反地,在这些应用场景中,则采用了基于LBP特征谱的统计分布特性来进行分类识别.
鉴于从前面的分析可以看出
一张分辨率设置为100×100像素的图片被划分为总共10×10=100个小区域(可以通过不同的划分方法实现),每个小区域内的所有像素点都会提取其LBP特征参数,并计算并构建统计直方图;因此,在整个图像中将形成总计有(1)个小区域以及(2)个统计直方图;基于这些统计直方图的数据信息就可以全面描述该原始图像的特点;为了进一步地,在比较两幅图像时可以采用多种相似性度量函数来评估它们之间的差异程度;
3、对LBP特征向量进行提取的步骤
(1)首先将检测窗口划分为16×16的小区域(cell);
(2)对于每一个cell中的单素素点而言,在其周围的8个素索点处进行灰度值对比操作:如果所有周围各点的灰度均高于该中心素索点,则将其标记为1;否则标记为0。经过上述方式处理后即可得到该区域中心点对应的LBP特征码。
然后统计每个cell中各个数值(假设为十进制LBP值)出现的频率情况;接着对这个频率分布表进行标准化处理。
最后一步会获取到每个cell各自的统计直方图,并将其连接起来形成一个特征向量;这个过程即整幅图像的整体LBP纹理特征向量。
然后便可利用SVM或者其他机器学习算法进行分类了。
LBP默认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 gray
cvtColor(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 gray
cvtColor(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;
}
运行截图:


