花老湿学习OpenCV:LBP(Location Binary Pattern)特征

引言:

LBP指局部二值模式,英文全称:Local Binary Pattern,是一种用来描述图像局部特征的算子,LBP特征具有灰度不变性和旋转不变性等显著优点。它是由T. Ojala, M.Pietikinen, 和 D. Harwood [1][2]在1994年提出,由于LBP特征计算简单、效果较好,因此LBP特征在计算机视觉的许多领域都得到了广泛的应用,LBP特征比较出名的应用是用在人脸识别和目标检测中,在计算机视觉开源库Opencv中有使用LBP特征进行人脸识别的接口,也有用LBP特征训练目标检测分类器的方法,Opencv实现了LBP特征的计算,但没有提供一个单独的计算LBP特征的接口。

 

LBP特征的原理:

LBP特征原理及代码实现 - SnailTyan - CSDN博客 https://blog.csdn.net/quincuntial/article/details/50541815

 

1.原始的LBP特征描述及计算方法

原始的LBP算子定义在像素3*3的邻域内,以邻域中心像素为阈值,相邻的8个像素的灰度值与邻域中心的像素值进行比较,若周围像素大于中心像素值,则该像素点的位置被标记为1,否则为0。这样,3*3邻域内的8个点经过比较可产生8位二进制数,将这8位二进制数依次排列形成一个二进制数字,这个二进制数字就是中心像素的LBP值,LBP值共有2^8种可能,因此LBP值有256种。中心像素的LBP值反映了该像素周围区域的纹理信息。

备注:计算LBP特征的图像必须是灰度图,如果是彩色图,需要先转换成灰度图。
上述过程用图像表示为:

这里写图片描述

 

代码实现: 

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"
#include <opencv2/xfeatures2d.hpp>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;

int main()
{
	//待检测图像
	Mat src = imread("F:\\visual studio\\Image\\women1.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	imshow("src", src);
	//转化为灰度图
	Mat graysrc;
	cvtColor(src, graysrc, COLOR_BGR2GRAY);

	int width = graysrc.cols;
	int height = graysrc.rows;

	Mat lbpImage = Mat::zeros(height -2,width-2 , CV_8UC1);

	for (int row = 1; row < height - 1; row++)
	{
		for (int col = 1; col < width - 1; col++)
		{
			uchar centerVal = graysrc.at<uchar>(row, col);//获取中心像素值
			//计算LBP值
			uchar code = 0; 
			code |= ((graysrc.at<uchar>(row - 1, col - 1) > centerVal) << 7);
			code |= ((graysrc.at<uchar>(row - 1, col ) > centerVal) << 6);
			code |= ((graysrc.at<uchar>(row - 1, col + 1) > centerVal) << 5);
			code |= ((graysrc.at<uchar>(row , col + 1) > centerVal) << 4);
			code |= ((graysrc.at<uchar>(row + 1, col + 1) > centerVal) << 3);
			code |= ((graysrc.at<uchar>(row + 1, col) > centerVal) << 2);
			code |= ((graysrc.at<uchar>(row + 1, col - 1) > centerVal) << 1);
			code |= ((graysrc.at<uchar>(row , col - 1) > centerVal) << 0);

			lbpImage.at<uchar>(row - 1, col - 1) = code; 
		}
	}
	imshow("LBP Image", lbpImage);
	waitKey(0);
}

效果展示:

 

2.LBP特征的改进版本

在原始的LBP特征提出以后,研究人员对LBP特征进行了很多的改进,因此产生了许多LBP的改进版本。

2.1圆形LBP特征(Circular LBP or Extended LBP)

由于原始LBP特征使用的是固定邻域内的灰度值,因此当图像的尺度发生变化时,LBP特征的编码将会发生错误,LBP特征将不能正确的反映像素点周围的纹理信息,因此研究人员对其进行了改进。基本的 LBP 算子的最大缺陷在于它只覆盖了一个固定半径范围内的小区域,这显然不能满足不同尺寸和频率纹理的需要。为了适应不同尺度的纹理特征,并达到灰度和旋转不变性的要求,Ojala 等对 LBP 算子进行了改进,将 3×3 邻域扩展到任意邻域,并用圆形邻域代替了正方形邻域,改进后的 LBP 算子允许在半径为 R 的圆形邻域内有任意多个像素点。从而得到了诸如半径为R的圆形区域内含有P个采样点的LBP算子:

这里写图片描述

 这种LBP特征叫做Extended LBP,也叫Circular LBP。使用可变半径的圆对近邻像素进行编码,可以得到如下的近邻:

这里写图片描述

对于给定中心点(xc,yc),其邻域像素位置为(xp,yp),p∈P,其采样点(xp,yp)用如下公式计算:

这里写图片描述

R是采样半径,p是第p个采样点,P是采样数目。由于计算的值可能不是整数,即计算出来的点不在图像上,我们使用计算出来的点的插值点。目的的插值方法有很多,Opencv使用的是双线性插值,双线性插值的公式如下:

这里写图片描述

 

 代码展示:

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"
#include <opencv2/xfeatures2d.hpp>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;

int radius = 2;
int maxVal = 10;

void ELBP_demo(int pos, void* data);
Mat graysrc;
int main()
{
	//待检测图像
	Mat src = imread("F:\\visual studio\\Image\\women1.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	imshow("src", src);
	//转化为灰度图
	cvtColor(src, graysrc, COLOR_BGR2GRAY);
	
	namedWindow("LBP Image", WINDOW_AUTOSIZE);
	createTrackbar("radius", "LBP Image", &radius, maxVal, ELBP_demo);
	ELBP_demo(0, 0);
	
	waitKey(0);
}

void ELBP_demo(int pos, void* data)
{
	int height = graysrc.rows;
	int width = graysrc.cols;
	int offset = 2 * radius;
	Mat elbpImage = Mat::zeros(height - offset, width - offset, CV_8UC1);
	int neighbours = 8; //理论上应根据半径变化,这里为了简便固定为8
	
	//圆形LBP特征计算,这种方法适于理解,但在效率上存在问题
	for (int row = radius; row < height - radius; row++)
	{
		for (int col = radius; col < width - radius; col++)
		{
			//获取中心像素值
			uchar centerVal = graysrc.at<uchar>(row, col);
			uchar code = 0;
			for (int i = 0; i < neighbours; i++)
			{
				//获取邻点坐标 这个地方可以优化 不必每次都进行计算 radius*cos、radius*sin
				float x = col + static_cast<float>(radius*cos(2.0*CV_PI*i / neighbours));
				float y = row + static_cast<float>(radius*sin(2.0*CV_PI*i / neighbours));
				
				//取整并根据取整结果处的值进行双线性插值,得到第k个采样点的灰度值

				//1.分别对x,y进行上下取整
				int x1 = static_cast<int>(floor(x));
				int y1 = static_cast<int>(floor(y));
				int x2 = static_cast<int>(ceil(x));
				int y2 = static_cast<int>(ceil(y));

				//2.计算四个点(x1,y1),(x1,y2),(x2,y1),(x2,y2)的权重
				float u = x - x1;
				float v = y - y1;
				
				float w1 = (1 - u)*(1 - v);
				float w2 = u*(1 - v);
				float w3 = (1 - u)*v;
				float w4 = u * v;

				//进行双线性插值
				float neighbour = graysrc.at<uchar>(y1, x1)*w1 + graysrc.at<uchar>(y2, x1)*w2 +
					graysrc.at<uchar>(y1, x2)*w3 + graysrc.at<uchar>(y2, x2)*w4;
				//通过与中心元素值进行比较 获得LBP值
				code |= ((neighbour > centerVal) << (neighbours - i - 1));
			}
			elbpImage.at<uchar>(row - radius, col - radius) = code;				
		}
	}
	Mat roi = elbpImage(Range(300, 325), Range(200, 205));
	cout << roi << endl;
	imshow("LBP Image", elbpImage);
}

 效率改进版: 

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"
#include <opencv2/xfeatures2d.hpp>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;

int radius = 2;
int maxVal = 10;

void ELBP_demo(int pos, void* data);
Mat graysrc;
int main()
{
	//待检测图像
	Mat src = imread("F:\\visual studio\\Image\\women1.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	imshow("src", src);
	//转化为灰度图
	cvtColor(src, graysrc, COLOR_BGR2GRAY);

	namedWindow("LBP Image1", WINDOW_AUTOSIZE);
	createTrackbar("radius", "LBP Image1", &radius, maxVal, ELBP_demo);
	ELBP_demo(0, 0);

	waitKey(0);
}

void ELBP_demo(int pos, void* data)
{
	int height = graysrc.rows;
	int width = graysrc.cols;
	int offset = 2 * radius;
	Mat elbpImage = Mat::zeros(height - offset, width - offset, CV_8UC1);
	int neighbours = 8; //理论上应根据半径变化,这里为了简便固定为8

	//圆形LBP特征计算,效率优化版本
	for (int i = 0; i < neighbours; i++)
	{
		//计算采样点对于中心点坐标的偏移量rx,ry;
		float rx = static_cast<float>(radius*cos(2.0 * CV_PI*i / neighbours));
		float ry = -static_cast<float>(radius*sin(2.0 * CV_PI*i / neighbours));

		//为双线性插值做准备
		//1.对采样点偏移量分别进行上下取整
		int x1 = static_cast<int>(floor(rx));
		int y1 = static_cast<int>(floor(ry));
		int x2 = static_cast<int>(ceil(rx));
		int y2 = static_cast<int>(ceil(ry));


		//计算权重
		float u = rx - x1;
		float v = ry - y1;
		//if (u < std::numeric_limits<float>::epsilon())
		//	u = 0;
		//if (v < std::numeric_limits<float>::epsilon())
		//	v = 0;
		
		float w1 = (1 - u)*(1 - v);
		float w2 = u * (1 - v);
		float w3 = (1 - u) * v;
		float w4 = u * v;

		//循环处理每个像素
		for (int row = radius; row < height - radius; row++)
		{
			for (int col = radius; col < width - radius; col++)
			{
				uchar centerval = graysrc.at<uchar>(row, col);
				float neighbour = graysrc.at<uchar>(row + y1, col + x1) * w1 +
					graysrc.at<uchar>(row + y2, col + x1) * w2 +
					graysrc.at<uchar>(row + y1, col + x2) * w3 +
					graysrc.at<uchar>(row + y2, col + x2) * w4;
					elbpImage.at<uchar>(row - radius, col - radius) |= ((neighbour > centerval) << (neighbours - i - 1));
			}
		}
	}
	Mat roi = elbpImage(Range(300, 325), Range(200, 205));
	cout << roi << endl;
	imshow("lbp image1", elbpImage);

}

效果展示:

使用上述同一种方法的不同实现,得到的2个LBP特征图像不同。根据多次调试发现,代码逻辑本身没有问题,只是涉及到精度、有效数字之类的问题,下面进行修改,主要修改了采样点与中心点处值比较时采用的阈值,由于计算精度的影响,对于同一组中心点和采样点,2种方法获得的数值有及其细微的差别,但是比较结果有时却不一致。因此我们调大比较时的阈值,可以忽略其细微差别。

代码修改:

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"
#include <opencv2/xfeatures2d.hpp>
#include <bitset>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;

int radius = 2;
int maxVal = 10;

void ELBP_demo(int pos, void* data);
Mat graysrc;
int main()
{
	//待检测图像
	Mat src = imread("F:\\visual studio\\Image\\women1.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	imshow("src", src);
	//转化为灰度图
	cvtColor(src, graysrc, COLOR_BGR2GRAY);
	
	namedWindow("LBP Image", WINDOW_AUTOSIZE);
	createTrackbar("radius", "LBP Image", &radius, maxVal, ELBP_demo);
	ELBP_demo(0, 0);
	
	waitKey(0);
}

void ELBP_demo(int pos, void* data)
{
	int height = graysrc.rows;
	int width = graysrc.cols;
	int offset = 2 * radius;
	Mat elbpImage = Mat::zeros(height - offset, width - offset, CV_8UC1);
	int neighbours = 8; //理论上应根据半径变化,这里为了简便固定为8
	
	//圆形LBP特征计算,这种方法适于理解,但在效率上存在问题,声明时默认neighbors=8
	for (int row = radius; row < height - radius; row++)
	{
		for (int col = radius; col < width - radius; col++)
		{
			//获取中心像素值
			uchar centerVal = graysrc.at<uchar>(row, col);
			uchar code = 0;
			for (int i = 0; i < neighbours; i++)
			{
				//获取邻点坐标 这个地方可以优化 不必每次都进行计算 radius*cos、radius*sin
				float x = col + static_cast<float>(radius*cos(2.0*CV_PI*i / neighbours));
				float y = row - static_cast<float>(radius*sin(2.0*CV_PI*i / neighbours));
				
				//取整并根据取整结果处的值进行双线性插值,得到第k个采样点的灰度值

				//1.分别对x,y进行上下取整
				int x1 = static_cast<int>(floor(x));
				int y1 = static_cast<int>(floor(y));
				int x2 = static_cast<int>(ceil(x));
				int y2 = static_cast<int>(ceil(y));

				//2.计算四个点(x1,y1),(x1,y2),(x2,y1),(x2,y2)的权重
				float u = x - x1;
				float v = y - y1;
				
				float w1 = (1 - u)*(1 - v);
				float w2 = u*(1 - v);
				float w3 = (1 - u)*v;
				float w4 = u * v;

	
				//进行双线性插值
				float neighbour = static_cast<float>(graysrc.at<uchar>(y1, x1)*w1 + graysrc.at<uchar>(y2, x1)*w2 +
					graysrc.at<uchar>(y1, x2)*w3 + graysrc.at<uchar>(y2, x2)*w4);
				//通过与中心元素值进行比较 获得LBP值
				code |= (((neighbour > centerVal)&&  abs(neighbour - centerVal) > /*std::numeric_limits<float>::epsilon()*/0.0001) << (neighbours - i - 1));

				//if (col == (368 + radius) && row == (140 + radius))
				//{
				//	cout << "1 = " << (int)graysrc.at<uchar>(y1,  x1) << " w1 = " << w1 << endl;
				//	cout << "2 = " << (int)graysrc.at<uchar>(y2,  x1) << " w2 = " << w2 << endl;
				//	cout << "3 = " << (int)graysrc.at<uchar>(y1,  x2) << " w3 = " << w3 << endl;
				//	cout << "4 = " << (int)graysrc.at<uchar>(y2,  x2) << " w4 = " << w4 << endl;
				//	cout << "W = " << w1 + w2 + w3 + w4 << endl;
				//	cout << "(y1,x1) = " << y1 << "," << x1 << endl;
				//	cout << "(y2,x2) = " << y2 << "," << x2 << endl;

				//	cout << "u = " << u << " v= " << v << endl;
				//	cout << "neighbour" << i << " = " << neighbour << endl;
				//	cout << "center = " << (int )centerVal << endl;
				//	cout << bitset<8>(code)  << endl;
				//}

			}
			elbpImage.at<uchar>(row - radius, col - radius) = code;	
			
		}
	}
	//Mat roi = elbpImage(Range(300, 325), Range(200, 205));
	//cout << roi << endl;
	imshow("LBP Image", elbpImage);
}

 效率优化版本: 

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"
#include <opencv2/xfeatures2d.hpp>
#include <bitset>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;

int radius = 2;
int maxVal = 10;

void ELBP_demo(int pos, void* data);
void mouseCallback(int event, int x, int y, int flags, void* data);
Mat graysrc;
Mat elbpImage;
int main()
{
	//待检测图像
	Mat src = imread("F:\\visual studio\\Image\\women1.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	imshow("src", src);
	//转化为灰度图
	cvtColor(src, graysrc, COLOR_BGR2GRAY);

	namedWindow("LBP Image1", WINDOW_AUTOSIZE);
	createTrackbar("radius", "LBP Image1", &radius, maxVal, ELBP_demo);
	setMouseCallback("LBP Image1", mouseCallback, 0);
	ELBP_demo(0, 0);

	waitKey(0);
}

void ELBP_demo(int pos, void* data)
{
	int height = graysrc.rows;
	int width = graysrc.cols;
	int offset = 2 * radius;
	elbpImage = Mat::zeros(height - offset, width - offset, CV_8UC1);
	int neighbours = 8; //理论上应根据半径变化,这里为了简便固定为8

	//圆形LBP特征计算,效率优化版本
	for (int i = 0; i < neighbours; i++)
	{
		//计算采样点对于中心点坐标的偏移量rx,ry;
		float rx = static_cast<float>(radius*cos(2.0 * CV_PI*i / neighbours));
		float ry = -static_cast<float>(radius*sin(2.0 * CV_PI*i / neighbours));
		if (abs(ry) < std::numeric_limits<float>::epsilon())
			ry = 0;
		if (abs(rx) < std::numeric_limits<float>::epsilon())
			rx = 0;
			

		//为双线性插值做准备
		//1.对采样点偏移量分别进行上下取整
		int x1 = static_cast<int>(floor(rx));
		int y1 = static_cast<int>(floor(ry));
		int x2 = static_cast<int>(ceil(rx));
		int y2 = static_cast<int>(ceil(ry));


		//计算权重
		float u = rx - x1;
		double v = ry - y1;
		if (u < std::numeric_limits<float>::epsilon())
			u = 0;
		if (v < std::numeric_limits<float>::epsilon())
			v = 0;
		//if (i == 6)
		//{
		//	cout << "rx = " << rx << "ry = " << ry << endl;
		//	cout << "x1 = " << x1 << " y1 = " << y1 << endl;
		//	cout << "x2 = " << x2 << " y2 = " << y2<< endl;
		//	cout << "u = " << u<< " v = " << v << endl;
		//}
		
		float w1 = (1 - u)*(1 - v);
		float w2 = u * (1 - v);
		float w3 = (1 - u) * v;
		float w4 = u * v;

		//循环处理每个像素
		for (int row = radius; row < height - radius; row++)
		{
			for (int col = radius; col < width - radius; col++)
			{
				uchar centerval = graysrc.at<uchar>(row, col);
				float neighbour = graysrc.at<uchar>(row + y1, col + x1) * w1 +
					graysrc.at<uchar>(row + y2, col + x1) * w2 +
					graysrc.at<uchar>(row + y1, col + x2) * w3 +
					graysrc.at<uchar>(row + y2, col + x2) * w4;			
				elbpImage.at<uchar>(row - radius, col - radius) |= ((neighbour > centerval && abs(neighbour-centerval) >/*std::numeric_limits<float>::epsilon()*/0.0001) << (neighbours - i - 1));
				
				// 取出一个点的值进行调试
				//if (col == (368 + radius) && row == (140 + radius))
				//{
				//	cout << "1 = " << (int)graysrc.at<uchar>(row + y1, col + x1) << " w1 = " << w1 << endl;
				//	cout << "2 = " << (int)graysrc.at<uchar>(row + y2, col + x1) << " w2 = " << w2 << endl;
				//	cout << "3 = " << (int)graysrc.at<uchar>(row + y1, col + x2) << " w3 = " << w3 << endl;
				//	cout << "4 = " << (int)graysrc.at<uchar>(row + y2, col + x2) << " w4 = " << w4 << endl;
				//	cout << "(y1,x1) = " << row + y1 << "," << col + x1 << endl;
				//	cout << "(y2,x2) = " << row + y2 << "," << col + x2 << endl;
				//	cout << "u = " << u << " v = " << v << endl;

				//	cout << "W = " << w1 + w2 + w3 + w4 << endl;
				//	cout << "neighbour" << i << " = " << neighbour << endl;
				//	cout << "center = " << (int)centerval << endl;
				//	cout << bitset<8>(elbpImage.at<uchar>(368, 140)) << endl;
				//}
					
			}
		}
	}
	//Mat roi = elbpImage(Range(300, 325), Range(200, 205));
	//cout << roi << endl;
	
	imshow("LBP Image1", elbpImage);

}

void mouseCallback(int event, int x, int y, int flags, void* data)
{
	if (flags == EVENT_FLAG_LBUTTON)
	{
		cout << "X = " << x << endl << " Y = " << y << endl;
		cout << (int )elbpImage.at<uchar>(x, y) << endl;
	}
}

效果展示:

此次获得的结果一致性更高:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值