图像处理之LBP特征(C++)

图像处理之LBP特征(C++)



前言

LBP(Local Binary Pattern)指局部二值模式,是一种用来描述图像局部特征的算子,LBP特征具有灰度不变性和旋转不变性等显著优点。由于LBP特征计算简单、效果较好,因此LBP特征在计算机视觉的许多领域都得到了广泛的应用。


一、LBP特征描述

原始的LBP算子定义为在33的窗口内,以窗口中心像素为阈值,将相邻的8个像素的灰度值与其进行比较,若周围像素值大于中心像素值,则该像素点的位置被标记为1,否则为0。这样,33邻域内的8个点经比较可产生8位二进制数(通常转换为十进制数即LBP码,共256种),然后按照顺时针依次排列形成一个二进制数字就是窗口中心像素点的LBP值,并用这个值来反映该区域的纹理信息。如下图:
LBP原理示意图
公式为:
LBP计算公式
参数解释:

  1. (xc,yc)为窗口中心的像素坐标;
  2. ic为未进行LBP计算前的窗口中心(xc,yc)像素值;
  3. ip为窗口中心邻域的灰度值;
  4. p为邻域的编码(0、1、2…7);
  5. s(x)是符号函数,当x>=0,s(x)=1;否则,s(x)=0。即,如下图
    符号函数

二、圆形LBP特征

基本的 LBP 算子的最大缺陷在于它只覆盖了一个固定半径范围内的小区域,这显然不能满足不同尺寸和频率纹理的需要。为了适应不同尺度的纹理特征,并达到灰度和旋转不变性的要求,对 LBP 算子进行了改进,将 3×3 邻域扩展到任意邻域,并用圆形邻域代替了正方形邻域,改进后的 LBP 算子允许在半径为 R 的圆形邻域内有任意多个像素点(P为采样点的个数)。如图。圆形LBP
对于给定中心点(xc,yc),其邻域像素位置为(xp,yp),p∈P,其采样点(xp,yp)用如下公式计算:

采样点坐标计算
参数解释:
P为采样点的总个数;
R是采样半径;
p是第p个采样点。
对于采样点未落在整数坐标上,进行双线性插值计算。
双线性插值

三、LBPH特征描述

LBP的应用中,如纹理分类、人脸分析等,一般都不将LBP图谱作为特征向量用于分类识别,而是采用LBP特征谱的统计直方图作为特征向量用于分类识别。
可以将一幅图片划分为若干的子区域,对每个子区域内的每个像素点都提取LBP特征,然后,在每个子区域内建立LBP特征的统计直方图。如此一来,每个子区域,就可以用一个统计直方图来进行描述;整个图片就由若干个统计直方图组成;
实现步骤:
1.计算图像的LBP特征图像(此处计算圆形LBP特征);
2.将LBP特征图像进行分块,默认将LBP特征图像分成8行8列。
3.计算每块区域特征图像的直方图cell_LBPH,将直方图进行归一化,直方图大小为1∗numPatterns,numPatterns(圆形LBP为256)为特征值模式;
4.将上面计算的每块区域特征图像的直方图按分块的空间顺序依次排列成一行,形成整幅图的LBP特征向量,特征向量的大小为1∗numPatterns∗64,64为特征图像分块数量—— 直方图是图信息。(直方图的横轴是LBP值(整数范围0~2p),纵轴是每个值出现的频次,因此直方图本身可以用一个一个向量来表示,例:向量(1,3,5,2,4,…)表示每个值出现的次数{0:1,1:3,2:5,3:2,4:4,…},dict中的key为LBP值,value为次数)。

四、代码实现

1.LBP实现

#include <iostream>
#include <opencv.hpp>
using namespace std;

/*
* @param cv::Mat src	输入图像
* @param cv::Mat dst 	输出图像
* @brief 计算原始的LBP特征
*/
void getOriginLBP(cv::Mat& src, cv::Mat& dst)
{
	dst.create(cv::Size(src.cols - 2, src.rows - 2), CV_8UC1);
	dst.setTo(0);
	unsigned char temp = 0;
	for (int i = 1; i < src.rows - 1; i++)
		for (int j = 1; j < src.cols - 1; j++)
		{
			temp = 0;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i - 1, j - 1)) << 7;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i - 1, j)) << 6;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i - 1, j + 1)) << 5;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i, j + 1)) << 4;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i + 1, j + 1)) << 3;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i + 1, j)) << 2;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i + 1, j - 1)) << 1;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i, j - 1)) << 0;
			dst.at<uchar>(i - 1, j - 1) = temp;
		}
}

int main()
{
	//读取图片
	string filepath = "F://work_study//algorithm_demo//baby.jpg";
	cv::Mat src = cv::imread(filepath, cv::IMREAD_GRAYSCALE);
	if (src.empty())
	{
		std::cout << "imread error" << std::endl;
		return -1;
	}
	cv::Mat dst;
	getOriginLBP(src, dst);

	cv::imshow("dst", dst);
	cv::waitKey(0);
	return 0;
}

LBP结果图

2.圆形LBP实现

#include <iostream>
#include <opencv.hpp>
using namespace std;


/*
* @param cv::Mat src	输入图像
* @param cv::Mat dst 	输出图像
* @param int radius		采样半径
* @param int neighbors	采样点个数
* @brief 计算圆形的LBP特征
*/
void getCircleLBP(cv::Mat& src, cv::Mat& dst, int radius, int neighbors=8)
{
	//重要:LBP特征图像的行数和列数
	dst.create(cv::Size(src.cols - 2 * radius, src.rows - 2 * radius), CV_8UC1);
	dst.setTo(0);
	for (int m = 0; m < neighbors; m++)
	{
		//计算x,y的偏移量
		float x_offset = radius * cos(2 * CV_PI * m / neighbors);
		float y_offset = -radius * sin(2 * CV_PI * m / neighbors);
		//进行双线性插值
		//计算各个插值点最近的坐标,对采样点的偏移量进行上下取整
		int x1 = floor(x_offset);
		int x2 = ceil(x_offset);
		int y1 = floor(y_offset);
		int y2 = ceil(y_offset);
		//映射到0-1之间
		float x_scale = x_offset - x1;
		float y_scale = y_offset - y1;
		//计算权重系数
		float w1 = (1 - x_scale) * (1 - y_scale);
		float w2 = x_scale * (1 - y_scale);
		float w3 = (1 - x_scale) * y_scale;
		float w4 = x_scale * y_scale;
		//循环处理每个像素
		for (int i = radius; i < src.rows - radius; i++)
			for (int j = radius; j < src.rows - radius; j++)
			{
				//计算经过二次插值得到的灰度值
				float temp = 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值累加
				dst.at<uchar>(i - radius, j - radius) += ((temp > src.at<uchar>(i, j)) << (neighbors - m - 1));
			}
	}
}

int main()
{
	//读取图片
	string filepath = "F://work_study//algorithm_demo//baby.jpg";
	cv::Mat src = cv::imread(filepath, cv::IMREAD_GRAYSCALE);
	if (src.empty())
	{
		std::cout << "imread error" << std::endl;
		return -1;
	}
	cv::Mat dst;
	getCircleLBP(src, dst, 3);

	cv::imshow("dst", dst);
	cv::waitKey(0);
	return 0;
}

Circlel LBP结果图

3.LBPH实现

#include <iostream>
#include <opencv.hpp>
using namespace std;


/*
* @param cv::Mat src	输入图像
* @param cv::Mat dst 	输出图像
* @brief 计算原始的LBP特征
*/
void getOriginLBP(cv::Mat& src, cv::Mat& dst)
{
	dst.create(cv::Size(src.cols - 2, src.rows - 2), CV_8UC1);
	dst.setTo(0);
	unsigned char temp = 0;
	for (int i = 1; i < src.rows - 1; i++)
		for (int j = 1; j < src.cols - 1; j++)
		{
			temp = 0;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i - 1, j - 1)) << 7;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i - 1, j)) << 6;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i - 1, j + 1)) << 5;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i, j + 1)) << 4;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i + 1, j + 1)) << 3;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i + 1, j)) << 2;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i + 1, j - 1)) << 1;
			temp += (src.at<uchar>(i, j) < src.at<uchar>(i, j - 1)) << 0;
			dst.at<uchar>(i - 1, j - 1) = temp;
		}
}

/*
* @param cv::Mat src	输入图像
* @param cv::Mat dst 	输出图像
* @param int radius		采样半径
* @param int neighbors	采样点个数
* @brief 计算圆形的LBP特征
*/
void getCircleLBP(cv::Mat& src, cv::Mat& dst, int radius, int neighbors=8)
{
	//重要:LBP特征图像的行数和列数
	dst.create(cv::Size(src.cols - 2 * radius, src.rows - 2 * radius), CV_8UC1);
	dst.setTo(0);
	for (int m = 0; m < neighbors; m++)
	{
		//计算x,y的偏移量
		float x_offset = radius * cos(2 * CV_PI * m / neighbors);
		float y_offset = -radius * sin(2 * CV_PI * m / neighbors);
		//进行双线性插值
		//计算各个插值点最近的坐标,对采样点的偏移量进行上下取整
		int x1 = floor(x_offset);
		int x2 = ceil(x_offset);
		int y1 = floor(y_offset);
		int y2 = ceil(y_offset);
		//映射到0-1之间
		float x_scale = x_offset - x1;
		float y_scale = y_offset - y1;
		//计算权重系数
		float w1 = (1 - x_scale) * (1 - y_scale);
		float w2 = x_scale * (1 - y_scale);
		float w3 = (1 - x_scale) * y_scale;
		float w4 = x_scale * y_scale;
		//循环处理每个像素
		for (int i = radius; i < src.rows - radius; i++)
			for (int j = radius; j < src.rows - radius; j++)
			{
				//计算经过二次插值得到的灰度值
				float temp = 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值累加
				dst.at<uchar>(i - radius, j - radius) += ((temp > src.at<uchar>(i, j)) << (neighbors - m - 1));
			}
	}
}

/*
* @param cv::Mat src	输入图像,计算得到的LBP特征图
* @param int minValue	LBP特征值的最小值
* @param int maxValue	LBP特征值的最大值(==numPatterns - 1)
* @param bool normed    是否归一化
* @brief 计算一个LBP特征图像块的直方图
*/
cv::Mat getLocalRegionLBPH(const cv::Mat& src, int minValue, int maxValue, bool normed)
{
	//定义存储直方图的矩阵
	cv::Mat result;
	//计算得到直方图bin的数目,直方图数组的大小
	int histSize = maxValue - minValue + 1;
	//定义直方图每一维的bin的变化范围
	float range[] = { static_cast<float>(minValue),static_cast<float>(maxValue + 1) };
	//定义直方图所有bin的变化范围
	const float* ranges = { range };
	//计算直方图,src是要计算直方图的图像,1是要计算直方图的图像数目,0是计算直方图所用的图像的通道序号,从0索引
	//Mat()是要用的掩模,result为输出的直方图,1为输出的直方图的维度,histSize直方图在每一维的变化范围
	//ranges,所有直方图的变化范围(起点和终点)
	calcHist(&src, 1, 0, cv::Mat(), result, 1, &histSize, &ranges, true, false);
	//归一化
	if (normed)
	{
		result /= (int)src.total();
	}
	//结果表示成只有1行的矩阵
	return result.reshape(1, 1);
}


/*
* @param cv::Mat src	输入图像,计算得到的LBP特征图
* @param int numPatterns	LBP特征值种类(范围)
* @param int grid_x		水平方向的块的数量
* @param int grid_y		竖直方向的块的数量
* @param bool normed    是否归一化
* @brief 计算LBP特征图像的直方图LBPH
*/
cv::Mat getLBPH(cv::InputArray _src, int numPatterns, int grid_x, int grid_y, bool normed)
{
	cv::Mat src = _src.getMat();
	int width = src.cols / grid_x;
	int height = src.rows / grid_y;
	//定义LBPH的行和列,grid_x*grid_y表示将图像分割成这么些块,numPatterns表示LBP值的模式种类
	cv::Mat result = cv::Mat::zeros(grid_x * grid_y, numPatterns, CV_32FC1);
	if (src.empty())
	{
		return result.reshape(1, 1);
	}
	int resultRowIndex = 0;
	//对图像进行分割,分割成grid_x*grid_y块,grid_x,grid_y默认为8
	for (int i = 0; i < grid_x; i++)
	{
		for (int j = 0; j < grid_y; j++)
		{
			//图像分块
			cv::Mat src_cell = cv::Mat(src, cv::Range(i * height, (i + 1) * height), cv::Range(j * width, (j + 1) * width));
			//计算直方图
			cv::Mat hist_cell = getLocalRegionLBPH(src_cell, 0, (numPatterns - 1), true);
			//将直方图放到result中
			cv::Mat rowResult = result.row(resultRowIndex);
			hist_cell.reshape(1, 1).convertTo(rowResult, CV_32FC1);
			resultRowIndex++;
		}
	}
	return result.reshape(1, 1);
}



int main()
{
	//读取图片
	string filepath = "F://work_study//algorithm_demo//baby.jpg";
	cv::Mat src = cv::imread(filepath, cv::IMREAD_GRAYSCALE);
	if (src.empty())
	{
		std::cout << "imread error" << std::endl;
		return -1;
	}
	cv::Mat circleLBPImg,dst;
	//计算得到LBP特征图
	getCircleLBP(src, circleLBPImg,3,8);
	//dst即为LBPH特征向量
	dst=getLBPH(circleLBPImg, 256, 8, 8, true).clone();

	cv::waitKey(0);
	return 0;
}

总结

本文主要介绍了LBP、Circle LBP、LBPH的C++实现和原理。

相关推荐

  1. 图像处理HOG特征C++)

    2024-04-24 09:02:02       14 阅读
  2. 数字图像处理图像形状特征

    2024-04-24 09:02:02       70 阅读
  3. C#&图像:2.图像处理

    2024-04-24 09:02:02       12 阅读
  4. Qt图像处理

    2024-04-24 09:02:02       35 阅读
  5. 数据处理图像压缩

    2024-04-24 09:02:02       9 阅读

最近更新

  1. pg数据库时间比较

    2024-04-24 09:02:02       0 阅读
  2. C# 枚举的定义及使用

    2024-04-24 09:02:02       0 阅读
  3. Prompt Engineering 探险

    2024-04-24 09:02:02       1 阅读
  4. 机器学习之神经网络

    2024-04-24 09:02:02       1 阅读
  5. Lianwei 安全周报|2024.07.09

    2024-04-24 09:02:02       1 阅读
  6. 每天一个数据分析题(四百一十八)- 相关分析

    2024-04-24 09:02:02       1 阅读
  7. 计算机网络面试常见题目(一)

    2024-04-24 09:02:02       1 阅读
  8. vue配置sql规则

    2024-04-24 09:02:02       1 阅读

热门阅读

  1. 使用python写一个井字棋窗口小游戏

    2024-04-24 09:02:02       17 阅读
  2. 暴力数据结构之单链表专题

    2024-04-24 09:02:02       15 阅读
  3. PostCss 概述

    2024-04-24 09:02:02       16 阅读
  4. 文件分享新风尚,二维码生成器全功能解析

    2024-04-24 09:02:02       19 阅读
  5. 新手入门人工智能:从零开始学习AI的正确途径

    2024-04-24 09:02:02       19 阅读
  6. 1. 2XX (Success 成功状态码)

    2024-04-24 09:02:02       14 阅读
  7. 服务器端的图片一般存储在哪?

    2024-04-24 09:02:02       30 阅读
  8. 实现 vue&react 混合开发项目步骤-微前端

    2024-04-24 09:02:02       17 阅读
  9. 240. 搜索二维矩阵 II

    2024-04-24 09:02:02       16 阅读
  10. 每天一个数据分析题(二百八十五)

    2024-04-24 09:02:02       17 阅读
  11. 每天一个数据分析题(二百八十七)

    2024-04-24 09:02:02       19 阅读