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

devtools/2024/9/20 9:19:29/ 标签: 图像处理, c++, 人工智能

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


文章目录

  • 图像处理之LBP特征(C++)
  • 前言
  • 一、LBP特征描述
  • 二、圆形LBP特征
  • 三、LBPH特征描述
  • 四、代码实现
    • 1.LBP实现
    • 2.圆形LBP实现
    • 3.LBPH实现
  • 总结


前言

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默认为8for (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++实现和原理。


http://www.ppmy.cn/devtools/12608.html

相关文章

C#基础|OOP、类与对象的认识

哈喽&#xff0c;你好&#xff0c;我是雷工&#xff01; 所有的面向对象的编程语言&#xff0c;都是把我们要处理的“数据”和“行为”封装到类中。 以下为OOP的学习笔记。 01 什么是面向对象编程&#xff08;OOP&#xff09;&#xff1f; 设计类&#xff1a;就是根据需求设计…

C++中的五种高级初始化技术:从reserve到piecewise_construct等

C高级初始化技术&#xff1a;reserve、emplace_back、constinit、Lambda表达式、piecewise_construct 一、简介二、reserve 结合 emplace_back三、C 20的constinit四、Lambda表达式和初始化五、make_unique_for_overwrite六、piecewise_construct 和 forward_as_tuple七、总结 …

Day26: Redis入门、开发点赞功能、开发我收到的赞的功能、重构点赞功能、开发关注、取消关注、开发关注列表、粉丝列表、重构登录功能

Redis入门 简介 Redis是NoSQL数据库&#xff08;Not only SQL&#xff09;值支持多种数据结构&#xff08;key都是string&#xff09;&#xff1a;字符串、哈希、列表、集合、有序集合把数据存在内存中&#xff0c;速度惊人&#xff1b;同时也可以讲数据快照&#xff08;数据…

Xilinx 7系列MMCM/PLL端口简介

在FPGA设计中&#xff0c;MMCM和PLL的端口允许设计者进行各种配置&#xff0c;包括设置时钟源的选择、分频系数、相位偏移等参数。此外&#xff0c;这些端口还可以提供时钟信号的状态信息&#xff0c;如是否锁定、频率误差等&#xff0c;以便设计者进行监控和调试。 具体的端口…

HTML学习笔记之计算机代码格式、语义元素、代码约定、字符实体、符号、URL(十)

详细资料来源地址&#xff1a;W3School 10、计算机代码元素 有时候我们需要展示带格式&#xff08;换行、空格等&#xff09;的计算机代码&#xff0c;我们就需要使用到计算机代码 10.1 <kbd>&#xff1a;定义键盘文本 HTML <kbd> 元素定义键盘输入 <!DOCT…

docker制作zookeeper镜像

制作 Zookeeper 镜像的步骤通常包括以下几个主要步骤&#xff1a; 准备 Dockerfile&#xff1a;编写 Dockerfile 文件&#xff0c;定义如何构建 Zookeeper 镜像。 构建镜像&#xff1a;使用 Dockerfile 构建 Zookeeper 镜像。 运行容器&#xff1a;运行基于构建的镜像创建的容…

Springboot3中aop几个通知注解执行的先后顺序

1、简介 随着Spring框架的不断更新迭代&#xff0c;在面向切面编程中&#xff0c;Spring AOP使用 Around(在方法执行前后) 、Before(在方法执行前)、 AfterReturning(未抛异常) 、 After(不论是否抛异常) 、 Around (在方法执行后) 2、注解描述 注解描述Before在代理方法执行…

Python | Leetcode Python题解之第48题旋转图像

题目&#xff1a; 题解&#xff1a; class Solution:def rotate(self, matrix: List[List[int]]) -> None:n len(matrix)# 水平翻转for i in range(n // 2):for j in range(n):matrix[i][j], matrix[n - i - 1][j] matrix[n - i - 1][j], matrix[i][j]# 主对角线翻转for …

黑马点评(四) -- 分布式锁

1 . 分布式锁基本原理和实现方式对比 分布式锁&#xff1a;满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁&#xff0c;只要大家使用的是同一把锁&#xff0c;那么我们就能锁住线程&#xff0c;不让线程进行&#xff0c;让…

Photoshop 2024 (ps) v25.6中文 强大的图像处理软件 mac/win

Photoshop 2024 for Mac是一款强大的图像处理软件&#xff0c;专为Mac用户设计。它继承了Adobe Photoshop一贯的优秀功能&#xff0c;并进一步提升了性能和稳定性。 Mac版Photoshop 2024 (ps)v25.6中文激活版下载 win版Photoshop 2024 (ps)v25.6直装版下载 无论是专业的设计师还…

每日一题:String、StringBuffer、StringBuilder有什么区别❓

String、StringBuffer和StringBuilder在Java中都用于处理字符串&#xff0c;但它们之间有一些关键的区别&#xff0c;主要体现在字符串的可变性、线程安全性以及性能上&#x1f50a;。 String&#x1f34a;: 不可变性&#xff1a;String是不可变的&#xff0c;这意味着一旦创…

滚动条详解:跨平台iOS、Android、小程序滚动条隐藏及自定义样式综合指南

滚动条是用户界面中的图形化组件&#xff0c;用于指示和控制内容区域的可滚动范围。当元素内容超出其视窗边界时&#xff0c;滚动条提供可视化线索&#xff0c;并允许用户通过鼠标滚轮、触屏滑动或直接拖动滑块来浏览未显示部分&#xff0c;实现内容的上下或左右滚动。它在保持…

sklearn【AUC-ROC】原理,以及绘制ROC曲线!

一、AUC-ROC 介绍 在分类任务中&#xff0c;特别是当数据集中的类别分布不平衡时&#xff0c;评估模型的性能变得尤为重要。AUC-ROC&#xff08;Area Under the Receiver Operating Characteristic Curve&#xff0c;受试者工作特征曲线下的面积&#xff09;是一种有效的评估指…

[HFCTF 2021 Final]easyflask

这ctf打着真累。刷题根本刷不完&#xff0c;知识点好多。。。也是好久没写&#xff0c;一直在准备hw&#xff0c;今儿整一个python的反序列化入门吧。呜呜呜&#xff0c;好多反序列化的题。。。 就先跟着提示走就可以了。直到我们找到了这个 #!/usr/bin/python3.6 import os i…

c#对控件的操作无效,子线程调用主线程的控件

很多人都曾遇到过一件事&#xff0c;我明明已经对这个控件赋值了&#xff0c;或者对这个控件进行了什么操作&#xff0c;但是操作无效。我就曾遇到过这个问题。当时我上网搜了一下。其中可能出现的问题就是我在子线程中调用主线程中的控件。当时给出的答案都是使用委托来解决这…

常见面试算法题-打麻将

■ 题目描述 【打麻将】 给定一个列表&#xff0c;里面含所有14个元素&#xff0c;问这14个元素&#xff0c;能不能组成33332的组合&#xff0c;3格式可以表示顺子&#xff0c;或者3张相同的牌&#xff0c;2表示对子&#xff08;两张相同的牌&#xff09;类似麻将胡牌一样&am…

ffmpeg支持MP3编码的方法

目录 现象 解决办法 如果有编译包没有链接上的情况 现象 解决办法 在ffmpeg安装包目录下 &#xff0c;通过./configure --list-encoders 和 ./configure --list-decoders 命令可以看到&#xff0c;ffmpeg只支持mp3解码&#xff0c;但是不支持mp3编码。 上网查寻后发现&…

DRF JWT认证基础

JWT认证 【1】base64使用 &#xff08;1&#xff09;使用场景 电子邮件附件&#xff1a;由于电子邮件协议只支持 ASCII 字符集&#xff0c;因此&#xff0c;如果要发送非 ASCII 数据&#xff08;如图片、音频、视频等&#xff09;&#xff0c;需要先将这些数据进行 base64 编…

ubuntu下chronyc tracking报文详解

在ubuntu下使用chronyc进行时钟的同步操作&#xff0c;下面是执行chrony tracking返回结果&#xff1a; Reference ID : AC1005E7 (ntpxx) Stratum : 12 Ref time (UTC) : Tue Apr 23 07:24:09 2024 System time : 0.000001974 seconds slow of NTP time Last …

Android Binder——数据传输限制(二十三)

在前面的学习中,我们了解到在创建用户和内核的虚拟空间的时候,会开辟 1M-8K 的内存空间,用于当前进程与 Binder 驱动进行传递数据,但是在实际传输过程中,其实并不能达到1M-8k的数据。这里我们就来分析一下 Binder 通信数据传输的限制。 一、Linux系统启动 Linux 系统在启…