【opencv学习笔记】LBP特征原理及代码实现

本文为转载文章,只转载了部分,若想查看跟多的LBP的改进版本,可以点击原文链接;

一、LBP特征的背景介绍

LBP局部二值模式,英文全称:Local Binary Pattern,是一种用来描述图像局部特征的算子,LBP特征具有灰度不变性和旋转不变性等显著优点。LBP特征比较出名的应用是用在人脸识别和目标检测中,在计算机视觉开源库Opencv中有使用LBP特征进行人脸识别的接口,也有用LBP特征训练目标检测分类器的方法,Opencv实现了LBP特征的计算,但没有提供一个单独的计算LBP特征的接口。所以需要我们自己去写源代码;

二、LBP特征的原理

1、原始LBP特征描述及计算方法
原始的LBP算子定义在像素33的邻域内,以邻域中心像素为阈值,相邻的8个像素的灰度值与邻域中心的像素值进行比较,若周围像素大于中心像素值,则该像素点的位置被标记为1,否则为0。这样,33邻域内的8个点经过比较可产生8位二进制数,将这8位二进制数依次排列形成一个二进制数字,这个二进制数字就是中心像素的LBP值,LBP值共有28
种可能,因此LBP值有256种。中心像素的LBP值反映了该像素周围区域的纹理信息。
计算LBP特征的图像必须是灰度图,如果是彩色图,需要先转换成灰度图。
上述过程用图像表示为:在这里插入图片描述
在这里插入图片描述
将上述过程用公式表示为:
在这里插入图片描述(xc,yc)为中心像素的坐标,p为邻域的第p个像素,ip为邻域像素的灰度值,ic为中心像素的灰度值,s(x)

为符号函数

原始LBP特征计算代码(Opencv下):

int main(int argc, char** argv) {
	src = imread("E:/tuku/text1.jpg");
	if (src.empty()) {
		cout << "can't find this picture...";
		return -1;
	}
	imshow("input", src);
	// convert to gray
	cvtColor(src, gray_src, COLOR_BGR2GRAY);
	int width = gray_src.cols;
	int height = gray_src.rows;
	//基本LBP演示
	Mat lbgImage = 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;
			lbgImage.at<uchar>(row-1, col-1) = code;
		}
	}
	imshow("out put", lbgImage);

效果图:
在这里插入图片描述

2、LBP特征的改进版本

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

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

由于原始LBP特征使用的是固定邻域内的灰度值,因此当图像的尺度发生变化时,LBP特征的编码将会发生错误,LBP特征将不能正确的反映像素点周围的纹理信息,因此研究人员对其进行了改进[3]。基本的 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<opencv2\opencv.hpp>
# include <iostream>
# include <math.h>
using namespace std;
using namespace cv;
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("E:/tuku/text1.jpg");
	if (src.empty()) {
		cout << "can't find this picture...";
		return -1;
	}
	imshow("input", src);
	// convert to gray
	cvtColor(src, gray_src, COLOR_BGR2GRAY);
	int width = gray_src.cols;
	int height = gray_src.rows;
	//基本LBP演示
	Mat lbgImage = 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;
			lbgImage.at<uchar>(row-1, col-1) = code;//其实这里就相当于半径等于1
		}
	}
	imshow("out put", lbgImage);
	//ELBP演示
	namedWindow("ELBP out put", 1);
	createTrackbar("ELBP radius:", "ELBP out put", &current_radius, max_count, ELBP_Demo);
	ELBP_Demo(0, 0);
	waitKey(0);
	return 0;
}
void ELBP_Demo(int, void*) {
	int offset = current_radius * 2;
	//LBP特征图像的行数和列数的计算要准确(这里确定它的大小)
	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++) {
	  //计算采样点对于中心点坐标的偏移量x,y
		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));//floor向下取整,即不大于要求值的最大的那个整数值
		int fy = static_cast<int>(floor(y));
		int cx = static_cast<int>(ceil(x));//向上取整计算,不小于最接近的整数。
		int cy = static_cast<int>(ceil(y));

		float tx = x - fx;
		float ty = y - fy;

		float w1 = (1 - tx)*(1 - ty);
		float w2 = ty * (1 - tx);
		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 out put", elbpImage);
}

效果图:
在这里插入图片描述

2.2 旋转不变LBP特征

从上面可以看出,上面的LBP特征具有灰度不变性,但还不具备旋转不变性,因此研究人员又在上面的基础上进行了扩展,提出了具有旋转不变性的LBP特征。
首先不断的旋转圆形邻域内的LBP特征,根据选择得到一系列的LBP特征值,从这些LBP特征值选择LBP特征值最小的作为中心像素点的LBP特征。具体做法如下图所示:

在这里插入图片描述
如上图所示(图片摘自网络),原始LBP得到的数值转化为二进制编码,对它进行循环移位操作,有8种情况(包括自身)。取其中最小的一个值,比如图中就对应着15,这个值是旋转不变的,因为对图像做旋转操作等价与上面8种移位的过程了,而8种情况都对应同一个值,即8个值中的最小值15,即拥有了旋转不变特性

//旋转不变圆形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<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++)
            {
                //获得中心像素点的灰度值
                _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<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;
        }
    }
}

radius = 3,neighbors = 8,最后一幅是旋转不变LBP特征
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值