02 特征点提取器 ORBextractor

news2025/2/1 16:59:03

文章目录

    • 02 特征点提取器 ORBextractor
      • 2.0 基础知识
        • 2.0.1 图像金字塔
        • 2.0.2 ORB 特征点的关键点和描述子
      • 2.1 构造函数:ORBextractor()
      • 2.2 构建图像金字塔 ComputePyramid()
      • 2.3 提取特征点并筛选 ComputeKeyPointsOctTree()
      • 2.4 筛选特征点 DistributeOctTree()
      • 2.5 计算特征点方向 computeOrientation()
      • 2.6 计算特征点描述子 computeOrbDescriptor()
      • 2.7 ORBextractor 类总结
        • 2.7.1 主要流程
        • 2.7.2 与其他类的关系

02 特征点提取器 ORBextractor

2.0 基础知识

2.0.1 图像金字塔

(1)尺度不变性:一幅图片中的某个物体的大小永远是那么大(设置一个标准尺度来度量),而不是因为它所在图片的放大缩小而改变。这就是尺度不变性。

(2)图像金字塔
在这里插入图片描述

将原始图像按照 1:1.2 的比例依次缩小,得到 n 幅图像,形如金字塔。观察这 n 幅图像,我们似乎离场景越来越远,这样就用一幅图像制造出了深度变化的效果。

在 SLAM 中,我们对这 n 张图像分别提取特征点,就相当于得到了不同距离的特征,涵盖了更多的尺度。试想一下,假设第一次我们在 10m 处获取一幅图像,经过图像金字塔,相当于得到了 10 1.2 \frac{10}{1.2} 1.210 10 1. 2 2 \frac{10}{1.2^2} 1.2210 10 1. 2 3 \frac{10}{1.2^3} 1.2310… 等处共 n 个不同距离的图像;当我们下次再处于不同距离拍摄该处场景时,就有更多的图像与特征点进行匹配。这样就解决了尺度导致的特征点匹配问题。

参考:https://blog.csdn.net/u011341856/article/details/103707313

2.0.2 ORB 特征点的关键点和描述子

ORB 的关键点是在 FAST 关键点基础上进行了改进,主要是增加了特征点的主方向,称之为 Oriented FAST。

描述子是在 BRIEF 描述子基础上加入了上述方向信息,称之为 Rotated BRIEF。

2.1 构造函数:ORBextractor()

构造函数原型

ORBextractor::ORBextractor(int _nfeatures, float _scaleFactor, int _nlevels, int _iniThFAST, int _minThFAST)

(1)从 yaml 文件中读取参数(以 KITTI00-02.yaml 为例)

成员变量意义在yaml中变量名
int _nfeatures期望提取的特征点个数ORBextractor.nFeatures2000
float _scaleFactor金字塔相邻层级缩放因子ORBextractor.scaleFactor1.2
int _nlevels金字塔层数ORBextractor.nLevels8
int _iniThFAST提取 FAST 特征点的默认阈值ORBextractor.iniThFAST20
int _minThFAST如果使用默认阈值提取不到特征点则使用最小阈值再次提取ORBextractor.minThFAST7

根据上述变量的值计算出下述成员变量:

变量意义
std::vector<float> mvScaleFactor各层级缩放系数{1, 1.2, 1.44, 1.728, 2.074, 2.488, 2.986, 3.583
std::vector<float> mvInvScaleFactor各层级缩放系数的倒数{1, 0.833, 0.694, 0.579, 0.482, 0.402, 0.335, 0.2791}
std::vector<float> mvLevelSigma2各层级缩放系数的平方{1, 1.44, 2.074, 2.986, 4.300, 6.190, 8.916, 12.838}
std::vector<float> mvInvLevelSigma2各层级缩放系数的平方的倒数{1, 0.694, 0.482, 0.335, 0.233, 0.162, 0.112, 0.078}
std::vector<int> mnFeaturesPerLevel每一层期望提取的特征点个数(正比于图层边长,总和为 nfeatures{122, 146, 174, 210, 252, 302, 362, 432}

(2)初始化用于计算描述子的 pattern 变量,也就是用于计算描述子的 256 对坐标

static int bit_pattern_31_[256*4] =
{
    8,-3, 9,5/*mean (0), correlation (0)*/,
    4,2, 7,-12/*mean (1.12461e-05), correlation (0.0437584)*/,
    -11,9, -8,2/*mean (3.37382e-05), correlation (0.0617409)*/,
    ...
}

共 256 行,每一行表示一对坐标点,如第一行为 (8, -3)(9, 5)

(3) 在提取 Oriented FAST 关键点后,还需要计算每个点的描述子。即以关键点为圆心,在半径为 16 的圆的范围内,计算特征点主方向和描述子。

在这里插入图片描述

(注:图片中半径为 8,仅作示意)

成员变量 std::vector<int> umax 中存储的是逼近圆的第一象限内 1 4 \frac{1}{4} 41 圆周上每个 v 坐标对应的 u 坐标。为保证严格对称性,先计算下 45° 圆周上点的坐标,再根据对称性补全上 45° 圆周上点的坐标。

int vmax = cvFloor(HALF_PATCH_SIZE * sqrt(2.f) / 2 + 1); 	// 45°射线与圆周交点的纵坐标
int vmin = cvCeil(HALF_PATCH_SIZE * sqrt(2.f) / 2);			// 45°射线与圆周交点的纵坐标

// 先计算下半45度的umax
for (int v = 0; v <= vmax; ++v) {
	umax[v] = cvRound(sqrt(15 * 15 - v * v));	
}

// 根据对称性补出上半45度的umax
for (int v = HALF_PATCH_SIZE, v0 = 0; v >= vmin; --v) {
    while (umax[v0] == umax[v0 + 1])
        ++v0;
    umax[v] = v0;
    ++v0;
}

2.2 构建图像金字塔 ComputePyramid()

变量访问控制意义
std::vector<cv::Mat> mvImagePyramidpublic存储图像金字塔每层的图像
const int EDGE_THRESHOLD全局变量为计算描述子和提取特征点补的 padding 厚度

函数原型

void ORBextractor::ComputePyramid(cv::Mat image)
{
	//开始遍历所有的图层,levels是yaml文件里面的
    for (int level = 0; level < nlevels; ++level)
    {
		//获取本层图像的缩放系数,mvInvScaleFactor[level]是从orbextrator得到的
        float scale = mvInvScaleFactor[level];
		//计算本层图像的像素尺寸大小
        Size sz(cvRound((float)image.cols*scale), cvRound((float)image.rows*scale));
		//全尺寸图像。包括无效图像区域的大小。将图像进行“补边”,EDGE_THRESHOLD区域外的图像不进行FAST角点检测
        Size wholeSize(sz.width + EDGE_THRESHOLD*2, sz.height + EDGE_THRESHOLD*2);
		// temp是扩展了边界的图像,是一个构造函数,拷贝了wholeSize的图像
        Mat temp(wholeSize, image.type()), masktemp;
        // mvImagePyramid 刚开始时是个...空的vector<Mat>
		// 将扩充后的图像拷贝给mvImagePyramid容器
        mvImagePyramid[level] = temp(Rect(EDGE_THRESHOLD, EDGE_THRESHOLD, sz.width, sz.height));

        // Compute the resized image
		//计算第0层以上resize后的图像
        if( level != 0 )
        {
			//将上一层金字塔图像根据前文设定sz缩放到当前层级
            resize(mvImagePyramid[level-1],	//输入图像
				   mvImagePyramid[level], 	//输出图像
				   sz, 						//输出图像的尺寸
				   0, 						//水平方向上的缩放系数,留0表示自动计算
				   0,  						//垂直方向上的缩放系数,留0表示自动计算
				   cv::INTER_LINEAR);		//图像缩放的差值算法类型,这里的是线性插值算法


			//把源图像拷贝到目的图像的中央,四面填充指定的像素。图片如果已经拷贝到中间,只填充边界
			//这样做是为了能够正确提取边界的FAST角点
			//EDGE_THRESHOLD指的这个边界的宽度,由于这个边界之外的像素不是原图像素而是算法生成出来的,所以不能够在EDGE_THRESHOLD之外提取特征点			
            copyMakeBorder(mvImagePyramid[level], 					//源图像
						   temp, 									//目标图像(此时其实就已经有大了一圈的尺寸了)
						   EDGE_THRESHOLD, EDGE_THRESHOLD, 			//top & bottom 需要扩展的border大小
						   EDGE_THRESHOLD, EDGE_THRESHOLD,			//left & right 需要扩展的border大小
                           BORDER_REFLECT_101+BORDER_ISOLATED);     //扩充方式,opencv给出的解释:						
        }
        else
        {
			//对于第0层未缩放图像,直接将图像深拷贝到temp的中间,并且对其周围进行边界扩展。此时temp就是对原图扩展后的图像
            copyMakeBorder(image,			//这里是原图像
						   temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD,
                           BORDER_REFLECT_101);            
        }
    }
}

包括两步:

  • 将图像缩放到 mvInvScaleFactor 对应尺寸;

  • 在图像四周补一圈厚度为 EDGE_THRESHOLD 的 padding(提取 FAST 特征点需要特征点周围半径为 3 的圆域,计算描述子需要特征点周围半径为 16 的圆域),copyMakeBorder() 函数实现。

在这里插入图片描述

  • 深灰色 为缩放后的原始图像;

  • 包含绿色边界在内的矩形 用于提取 FAST 特征点;

  • 包含浅灰色边界在内的整个矩形 用于计算 ORB 描述子。

为什么要扩充图像边界呢?

利用 FAST 算法在提取特征点时,图像边缘的特征点半径为3的圆无法取到(边界外无像素点),为了解决此问题,我们对图像边界进行填充。

参考:https://www.pudn.com/news/62f50f18f97302478e3581fc.html

2.3 提取特征点并筛选 ComputeKeyPointsOctTree()

在这里插入图片描述

我们希望 特征点均匀地分布在图像的所有部分。所以在提取时会将图片分成 30*30(单位像素)的一个一个小格子(cell)来提取特征点。并且在提取 FAST 角点时,我们设计了两个阈值 _iniThFAST_minThFAST,这样在每个 cell 中,就可以根据实际情况进行调整,尽可能保证每个 cell 中都能提取到特征点。
在这里插入图片描述
代码实现主要有两步:

  • 划分 cell,先用默认阈值提取特征点,如果找不到,就降低阈值,用 _minThFAST 搜索特征点;

  • 对得到的所有特征点进行八叉树筛选,若某区域内特征点数目过于密集,则只取其中响应值最大的那个。
    在这里插入图片描述

2.4 筛选特征点 DistributeOctTree()

筛选完特征点后,还是可能出现某些 cell 中特征点密集,某些 cell 中稀疏甚至没有特征点。因此采用类似八叉树的方法重新分发角点,使一个区域只有一个特征点(注意,这里的区域是重新划分的,不是之前的 cell)。
在这里插入图片描述

(没有特征点或只有一个特征点的区域不再分裂。)

2.5 计算特征点方向 computeOrientation()

使用特征点周围半径为 19 的圆的重心方向作为特征点方向。

M 00 = ∑ X = − R R ∑ Y = − R R I ( x , y ) M 10 = ∑ X = − R R ∑ X = − R R x I ( x , y ) M 01 = ∑ X = − R R ∑ X = − R R y I ( x , y ) Q X = = M 10 M 00 , Q Y = M 01 M 00 C = ( m 10 m 00 , m 00 m 00 ) θ = atan ⁡ 2 ( m 01 , m 10 ) \begin{aligned} & M_{00}=\sum_{X=-R}^R \sum_{Y=-R}^R I(x, y) \\ & M_{10}=\sum_{X=-R}^R \sum_{X=-R}^R x I(x, y) \\ & M_{01}=\sum_{X=-R}^R \sum_{X=-R}^R y I(x, y) \\ & Q_{X=}=\frac{M_{10}}{M_{00}}, Q_Y=\frac{M_{01}}{M_{00}} \\ & C=\left(\frac{m_{10}}{m_{00}}, \frac{m_{00}}{m_{00}}\right) \\ & \theta=\operatorname{atan} 2\left(m_{01}, m_{10}\right) \end{aligned} M00=X=RRY=RRI(x,y)M10=X=RRX=RRxI(x,y)M01=X=RRX=RRyI(x,y)QX==M00M10,QY=M00M01C=(m00m10,m00m00)θ=atan2(m01,m10)
c x = ∑ x = − R ∑ y = − R R x I ( x , y ) ⏞ m 10 ∑ x = − R R ∑ y = − R R I ( x , y ) ⏟ m 00 , c y = ∑ x = − R R ∑ y = − R R y I ( x , y ) ⏞ m 01 ∑ x = − R R ∑ y = − R R I ( x , y ) ⏟ m 00 θ = arctan ⁡ 2 ( c y , c x ) = arctan ⁡ 2 ( m 01 , m 10 ) \begin{aligned} & c_x=\frac{\overbrace{\sum_{x=-R} \sum_{y=-R}^R x I_{(x, y)}}^{m_{10}}}{\underbrace{\sum_{x=-R}^R \sum_{y=-R}^R I_{(x, y)}}_{m_{00}}}, c_y=\frac{\overbrace{\sum_{x=-R}^R \sum_{y=-R}^R y I_{(x, y)}}^{m_{01}}}{\underbrace{\sum_{x=-R}^R \sum_{y=-R}^R I_{(x, y)}}_{m_{00}}} \\ & \theta=\arctan 2\left(c_y, c_x\right)=\arctan 2\left(m_{01}, m_{10}\right) \\ & \end{aligned} cx=m00 x=RRy=RRI(x,y)x=Ry=RRxI(x,y) m10,cy=m00 x=RRy=RRI(x,y)x=RRy=RRyI(x,y) m01θ=arctan2(cy,cx)=arctan2(m01,m10)

2.6 计算特征点描述子 computeOrbDescriptor()

在特征点周围半径为 16 的圆域内选取 256 对点,比较,得到 256 位描述子。
在这里插入图片描述

computeOrientation() 中,我们求出了每个特征点的主方向,因此在计算描述子之前,要先将特征点周围像素旋转到主方向上来。
在这里插入图片描述

2.7 ORBextractor 类总结

2.7.1 主要流程

ORBextractor 类用于 tracking 线程中第一步预处理。

主要流程为
在这里插入图片描述

2.7.2 与其他类的关系

Frame类 中与 ORBextractor 有关的成员变量和函数

成员变量/函数访问控制意义
ORBextractor* mpORBextractorLeftpublic左目特征点提取器
ORBextractor* mpORBextractorRightpublic右目特征点提取器(单目/RGBD时为空指针)
ExtractORB()public提取特征点,直接调用 mpORBextractorLeftmpORBextractorRight
FramepublicFrame类的构造函数,调用 ExtractORB() 提取特征点

每次提取完 ORB 特征点之后,图像金字塔信息就会作废,下一帧图像到来时调用 ComputePyramid() 函数会覆盖掉上一帧的图像金字塔信息;但已经提取到的特征点信息会被保留在 Frame 对象中。所以 ORB-SLAM2 是稀疏重建,每帧图像只会保留最多 nfeatures 个特征点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1329301.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++实现布隆过滤器

目录 一、什么是布隆过滤器 二、布隆过滤器的映射 三、布隆过滤器的作用 四、布隆过滤器的实现 五、总结测试 一、什么是布隆过滤器 之前我们学习了位图&#xff0c;我们知道位图主要是实现了整形的映射bit位&#xff0c;这样可以大幅度的节省空间&#xff0c;那么针对于…

【笔记】Spring是什么

什么是spring&#xff1f; Spring的基础知识铺垫 IOC AOP<-Spring->容器->生态 先说你的认知&#xff0c;总-分结构 spring是一个基础的框架&#xff0c;同时提供了Bean的容器&#xff0c;用来方便装载具体的Bean对象&#xff0c;之前在使用对象的时候必须自己new&…

代码随想录第三十八天(一刷C语言)|零钱兑换II组合总数和 IV

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、零钱兑换II 思路&#xff1a;参考carl文档 1、确定dp数组以及下标的含义&#xff1a;凑成总金额j的货币组合数为dp[j]。 2、确定递推公式&#xff1a;dp[j] 就是所有的dp[j - coins[i…

中国ESG的新故事:主动、常态与变革

ESG的终局不仅仅是与业务的结合&#xff0c;而是需要将ESG 融入企业价值内核&#xff0c;实现社会价值与商业价值的深度融合&#xff0c;即有意义地盈利。 作者|斗斗 编辑|皮爷 出品|产业家 “到这里来吧&#xff0c;我将帮你们获得这个世界。我的文明已无力解决自己的…

微服务之配置中心与服务跟踪

zookeeper 配置中心 实现的架构图如下所示&#xff0c;采取数据加载到内存方式解决高效获取的问题&#xff0c;借助 zookeeper 的节点监听机制来实现实时感知。 配置中心数据分类 事件调度&#xff08;kafka&#xff09; 消息服务和事件的统一调度&#xff0c;常用用 kafka …

pytorch张量的创建

张量的创建 张量&#xff08;Tensors&#xff09;类似于NumPy的ndarrays &#xff0c;但张量可以在GPU上进行计算。从本质上来说&#xff0c;PyTorch是一个处理张量的库。一个张量是一个数字、向量、矩阵或任何n维数组。 import torch import numpy torch.manual_seed(7) # 固…

linux系统和网络(二):进程和系统时间

本文主要探讨linux系统进程和系统相关知识&#xff0c;本博客其他博文对该文章的部分内容有详细介绍 main函数 int main(int argc,char *argv[],char *envp[]); 操作系统下main执行前先执行引导代码,编译连接引导代码和程序连接在一起构成可执行程序,加载器将程序加载到内存中…

react 2

1.快速搭建开发环境 2.react渲染流程 3.1 jsx基础 概念 3.2 jsx基础 本质 3.3 jsx基础 jsx表达式 3.4 jsx基础 实现列表渲染 3.5 jsx基础 实现条件渲染 3.5 jsx基础 实现复杂的条件渲染 4. react中事件绑定 5.react组建基础使用 6.1 useState 6.2 useState修改状态的规则 7.基础…

渗透测试和漏洞扫描有什么区别

渗透测试和漏洞扫描是网络安全领域中非常重要的两种技术手段&#xff0c;它们都可以帮助组织或企业发现和修复系统中的漏洞和弱点。然而&#xff0c;这两种技术手段在目的、深度、方法和时间和成本等方面存在显著的区别。 首先我们来了解下渗透测试和漏洞扫描分别是什么&#x…

测试开发体系介绍——测试体系介绍-L1

目录&#xff1a; 软件测试基础概念 软件测试:软件测试作用:软件缺陷:软件测试原则:软件测试对象:测试用例软件开发流程 软件:软件生命周期:软件开发流程:瀑布模型:瀑布模型优缺点敏捷开发模型: XP - 极限编程:SCRUM:DevOps&#xff1a;DevOps 生命周期&#xff1a;DevOps 对发…

C语言中关于操作符的理解

本篇文章只会列出大家在生活中经常使用的操作符 算术操作符 在算数操作符中常用的有&#xff0c;&#xff0c;-&#xff0c;*&#xff0c;/&#xff0c;% &#xff0c;我们重点讲一讲 / (除) 和 % (模) " / "运算 #include <stdio.h>int main() {int a5/2;fl…

【Amazon 实验③】使用Amazon WAF做基础 Web Service 防护之速率策略

文章目录 1. 速率策略1.1 介绍 2. 实验步骤2.1 添加规则2.2 测试2.3 结果 通过上一篇文章大家了解到如何使用Amazon WAF做关于自定义规则设置的 Web Service 防护【Amazon 实验②】使用Amazon WAF做基础 Web Service 防护之自定义规则&#xff0c;本篇文章将继续讲解一下关于速…

WebGL开发三维解剖学应用

开发基于 WebGL 的三维解剖学应用通常涉及以下步骤。这些步骤包括创建三维模型、整合交互性、优化性能等&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.三维模型创建&#xff1a; 首先&#xff0…

SpringBoot 使用Quartz执行定时任务对象时无法注入Bean问题

文章目录 问题描述解决方案结束语 大家好&#xff01;今天是2023年12月21日 | 农历十一月初九(距离2024年还有一周左右的时间)&#xff0c;最近还是比较忙的&#xff0c;忙着搞钱&#xff0c;毕竟马上过年啦&#xff01; 问题描述 感谢大家对我一直以来的支持与帮助&#xff0c…

7.串口通信uart编写思路及自定义协议

前言&#xff1a; 串口是很重要的&#xff0c;有许多模块通信接口就是串口&#xff0c;例如gps模块&#xff0c;蓝牙模块&#xff0c;wifi模块还有一些精度比较高的陀螺仪模块等等&#xff0c;所以学会了串口之后&#xff0c;这些听起来很牛批的模块都能够用起来了。此外&#…

Qt/QML编程学习之心得:在QML工程中添加库(十四)

实现库并且使用库&#xff0c;类似于vc中的静态库library、动态库dll、COM组件等方法一样&#xff0c;在Qt中也经常会使用库&#xff0c;或者将部分功能打包成库。 右击Qt项目&#xff0c;点击add library... 在linux中将.a文件导入&#xff0c;工程会自动在.pro温江中增加相应…

centos安装Jenkins并拉取git远程仓库的代码进行自动化构建部署

安装Jenkins并拉取git远程仓库的代码进行自动化构建部署 1 前置条件2 先安装jdk113 安装git4 安装maven5 安装jenkins5.1下载jenkins5.2启动jenkins 6 使用jenkins拉取git仓库代码并部署6.1 安装插件6.2 在jenkins中配置maven6.3在jenkins上构建maven项目6.4 配置拉取的git仓库…

成功案例分享:物业管理小程序如何助力打造智慧社区

随着科技的进步和互联网的普及&#xff0c;数字化转型已经渗透到各个行业&#xff0c;包括物业管理。借助小程序这一轻量级应用&#xff0c;物业管理可以实现线上线下服务的无缝对接&#xff0c;提升服务质量&#xff0c;优化用户体验。本文将详细介绍如何通过乔拓云网设计小程…

【vtkWidgetRepresentation】第十六期 vtkContourRepresentation(三)

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享vtkContourLineInterpolator接口的源码剖析和实例应用,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 目录 前言 …

csrf自动化检测调研

https://github.com/pillarjs/understanding-csrf/blob/master/README_zh.md CSRF 攻击者在钓鱼站点&#xff0c;可以通过创建一个AJAX按钮或者表单来针对你的网站创建一个请求&#xff1a; <form action"https://my.site.com/me/something-destructive" metho…