OpenCV-图像特征提取算法3_LBP

一,简介

LBP(Local Binary Pattern,局部二值模式)是一种用来描述图像局部纹理特征的算子;它具有旋转不变性和灰度不变性等显著的优点。它是首先由T. Ojala, M.Pietikäinen, 和 D. Harwood 在1994年提出,用于纹理特征提取。

二、原始LBP特征描述及计算方法

2.1,算法描述

       1)对图像中的所有点,以该点为中心,取3x3的邻域窗口;

       2)将8-邻域像素值与中心点像素值进行比较,大于或等于中心像素标记为1,否则标记为0;

       3)将周围0-1序列,以一定的顺序排列,成一个8位的无符号的二进制数,转化成整数;

       4)这个整数就是表征这个窗口的LBP值。

       LBP值共有2的8次方种可能,因此LBP值有256种。中心像素的LBP值反映了该像素周围区域的纹理信息。 以上,便是最基本的LBP算子。由于直接利用的灰度比较,所以其具有灰度不变性;但是,有两个很明显的缺点:

     1)产生的二进制模式多;

     2)不具有旋转不变性

2.2,实现代码

实现代码部分整理了好久。

//原始LBP特征计算
Mat LBP(Mat img)
{
	Mat result;
	result.create(img.rows - 2, img.cols - 2, img.type());

	result.setTo(0);

	for (int i = 1; i<img.rows - 1; i++)
	{
		for (int j = 1; j<img.cols - 1; j++)
		{
			uchar center = img.at<uchar>(i, j);
			uchar code = 0;
			code |= (img.at<uchar>(i - 1, j - 1) >= center) << 7;
			code |= (img.at<uchar>(i - 1, j) >= center) << 6;
			code |= (img.at<uchar>(i - 1, j + 1) >= center) << 5;
			code |= (img.at<uchar>(i, j + 1) >= center) << 4;
			code |= (img.at<uchar>(i + 1, j + 1) >= center) << 3;
			code |= (img.at<uchar>(i + 1, j) >= center) << 2;
			code |= (img.at<uchar>(i + 1, j - 1) >= center) << 1;
			code |= (img.at<uchar>(i, j - 1) >= center) << 0;
			result.at<uchar>(i - 1, j - 1) = code;
		}
	}
	return result;
}
//测试
void CCutImageVS2013Dlg::OnBnClickedTestButton1()
{
	Mat image = imread("D:\\test\\hog.png", 0);
	Size dsize = Size(image.cols * 0.5, image.rows * 0.5);
	resize(image, image, dsize);
	Mat dst = LBP(image);
	imshow("原图", image);
	imshow("LBP", dst);
	waitKey(0);
}

2.3,处理效果

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

3.1,算法描述

        不难注意到原始的LBP算子仅仅只是覆盖了很小的一个3×3领域的范围,这显然不能满足提取不同尺寸纹理特征的需求。为了适应不同尺度的纹理特征,并达到灰度和旋转不变性的要求,Ojala等对LBP 算子进行了改进,将 3×3邻域扩展到任意邻域,并用圆形邻域代替了正方形邻域。改进后的LBP 算子允许在半径为 R 的圆形邻域内有任意多个像素点。

       假设半径为R的圆形区域内含有P个采样点的LBP算子(1,2指的是半径,8,16指的是采样点数): 

比如下图定义了一个5X5的领域:

上图中的8个黑色的采样点,R是采样半径,p是第p个采样点,P是采样数目。对于给定中心点(x_{c},y_{c}),其邻域像素位置为(x_{p},y_{p}),p∈P,其采样点(x_{p},y_{p})用如下公式计算:

                                                                                   x_{p}= x_{c}+R*cos(2\pi p/P)                               

                                                                                   y_{p}= y_{c}+R*sin(2\pi p/P)

通过上式可以计算任意个采样点的坐标,但是计算得到的坐标未必完全是整数,所以可以通过双线性插值来得到该采样点的像素值:

 3.2,实现代码

//圆形LBP特征计算,这种方法适于理解,但在效率上存在问题,声明时默认neighbors=8
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; i<src.rows - radius; i++)
	{
		for (int j = radius; j<src.cols - radius; j++)
		{
			//获得中心像素点的灰度值
			int center = src.at<uchar>(i, j);
			unsigned char lbpCode = 0;
			for (int k = 0; k<neighbors; 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<uchar>(x1, y1) * w1 + src.at<uchar>(x1, y2) *w2 \
					+ src.at<uchar>(x2, y1) * w3 + src.at<uchar>(x2, y2) *w4;
				//通过比较获得LBP值,并按顺序排列起来
				lbpCode |= (neighbor>center) << (neighbors - k - 1);
			}
			dst.at<uchar>(i - radius, j - radius) = lbpCode;
		}
	}
}

//圆形LBP特征计算,效率优化版本,声明时默认neighbors=8
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<neighbors; 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; i<src.rows - radius; i++)
		{
			for (int j = radius; j<src.cols - radius; j++)
			{
				//获得中心像素点的灰度值
				int center = src.at<uchar>(i, j);
				//根据双线性插值公式计算第k个采样点的灰度值
				float neighbor = src.at<uchar>(i + x1, j + y1) * w1 + src.at<uchar>(i + x1, j + y2) *w2 \
					+ src.at<uchar>(i + x2, j + y1) * w3 + src.at<uchar>(i + x2, j + y2) *w4;
				//LBP特征图像的每个邻居的LBP值累加,累加通过与操作完成,对应的LBP值通过移位取得
				dst.at<uchar>(i - radius, j - radius) |= (neighbor>center) << (neighbors - k - 1);
			}
		}
	}
}


//一个窗口显示多幅图像
//const std::string& MultiShow_WinName //窗口名称
//const vector<Mat>& SrcImg_V   //图片数组
//CvSize SubPlot                //显示图像排列方式,比如cvSize(4, 1)表示1行4列
//CvSize ImgMax_Size            //图像尺寸
//bool bgray = true             //true表示显示灰度图,false表示显示彩色图,默认为灰度图
void MultiImage_OneWin(const std::string& MultiShow_WinName, const vector<Mat>& SrcImg_V, CvSize SubPlot, CvSize ImgMax_Size,bool bgray=true)
{

	//************* Usage *************//
	//vector<Mat> imgs(4);
	//imgs[0] = imread("F:\\SA2014.jpg");
	//imgs[1] = imread("F:\\SA2014.jpg");
	//imgs[2] = imread("F:\\SA2014.jpg");
	//imgs[3] = imread("F:\\SA2014.jpg");
	//MultiImage_OneWin("T", imgs, cvSize(2, 2), cvSize(400, 280));

	//Window's image
	Mat Disp_Img;
	//Width of source image
	CvSize Img_OrigSize = cvSize(SrcImg_V[0].cols, SrcImg_V[0].rows);
	//******************** Set the width for displayed image ********************//
	//Width vs height ratio of source image
	float WH_Ratio_Orig = Img_OrigSize.width / (float)Img_OrigSize.height;
	CvSize ImgDisp_Size = cvSize(100, 100);
	if (Img_OrigSize.width > ImgMax_Size.width)
		ImgDisp_Size = cvSize(ImgMax_Size.width, (int)ImgMax_Size.width / WH_Ratio_Orig);
	else if (Img_OrigSize.height > ImgMax_Size.height)
		ImgDisp_Size = cvSize((int)ImgMax_Size.height*WH_Ratio_Orig, ImgMax_Size.height);
	else
		ImgDisp_Size = cvSize(Img_OrigSize.width, Img_OrigSize.height);
	//******************** Check Image numbers with Subplot layout ********************//
	int Img_Num = (int)SrcImg_V.size();
	if (Img_Num > SubPlot.width * SubPlot.height)
	{
		cout << "Your SubPlot Setting is too small !" << endl;
		exit(0);
	}
	//******************** Blank setting ********************//
	CvSize DispBlank_Edge = cvSize(80, 60);
	CvSize DispBlank_Gap = cvSize(15, 15);
	//******************** Size for Window ********************//
	if (bgray)//灰度图
	{
		Disp_Img.create(Size(ImgDisp_Size.width*SubPlot.width + DispBlank_Edge.width + (SubPlot.width - 1)*DispBlank_Gap.width,
			ImgDisp_Size.height*SubPlot.height + DispBlank_Edge.height + (SubPlot.height - 1)*DispBlank_Gap.height), CV_8UC1);//此处修改三通道或者单通道图像
	}
	else //彩色图
	{
		Disp_Img.create(Size(ImgDisp_Size.width*SubPlot.width + DispBlank_Edge.width + (SubPlot.width - 1)*DispBlank_Gap.width,
			ImgDisp_Size.height*SubPlot.height + DispBlank_Edge.height + (SubPlot.height - 1)*DispBlank_Gap.height), CV_8UC3);//此处修改三通道或者单通道图像
	}
	
	Disp_Img.setTo(0);//Background
	//Left top position for each image
	int EdgeBlank_X = (Disp_Img.cols - (ImgDisp_Size.width*SubPlot.width + (SubPlot.width - 1)*DispBlank_Gap.width)) / 2;
	int EdgeBlank_Y = (Disp_Img.rows - (ImgDisp_Size.height*SubPlot.height + (SubPlot.height - 1)*DispBlank_Gap.height)) / 2;
	CvPoint LT_BasePos = cvPoint(EdgeBlank_X, EdgeBlank_Y);
	CvPoint LT_Pos = LT_BasePos;

	//Display all images
	for (int i = 0; i < Img_Num; i++)
	{
		//Obtain the left top position
		if ((i%SubPlot.width == 0) && (LT_Pos.x != LT_BasePos.x))
		{
			LT_Pos.x = LT_BasePos.x;
			LT_Pos.y += (DispBlank_Gap.height + ImgDisp_Size.height);
		}
		//Writting each to Window's Image
		Mat imgROI = Disp_Img(Rect(LT_Pos.x, LT_Pos.y, ImgDisp_Size.width, ImgDisp_Size.height));
		resize(SrcImg_V[i], imgROI, Size(ImgDisp_Size.width, ImgDisp_Size.height));

		LT_Pos.x += (DispBlank_Gap.width + ImgDisp_Size.width);
	}

	//Get the screen size of computer
	int Scree_W = GetSystemMetrics(SM_CXSCREEN);
	int Scree_H = GetSystemMetrics(SM_CYSCREEN);

	cvNamedWindow(MultiShow_WinName.c_str(), CV_WINDOW_AUTOSIZE);
	cvMoveWindow(MultiShow_WinName.c_str(), (Scree_W - Disp_Img.cols) / 2, (Scree_H - Disp_Img.rows) / 2);//Centralize the window
	cvShowImage(MultiShow_WinName.c_str(), &(IplImage(Disp_Img)));
	cvWaitKey(0);
	cvDestroyWindow(MultiShow_WinName.c_str());
}


//测试
void CCutImageVS2013Dlg::OnBnClickedTestButton1()
{
	Mat image = imread("D:\\test\\hog.png", 0);
	Size dsize = Size(image.cols * 0.25, image.rows * 0.25);
	resize(image, image, dsize);

	//-------------原始LBP---------------
	//Mat dst = LBP(image);
	//imshow("原图", image);
	//imshow("LBP", dst);
	//waitKey(0);

	if (image.empty()){
		cout << "图像加载失败" << endl;
		return;
	}
	vector<Mat> imageConvert(4); //改变亮度后的图
	vector<Mat> dst(4);          //LBP处理后的图
	image.convertTo(imageConvert[0], image.type(), 1, -30); //调节图像亮度
	image.convertTo(imageConvert[1], image.type(), 1, -10); //调节图像亮度
	image.convertTo(imageConvert[2], image.type(), 1, 10); //调节图像亮度
	image.convertTo(imageConvert[3], image.type(), 1, 30); //调节图像亮度

	//getCircularLBPFeature(imageConvert[0], dst[0], 3, 8); //LBP处理
	//getCircularLBPFeature(imageConvert[1], dst[1], 3, 8); //LBP处理
	//getCircularLBPFeature(imageConvert[2], dst[2], 3, 8); //LBP处理
	//getCircularLBPFeature(imageConvert[3], dst[3], 3, 8); //LBP处理

	getCircularLBPFeatureOptimization(imageConvert[0], dst[0], 3, 8); //LBP处理
	getCircularLBPFeatureOptimization(imageConvert[1], dst[1], 3, 8); //LBP处理
	getCircularLBPFeatureOptimization(imageConvert[2], dst[2], 3, 8); //LBP处理
	getCircularLBPFeatureOptimization(imageConvert[3], dst[3], 3, 8); //LBP处理

	MultiImage_OneWin("原图", imageConvert, cvSize(4, 1), cvSize(image.cols, image.rows), true);
	MultiImage_OneWin("圆形LBP处理", dst, cvSize(4, 1), cvSize(image.cols, image.rows), true);
	return;
}

3.3,处理效果

通过LBP特征的定义可以看出,LBP特征对光照变化是鲁棒的,其效果如下图所示: 

四、旋转不变LBP特征

4.1,算法描述

       由于LBP的二进制模式是以一定的方向、顺序进行编码的,所以当图像发生旋转时,按这种编码的话,LBP值会发生改变,因此是不具有旋转不变性的。Maenpaa等人提出了具有旋转不变性的LBP算子。

       解决办法是:不断旋转邻域得到一系列的LBP值,取其中最小值作为该邻域的LBP值。旋转过程实质上就是对二进制模式进行循环移位的过程。

一共36个旋转不变的LBP编码模式,如下图所示: 

       通过引入旋转不变的定义,使LBP算子更具鲁棒性。但这也是LBP算子丢失了方向信息。在很多场合,方向信息非常重要;然而,在纹理图像分析中,LBP依然被证明是有效的。 

4.2,实现代码

//旋转不变圆形LBP特征计算,声明时默认neighbors=8
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<neighbors; 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; i<src.rows - radius; i++)
		{
			for (int j = radius; j<src.cols - radius; j++)
			{
				//获得中心像素点的灰度值
				int center = src.at<uchar>(i, j);
				//根据双线性插值公式计算第k个采样点的灰度值
				float neighbor = src.at<uchar>(i + x1, j + y1) * w1 + src.at<uchar>(i + x1, j + y2) *w2 \
					+ src.at<uchar>(i + x2, j + y1) * w3 + src.at<uchar>(i + x2, j + y2) *w4;
				//LBP特征图像的每个邻居的LBP值累加,累加通过与操作完成,对应的LBP值通过移位取得
				dst.at<uchar>(i - radius, j - radius) |= (neighbor>center) << (neighbors - k - 1);
			}
		}
	}
	//进行旋转不变处理
	for (int i = 0; i<dst.rows; i++)
	{
		for (int j = 0; j<dst.cols; j++)
		{
			unsigned char currentValue = dst.at<uchar>(i, j);
			unsigned char minValue = currentValue;
			for (int k = 1; k<neighbors; k++)
			{
				//循环左移
				unsigned char temp = (currentValue >> (neighbors - k)) | (currentValue << k);
				if (temp < minValue)
				{
					minValue = temp;
				}
			}
			dst.at<uchar>(i, j) = minValue;
		}
	}
}

//测试各种LBP
void TestCircularLBP()
{
	Mat image = imread("D:\\test\\hog.png", 0);
	Size dsize = Size(image.cols * 0.25, image.rows * 0.25);
	resize(image, image, dsize);

	//-------------1原始LBP---------------
	//Mat dst = LBP(image);
	//imshow("原图", image);
	//imshow("LBP", dst);
	//waitKey(0);

	if (image.empty()){
		cout << "图像加载失败" << endl;
		return;
	}
	vector<Mat> imageConvert(4); //改变亮度后的图
	vector<Mat> dst(4);          //LBP处理后的图
	image.convertTo(imageConvert[0], image.type(), 1, -30); //调节图像亮度
	image.convertTo(imageConvert[1], image.type(), 1, -10); //调节图像亮度
	image.convertTo(imageConvert[2], image.type(), 1, 10); //调节图像亮度
	image.convertTo(imageConvert[3], image.type(), 1, 30); //调节图像亮度


	//-------------2圆形LBP---------------
	//getCircularLBPFeature(imageConvert[0], dst[0], 3, 8); //LBP处理
	//getCircularLBPFeature(imageConvert[1], dst[1], 3, 8); //LBP处理
	//getCircularLBPFeature(imageConvert[2], dst[2], 3, 8); //LBP处理
	//getCircularLBPFeature(imageConvert[3], dst[3], 3, 8); //LBP处理

	//getCircularLBPFeatureOptimization(imageConvert[0], dst[0], 3, 8); //LBP处理
	//getCircularLBPFeatureOptimization(imageConvert[1], dst[1], 3, 8); //LBP处理
	//getCircularLBPFeatureOptimization(imageConvert[2], dst[2], 3, 8); //LBP处理
	//getCircularLBPFeatureOptimization(imageConvert[3], dst[3], 3, 8); //LBP处理
	
	//-------------3旋转LBP---------------
	getRotationInvariantLBPFeature(imageConvert[0], dst[0], 3, 8); //LBP处理
	getRotationInvariantLBPFeature(imageConvert[1], dst[1], 3, 8); //LBP处理
	getRotationInvariantLBPFeature(imageConvert[2], dst[2], 3, 8); //LBP处理
	getRotationInvariantLBPFeature(imageConvert[3], dst[3], 3, 8); //LBP处理

	MultiImage_OneWin("原图", imageConvert, cvSize(4, 1), cvSize(image.cols, image.rows), true);
	MultiImage_OneWin("旋转不变性LBP处理", dst, cvSize(4, 1), cvSize(image.cols, image.rows), true);
	return;
}

//测试
void CCutImageVS2013Dlg::OnBnClickedTestButton1()
{
	TestCircularLBP();
}

4.3,处理效果

五、LBP等价模式

5.1,算法描述

       也称为Uniform Pattern LBP特征。原始的LBP算子,随着邻域内采样点数的增加,二进制模式的种类是急剧增加的。

       对于半径为R的圆形区域内含有P个采样点的LBP算子将会产P^2中模式,如5X5领域内20个采样点,有2^20=104857种二进制模式。过多的二值模式对于特征的提取以及信息的存取都是不利的。例如,将LBP算子用于人脸识别时,常采用的LBP模式的统计直方图来表达人脸信息,而较多的模式种类将使得数据量过大,且直方图过于稀疏。因此,需要对原始LBP模式进行降维,使得数据量减少的情况下能最好的代表图像的信息。

        旋转LBP模式同样存在缺陷,大量的实验证明LBP模式的36种情况在一幅图像中分布出现的频率差异较大,得到的效果不是很好。因此人们提出了uniform LBP。
        “等价模式”被定义为:当某个LBP所对应的循环二进制数从0到1或者从1到0最多有两次跳变时,该LBP所对应的二进制就称为一个等价模式。在实际图像中,计算出来的大部分值都在等价模式之中,可达百分之90%以上。uniformLBP模式的个数为P(P-1)+2,P为领域像素点个数。对于8个采样点,uniform形式有58种输出, 其他的所有值为第59类,这样直方图从原来的256维降到了59维,并且可以减少高频噪声带来的影响。
uniform形式的58种LBP模式如下图所示:
 

5.2,实现代码

//计算跳变次数
#include<bitset>
int getHopTimes(int n)
{
	int count = 0;
	bitset<8> binaryCode = n;
	for (int i = 0; i<8; i++)
	{
		if (binaryCode[i] != binaryCode[(i + 1) % 8])
		{
			count++;
		}
	}
	return count;
}

//4等价模式LBP特征计算
void getUniformPatternLBPFeature(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);
	//LBP特征值对应图像灰度编码表,直接默认采样点为8位
	uchar temp = 1;
	uchar table[256] = { 0 };
	for (int i = 0; i<256; i++)
	{
		if (getHopTimes(i)<3)
		{
			table[i] = temp;
			temp++;
		}
	}
	//是否进行UniformPattern编码的标志
	bool flag = false;
	//计算LBP特征图
	for (int k = 0; k<neighbors; k++)
	{
		if (k == neighbors - 1)
		{
			flag = true;
		}
		//计算采样点对于中心点坐标的偏移量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; i<src.rows - radius; i++)
		{
			for (int j = radius; j<src.cols - radius; j++)
			{
				//获得中心像素点的灰度值
				int center = src.at<uchar>(i, j);
				//根据双线性插值公式计算第k个采样点的灰度值
				float neighbor = src.at<uchar>(i + x1, j + y1) * w1 + src.at<uchar>(i + x1, j + y2) *w2 \
					+ src.at<uchar>(i + x2, j + y1) * w3 + src.at<uchar>(i + x2, j + y2) *w4;
				//LBP特征图像的每个邻居的LBP值累加,累加通过与操作完成,对应的LBP值通过移位取得
				dst.at<uchar>(i - radius, j - radius) |= (neighbor>center) << (neighbors - k - 1);
				//进行LBP特征的UniformPattern编码
				if (flag)
				{
					dst.at<uchar>(i - radius, j - radius) = table[dst.at<uchar>(i - radius, j - radius)];
				}
			}
		}
	}
}

//测试各种LBP
void TestCircularLBP()
{
	Mat image = imread("D:\\test\\hog.png", 0);
	Size dsize = Size(image.cols * 0.25, image.rows * 0.25);
	resize(image, image, dsize);

	//-------------1原始LBP---------------
	//Mat dst = LBP(image);
	//imshow("原图", image);
	//imshow("LBP", dst);
	//waitKey(0);

	if (image.empty()){
		cout << "图像加载失败" << endl;
		return;
	}
	vector<Mat> imageConvert(4); //改变亮度后的图
	vector<Mat> dst(4);          //LBP处理后的图
	image.convertTo(imageConvert[0], image.type(), 1, -30); //调节图像亮度
	image.convertTo(imageConvert[1], image.type(), 1, -10); //调节图像亮度
	image.convertTo(imageConvert[2], image.type(), 1, 10); //调节图像亮度
	image.convertTo(imageConvert[3], image.type(), 1, 30); //调节图像亮度


	//-------------2圆形LBP---------------
	//getCircularLBPFeature(imageConvert[0], dst[0], 3, 8); //LBP处理
	//getCircularLBPFeature(imageConvert[1], dst[1], 3, 8); //LBP处理
	//getCircularLBPFeature(imageConvert[2], dst[2], 3, 8); //LBP处理
	//getCircularLBPFeature(imageConvert[3], dst[3], 3, 8); //LBP处理

	//getCircularLBPFeatureOptimization(imageConvert[0], dst[0], 3, 8); //LBP处理
	//getCircularLBPFeatureOptimization(imageConvert[1], dst[1], 3, 8); //LBP处理
	//getCircularLBPFeatureOptimization(imageConvert[2], dst[2], 3, 8); //LBP处理
	//getCircularLBPFeatureOptimization(imageConvert[3], dst[3], 3, 8); //LBP处理
	
	//-------------3旋转LBP---------------
	//getRotationInvariantLBPFeature(imageConvert[0], dst[0], 3, 8); //LBP处理
	//getRotationInvariantLBPFeature(imageConvert[1], dst[1], 3, 8); //LBP处理
	//getRotationInvariantLBPFeature(imageConvert[2], dst[2], 3, 8); //LBP处理
	//getRotationInvariantLBPFeature(imageConvert[3], dst[3], 3, 8); //LBP处理
	
	//-------------4等价LBP---------------
	getUniformPatternLBPFeature(imageConvert[0], dst[0], 3, 8); //LBP处理
	getUniformPatternLBPFeature(imageConvert[1], dst[1], 3, 8); //LBP处理
	getUniformPatternLBPFeature(imageConvert[2], dst[2], 3, 8); //LBP处理
	getUniformPatternLBPFeature(imageConvert[3], dst[3], 3, 8); //LBP处理

	MultiImage_OneWin("原图", imageConvert, cvSize(4, 1), cvSize(image.cols, image.rows), true);
	MultiImage_OneWin("旋转不变性LBP处理", dst, cvSize(4, 1), cvSize(image.cols, image.rows), true);
	return;
}

//测试
void CCutImageVS2013Dlg::OnBnClickedTestButton1()
{
	TestCircularLBP();
}

5.3,处理效果

 

欢迎扫码关注我的微信公众号

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值