LBP(Local Binary Patterns) 局部二值模式特征,应用于很多领域,比如人脸识别,纹理检测,对象检测
假如有一个3x3的灰度图
首先对这张图做二值化,阈值选中心像素值—5,大于等于5的转化为1,小于的为0,得到一个二进制值( (0,0)位置最高位开始顺时针 ) 00010011,转换为十进制为19,此3x3的灰度图的LBP就为19,然后以此值替换中心像素值。
然后以卷积的方式对整张图进行这样的处理,最后就得到了整张图的LBP特征。
LBP表达
若取逆时针( (0,0)位置最低位 ) :
Pattern = 11110001 (十进制为241)
LBP = 1 + 16 + 32 + 64 + 128 = 241
C = (6+7+8+9+7)/5 + (5+2+1)/3 = 4.7 (局部对比度,对比度小于2的可以认为是平坦区域,角点或边缘的对比度都会很高)LBP扩展与多尺度表达
还可以取圆形像素点计算。
LBP可以在不同尺度空间进行计算,验证是否有尺度不变性,同时可以用局部对比度验证光照不变性。LBP统一模式
U表示pattern值由0到1,由1到0的转折次数。
所有U=0,或U=2的都称为统一模式,其他情形的称为非统一模式。LBP统一与非统一模式的直方图
直方图的bins :统一模式的情形有58种,所有非统一模式都放到1个bins中,所以 ULBP = 59 。
用于纹理的匹配。仔细观察统一模式的情形,会发现各统一模式之间可以通过旋转来转换,此为LBP的旋转不变性。
至此,LBP特征也具备先前讲过的SURF,SIFT相同的关键特性,而且计算量比它们都要小很多,常用作于人脸检测,比Haar特征(因为有浮点运算)要快几倍。
代码
#include "../common/common.hpp"
static Mat gray;
static const char title[] = "LBP features";
static int current_radius = 3; // LBP拓展的圆形的半径
static int max_radius = 20;
static void m_lbp(int, void*);
void main(int argc, char** argv)
{
gray = imread(getCVImagesPath("images/test1_3.png"), IMREAD_GRAYSCALE);
imshow("src-15", gray);
// LBP 基本演示
int width = gray.cols;
int height = gray.rows;
Mat lbpImage_cw = Mat::zeros(gray.rows - 2, gray.cols - 2, CV_8UC1); // 顺时针,减2是因为卷积时,上下左右两行像素忽略掉
Mat lbpImage_ccw = Mat::zeros(gray.rows - 2, gray.cols - 2, CV_8UC1); // 逆时针
for (int row = 1; row < height - 1; row++) {
for (int col = 1; col < width - 1; col++) {
uchar center = gray.at<uchar>(row, col);
uchar code_cw = 0;
code_cw |= (gray.at<uchar>(row - 1, col - 1) >= center) << 7; // (0,0)位置最高位, 只判断大于 与 判断大于等于,图像结果差别很大
code_cw |= (gray.at<uchar>(row - 1, col) >= center) << 6;
code_cw |= (gray.at<uchar>(row - 1, col + 1) >= center) << 5;
code_cw |= (gray.at<uchar>(row, col + 1) >= center) << 4;
code_cw |= (gray.at<uchar>(row + 1, col + 1) >= center) << 3;
code_cw |= (gray.at<uchar>(row + 1, col) >= center) << 2;
code_cw |= (gray.at<uchar>(row + 1, col - 1) >= center) << 1;
code_cw |= (gray.at<uchar>(row, col - 1) >= center) << 0;
lbpImage_cw.at<uchar>(row - 1, col - 1) = code_cw;
uchar code_ccw = 0;
code_ccw |= (gray.at<uchar>(row - 1, col - 1) >= center) << 0; // (0,0)位置最低位
code_ccw |= (gray.at<uchar>(row - 1, col) >= center) << 1;
code_ccw |= (gray.at<uchar>(row - 1, col + 1) >= center) << 2;
code_ccw |= (gray.at<uchar>(row, col + 1) >= center) << 3;
code_ccw |= (gray.at<uchar>(row + 1, col + 1) >= center) << 4;
code_ccw |= (gray.at<uchar>(row + 1, col) >= center) << 5;
code_ccw |= (gray.at<uchar>(row + 1, col - 1) >= center) << 6;
code_ccw |= (gray.at<uchar>(row, col - 1) >= center) << 7;
lbpImage_ccw.at<uchar>(row - 1, col - 1) = code_ccw;
}
}
imshow("LBP base cw", lbpImage_cw);
imshow("LBP base ccw", lbpImage_ccw);
// LBP扩展与多尺度表达
namedWindow(title, CV_WINDOW_AUTOSIZE);
createTrackbar("minHessian:", title, ¤t_radius, max_radius, m_lbp);
m_lbp(0, 0);
waitKey(0);
}
void m_lbp(int, void*)
{
int offset = current_radius * 2; // 边缘未计算的像素行列数
Mat elbpImage = Mat::zeros(gray.rows - offset, gray.cols - offset, CV_8UC1); // 减去边缘未计算的像素行列数
int width = gray.cols;
int height = gray.rows;
int numNeighbors = 8; // 为了简化算法,不管LBP圆形半径多大,P值都取8
for (int n = 0; n < numNeighbors; n++) {
// 对此有不明白的,去看看特殊角的sin,cos的值的表
// sin(0-π)=0-1-0, sin(π-2π)=0-(-1)-0, cos(0-π)=1-0-(-1), cos(π-2π)=(-1)-0-1
// 从x坐标正方向开始,逆时针选取像素坐标点, LBP选哪个点做起始高位点和沿哪个方向选择都无所谓?
float x = static_cast<float>(current_radius) * cos(2.0 * CV_PI*n / static_cast<float>(numNeighbors)); // c++类型转换不建议用强转或隐式转换
float y = static_cast<float>(current_radius) * -sin(2.0 * CV_PI*n / static_cast<float>(numNeighbors));
// 取当前点的周围四个点,做双线性插值
int fx = static_cast<int>(floor(x)); // 向下取整数,即不大于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;
// 当前点的周围四个点的权重,这个四个点的权重加起来等于1
float w1 = (1 - tx)*(1 - ty); // (0,0)
float w2 = tx*(1 - ty); // (1,0)
float w3 = (1 - tx)* ty; // (0,1)
float w4 = tx*ty; // (1,1)
for (int row = current_radius; row < (height - current_radius); row++) {
for (int col = current_radius; col < (width - current_radius); col++) {
float t = w1* gray.at<uchar>(row + fy, col + fx) + w2* gray.at<uchar>(row + fy, col + cx) +
w3* gray.at<uchar>(row + cy, col + fx) + w4* gray.at<uchar>(row + cy, col + cx); // 权重计算当前点的像素值
uchar center = gray.at<uchar>(row, col); // 中心像素值
// numeric_limits C++11 模板类 std::numeric_limits<float>::epsilon()表示的是最小非0浮点数
// 三元表达式就是在判断 t >= center , 然后移位
// 浮点数间判断等于: == 完全一样才相等, std::numeric_limits<float>::epsilon() 差值若在浮点数的精度范围内表示相等
elbpImage.at<uchar>(row - current_radius, col - current_radius) +=
((t > center) && (abs(t - center) > std::numeric_limits<float>::epsilon())) << n; // 移位相加,最终得到二进制转十进制的值
}
}
}
cout << std::numeric_limits<float>::epsilon() << endl; // 1.19209e-07 这里的e不是自然常数(2.71828),而是10,所以为 0.000000119209
imshow(title, elbpImage);
}