精华内容
下载资源
问答
  • orb特征
    2022-06-20 01:22:53

    1 什么是ORB特征

    特征是图像信息的一种表达方式,我们可以把角点、边缘和区块都可以看作图像中有代表性的地方,我们发现,图像中的角点、边缘相比于区块更加“特别”,一种直观的提取特征的方式就是在不同的图像间辨认角点,确定它们的对应关系。在这种做法中,角点就是所谓的特征。角点的提取算法有很多,例如Harris角点、FAST角点、GFTT角点等等,然而ORB(Oriented Fast and Rotated BRIEF)是Oriented Fast + Rotated BRIEF的缩写,是目前最快速稳定的特征点检测和提取算法。

    同一幅图像同时提取约1000个特征点的情况下:

    检测器ORBSURFSIFT
    一帧耗时(ms)15.3217.35228.7

    2 FAST关键点

    算法步骤:
    1、选取像素点p,假设他的亮度为lp;
    2、设置一个阈值T(比如lp的20%);
    3、以像素p为中心,选取半径为3的圆上的16个像素点;
    4、假如选取的圆上,有连续的N个点的亮度大于lp+T或者小于lp+T,那么像素p可以被认为是特征点;(N通常取12即FAST-12,其他常用的N取值为9或者11,他们分别被称为FAST-9和FAST-11)
    5、循环以上4步,对每个像素执行相同操作。

    在这里插入图片描述

    在FAST-12算法中,为了更高效,增加了一项预测试的操作,以快速地排除绝大多是不是角点的像素点:
    对于每个像素,直接检测领域圆上第1,5,9,13上的亮度,只有当这4个中的3个的亮度同时大于lp+T或者小于lp+T,才有可能是角点,否则直接排除。

    原始的FAST角点经常出现”扎堆“的现象,为了解决这个问题,可以采用非极大值抑制的算法:

    • 假设 p , q p,q pq两点相邻,分别计算出两个点与其周围16个像素点之间的差分和为 V V V

    • 去除 V V V值较小的点,即把非最大的角点抑制掉。

    缺点及解决办法:

    • 尺度不变性:可以通过金字塔解决;
    • 旋转不变性:可以用质心标定方向解决。

    3 尺度不变性

    算法步骤:
    1、对图像做不同尺度的高斯模糊;
    2、对图像做降采样;
    3、对每层金字塔做FAST特征点检测;
    4、 N N N副不同比例的图像提取出的特征点总和作为这幅图像的FAST特征点。

    4 旋转不变性

    旋转不变性是通过灰度质心法计算出特征方向,在描述子中利用这一角度,消除由于旋转带来的影响。

    4.1 灰度质心法

    1、在一个小的图像块 B B B中,定义图像块的矩为:
    m p q = ∑ x , y ∈ B x p y q I ( x , y )         p , q   =   { 0 , 1 } m_{pq} = \sum_{x,y\in B} x^{p}y^{q}I(x,y)\ \ \ \ \ \ \ p,q\ =\ \{0,1 \} mpq=x,yBxpyqI(x,y)       p,q = {0,1}

    2、通过矩可以找到图像块的质心:
    C   =   ( m 10 m 00 , m 01 m 00 ) C\ =\ (\frac{m_{10}}{m_{00}},\frac{m_{01}}{m_{00}} ) C = (m00m10,m00m01)
    3、连接图像块的几何中心 O O O与质心 C C C,得到一个向量 O C → \overrightarrow{OC} OC ,于是特征点的方向可以定义为:
    θ   =   a r c t a n ( m 01 / m 10 ) \theta\ = \ arctan(m_{01}/m_{10}) θ = arctan(m01/m10)
    在公式中, I ( x , y ) I(x,y) I(x,y)表示像素坐标 ( x , y ) (x,y) (x,y)处的灰度值; m 10 m 00 = ∑ x , y ∈ B x I ( x , y ) ∑ x , y ∈ B I ( x , y ) \frac{m_{10}}{m_{00}}=\frac{\sum_{x,y\in B} xI(x,y)}{\sum_{x,y\in B} I(x,y)} m00m10=x,yBI(x,y)x,yBxI(x,y) x x x加权的总量与总和相比显示处 x x x在图像的那一列的时候像素值比较大,代表了图像像素在 x x x方向上的偏重,即是质心的 x x x坐标;同样的, m 01 m 00 = ∑ x , y ∈ B y I ( x , y ) ∑ x , y ∈ B I ( x , y ) \frac{m_{01}}{m_{00}}=\frac{\sum_{x,y\in B} yI(x,y)}{\sum_{x,y\in B} I(x,y)} m00m01=x,yBI(x,y)x,yByI(x,y)代表了图像像素在 y y y方向上的偏重,即是质心的 y y y坐标。

    4.2 rBRIEF描述子

    rBRIEF特征描述是在BREIF的基础上加入旋转因子改进的,下面首先介绍BREIF描述子的算法原理。

    4.2.1 BREIF描述子

    BRIEF算法计算出来的是一个二进制串的特诊描述符,它是在一个特征点的领域Path内,通过一种选定的方式来挑选N个点对,然后通过对每个点对 ( p , q ) (p,q) (p,q)比较这两点的亮度值,如果 I ( p ) > I ( q ) I(p)>I(q) I(p)>I(q)则这个点对生成的二进制串中一个值为1,如果 I ( p ) ≤ I ( q ) I(p)≤I(q) I(p)I(q)则对应二进制串中的值为0。一般的,N取128、256或512,OpenCV默认为256,并且为了增加描述子的抗噪性,算法首先需要对图像进行高斯平滑处理。

    关于点对的选择,假设我们在特征点的领域块 S × S S×S S×S内选择N个点对 ( p , q ) (p,q) (p,q),Calonder的实验中测试了5种采样方法:

    • 在图像块内平均采样;
    • p p p q q q都符合 ( 0 , 1 25 S 2 ) (0,\frac{1}{25}S^{2}) (0,251S2)的高斯分布;
    • p p p符合 ( 0 , 1 25 S 2 ) (0,\frac{1}{25}S^{2}) (0,251S2)的高斯分布,而 q q q符合 ( 0 , 1 100 S 2 ) (0,\frac{1}{100}S^{2}) (0,1001S2)的高斯分布;
    • 在空间量化极坐标下的离散位置随机采样;
    • p p p固定为 ( 0 , 0 ) (0,0) (0,0) q q q在周围平均采样;

    下面是以上5种采样方法的结果示意图:

    在这里插入图片描述在这里插入图片描述

    论文指出,第二种方法可以取得较好的匹配结果。

    经过上述步骤,我们可以得到每个特征点表示的一串二进制编码。

    4.2.2Hamming距离

    汉明距离是使用数据阐述差错制控制编码里面的,汉明距离是一个概念,它表示两个(相同长度)字对应位不同的数量,我们以 d ( x , y ) d(x,y) d(x,y)表示两个字 x , y x,y x,y之间的汉明距离。对两个字符串进行异或运算,并统计结果为1的个数,那么这个数就是汉明距离。

    • 两个特征编码对应bit位上相同元素的个数小于128的,一定不是配对的;
    • 一幅图上特征点与另一幅图上特征编码对应bit位上相同元素的个数最多的特征点配成一对。
      其实就是按位求异或的过程。(相同为0,不同为1)
    ABP
    000
    011
    101
    110

    所以,对于BRIEF来说,描述子里不包含旋转属性,一旦当匹配的图像有稍微大的旋转角度,按照Hamming算法,匹配度将大幅下降。

    4.2.3 rBRIEF描述子

    首先,进行前期的优化:

    1、采用积分图像来进行平滑;
    2、在特征点的31×31的领域内,随机产生点对,并以随机点为中心,取5×5的子窗口;
    3、比较两个随机点的子窗口内25个像素的大小进行编码。(而不仅仅是两个随机点了)

    其次,增加了旋转不变性:

    • 对于任意特征点,在31×31领域内位置为 ( x i , y i ) (x_i,y_i) (xi,yi) n n n对点集,可以用 2 × n 2×n 2×n的矩阵来表示:

    S = ( x 1 , . . . , x n y 1 , . . . , y n ) S=\left( \begin{matrix} x_1, & ... & ,x_n \\ y_1, & ... & ,y_n \end{matrix} \right) S=(x1,y1,......,xn,yn)

    • 利用FAST求出的特征点主方向 θ \theta θ和对应的旋转矩阵 R θ R_\theta Rθ,计算出来的 S θ S_\theta Sθ来代表 S S S

    θ   =   a r c t a n ( m 01 / m 10 ) \theta\ = \ arctan(m_{01}/m_{10}) θ = arctan(m01/m10)
    S = [ c o s θ − s i n θ s i n θ c o s θ ] S=\left[ \begin{matrix} cos\theta & -sin\theta \\ sin\theta & cos\theta \end{matrix} \right] S=[cosθsinθsinθcosθ]
    S θ = R θ S S_\theta = R_\theta S Sθ=RθS

    • 计算旋转描述子(steered BRIEF)

    g n ( p , θ ) : = f n ( p ) ∣ ( x i , y i ) ∈ S θ g_n(p,\theta):=f_n(p)|(x_i,y_i)\in S_\theta gn(p,θ):=fn(p)(xi,yi)Sθ

    其中, f n ( p ) : = ∑ i ≤ i ≤ n 2 i − 1 ( p ; x i , y i ) f_n(p):=\sum_{i≤i≤n}2^{i-1}(p;x_i,y_i) fn(p):=iin2i1(p;xi,yi)为BRIEF的描述子。

    steered BRIEF得到的描述子具有旋转不变性,但是描述子的相关性却不如原始的BRIEF算法,rBRIEF没有采用原始5种去点对的方法,而是通过机器学习的方法重新选择了点对集合。
    至此,ORB的优化就结束了。

    5 小结

    • FAST是用来寻找特征点的,ORB在FAST的基础上通过金字塔、灰度质心标定解决了尺度不变和旋转不变,即oFAST;
    • BRIEF是用来构造描述子的,ORB在BRIEF的基础上通过引入oFAST的选择角度和机器学习解决了选择特性和特征点难以区分的问题。
    • 最终,通过计算Hamming距离计算描述子的距离,即两特征之间的相似程度。

    参考文献
    [1] 《视觉SLAM十四讲从理论到实践》 高翔,张涛

    更多相关内容
  • ORB特征描述符,基于python源码实现,基于opencv库实现
  • ORB特征匹配中所使用到的图片,它们是来自公开数据集中的两张图片,相机发生了微小的运动。在特征匹配中有较好的显示效果。
  • 分别用SIFT、SURF、ORB特征匹配要求用绿色线条画出两张图对应的匹配点(出3张图) 再使用RANSAC滤除离群点(参数自行调优)后用绿色线条画出两张图对应的匹配点(出3张图) 然后根据对应点分别计算图B到图A的单应...
  • 对位姿变换较小的两幅图提取各自的ORB特征并进行匹配
  • ORB特征提取与匹配.zip

    2021-12-13 16:35:35
    orb特征提取与匹配
  • 一种改进ORB特征匹配的半稠密三维重建ORB-SLAM算法.docx
  • 计算ORB特征点和描述子,以进行特征点匹配,估计机器人位姿
  • 编写一个类,并在ROS下实现了两张图片的ORB特征提取与匹配,具体用法可以参考我的博文: https://blog.csdn.net/qinqinxiansheng/article/details/106975663
  • GPU加速与L-ORB特征提取的全景视频实时拼接,。。。。。。
  • ORB特征

    2021-11-23 13:40:08
    ORB特征由关键点和描述子组成 关键点选取:采用FAST算法 ORB 特征检测的第一步是查找图像中的关键点,而关键点检测算法即使用 FAST 算法。 FAST 是 Features from Accelerated Segments Test 的简称,可以快速选择...

    ORB特征由关键点和描述子组成

    关键点选取:采用FAST算法

    ORB 特征检测的第一步是查找图像中的关键点,而关键点检测算法即使用 FAST 算法。
    FAST 是 Features from Accelerated Segments Test 的简称,可以快速选择关键点,算法步骤如下:
    给与一个像素点 p,FAST 比较目标 p 圆圈范围中的 16 个像素,每个像素按高于 p,小于 p,或者与 p 相似,分为三类。

    注意这里的比较是带有阈值 h 的。对于给定的阈值 h,更亮的像素将是亮度超过 Ip+h 的像素,更暗的像素将是亮度低于 Ip-h 的像素,相似像素将是亮度在这两个值之间的像素。在对像素分类后, 如果圈圈上有 8 个以上的相连像素,暗于或亮于 p 则将像素 p 选作关键点。
    而 FAST 如此高效的原因是,仅将 p 与圆圈中的 4 个等距像素相比。这种方法已经证明和比较 16 个周围像素的效果相同。如果至少有一对连续像素的亮度高于或低于 p,则将 p 选作关键点。这种优化使得在整个图像中搜索关键点的时间缩短了四倍。

    但是,这些关键点可以像我们提供什么样的信息?对比邻近像素的亮度有何意义?首先观察一下 FAST 算法标记关键点的图像。

    可以看出关键点位于亮度有变化的区域,此类区域通常确定了某种边缘,例如猫的爪子。边缘定义了猫的界限,以及脸部区域的界限,因此这些关键点使我们能够识别这只猫,而不是图像中的任何其他对象或背景。

    然后是BRIEF描述子法:

    我们已经知道 ORB 如何使用 FAST 确定图像中的关键点,下面我们将了解 ORB 如何使用 BRIEF 算法,并将这些关键点转换为特征向量。

    ORB 算法的第二步是将第一个算法发现的关键点变成特征向量,这些特征向量可以共同表示一个对象。

    要创建特征向量,ORB 会用到 BRIEF 算法。BRIEF 是 Binary Robust Independent Elementary Features 的简称,它的作用是根据一组关键点创建二元特征向量。正如在简介视频中看到的,二元特征向量又称为二元描述符,是仅包含 1 和 0 的特征向量。在 BRIEF 中 每个关键点由一个二元特征向量描述,该向量一般为 128-512 位的字符串,其中仅包含 1 和 0。

    注意,这里的“位”是二进制位的简称,1 位只能存储一个二进制值,要么 1 要么是 0。位字符串是一组位,这些是位字符串示例。

    第一个是 1 位字符串 因此只存储 1 位。第二个是 2 位字符串,因此可以存储 2 个二进制位。在此示例中它存储的是 0 和 1。同样,第三个是 3 位字符串,因此可以存储 3 个位,以此类推。计算机运行的是二进制或机器代码,因此使用二元特征向量的一大优势是可以非常高效地存储在内存中,并且可以快速计算。速度对实时应用来说极为关键,这些特性不仅使 BRIEF 速度非常快,而且使 BRIEF 能够在计算资源非常有限的设备(例如智能手机)上运行。

    BRIEF 如何为每个点创建这些二元描述符?BRIEF 算法首先利用高斯核对给定图像进行平滑处理,以防描述符对高频噪点过于敏感。然后,对于给定关键点,例如猫爪上的这个点。

    BRIEF 从该关键点周围界定好的邻域内随机选择一对像素,关键点周围的邻域称为 Patch,它是一个具有特定像素宽度和高度的正方形。
    这里显示的随机对中的第一个像素,是一个蓝色正方形,它是从以关键点为中心的高斯分布中抽取的一个像素,标准偏差或分散趋势为 σ。

    这里显示为黄色正方形的像素,是随机对中的第二个像素。它是从以该第一个像素为中心的高斯分布中抽取的像素,标准偏差为 σ/2,经验表明这种高斯选择提高了特征匹配率。

    BRIEF 然后开始为关键点构建二元描述符,方法是如下所示地比较这两个像素的亮度。如果第一个像素比第二个亮,则为描述符中的相应位分配值 1,否则分配值 0。

    在这个示例中第二个像素比第一个亮,因此我们为特征向量的第一个位分配值 0。总共由256位,则此次比较后特征向量的第一个位对应的是这个关键点的第一个随机点对,然后 BRIEF 会针对同一关键点选择新的随机像素对比较它们的亮度并为特征向量中的下个位分配 1 或 0。

    在上面新选取的随机像素中,我们看到现在第一个像素比第二个亮,因此为特征向量中的第二个位分配值 1。

    对于 256 位向量,BRIEF 会针对同一关键点重复这一流程 256 次,然后转到下个关键点。接着将 256 个像素亮度比较结果放入该关键点的二元特征向量中。BRIEF 像这样为图像中的每个关键点创建一个向量。

    展开全文
  • ORB特征匹配

    千次阅读 2020-02-21 16:49:01
    个人博客:... ... 接<SLAM基本概念> ORB特征由关键点和描述子两部分组成,关键点称为“Oriented FAST”,是一种改进的FAST 角点。它的描述子称为BRIEF(Binary Robust Independent Elem...

     个人博客:http://www.chenjianqu.com/

    原文链接:http://www.chenjianqu.com/show-88.html

     接<SLAM基本概念>

        ORB特征关键点描述子两部分组成,关键点称为“Oriented FAST”,是一种改进的FAST 角点。它的描述子称为BRIEF(Binary Robust Independent Elementary Feature)。提取 ORB 特征分为如下两个步骤:

        1. FAST 角点提取:找出图像中的“角点”。相较于原始的 FAST,ORB 中计算了特征点的主方向,为BRIEF 描述子增加了旋转不变特性。

        2. BRIEF 描述子的计算:对前一步提取出特征点的周围图像区域进行描述。ORB 对 BRIEF 进行了改进,主要是在 BRIEF 中使用了先前计算的方向信息。

     

    FAST关键点

        FAST 是一种角点,主要检测局部像素灰度变化明显的地方,以速度快著称。它的思想是:如果一个像素与邻域的像素差别较大(过亮或过暗),那么它可能是角点。检测步骤如下:

    1. 在图像中选取像素 p,假设它的亮度为Ip

    2. 设置一个阈值 T(比如,Ip的20%)。

    3. 以像素 p 为中心,选取半径为3的圆上的16个像素点。

    4. 假如选取的圆上有连续的 N 个点的亮度大于 Ip+T 或小于 Ip−T,那么像素p  可以被认为是特征点(N通常取12,即为 FAST-12。其他常用的N取值为9和11,它们分别被称为FAST-9和FAST-11)。

    5. 循环以上四步,对每一个像素执行相同的操作。

        在FAST-12算法中,可以进行预测试操作,以快速地排除绝大多数不是角点的像素。具体操作为,对于每个像素,直接检测邻域圆上的第 1, 5, 9, 13 个像素的亮度。只有当这 4个像素中有 3 个同时大于 Ip+T 或小于 Ip−T 时,当前像素才有可能是一个角点,否则应该直接排除。这大大加速了角点检测。

        还需要用非极大值抑制(Non-maximal suppression),在一定区域内仅保留响应极大值的角点,避免角点集中的问题。

    1.jpg

        FAST特征点的计算仅仅是比较像素间亮度的差异,所以速度非常快。它的缺点是重复性不强,分布不均匀,不具有方向信息。同时,由于它固定取半径为3的圆,存在尺度问题:远处看着像是角点的地方,接近后看可能就不是角点了。

        针对 FAST 角点不具有方向性和尺度的弱点,ORB 添加了尺度和旋转的描述。尺度不变性由构建图像金字塔解决,在金字塔的每一层上检测角点。特征的旋转是由灰度质心法(Intensity Centroid)实现。

    图像金字塔

    2.jpg

        图像金字塔如上图,金字塔底层是原始图像,每往上一层,就对图像进行一个固定倍率的缩放,这样就有了不同分辨率的图像。较小的图像可以看成是远处看过来的场景。在特征匹配算法中,我们可以匹配不同层上的图像,从而实现尺度不变性。例如,如果相机在后退,那么我们应该能够在上一个图像金字塔的上层和下一个图像的下层中找到匹配。

    旋转不变性

        计算特征点附近的图像灰度质心来保持旋转不变性。所谓质心是指以图像块灰度值作为权重的中心。其具体操作步骤如下:

        1. 在一个小的图像块 B 中,定义图像块的矩为:

    3.jpg

        2. 通过矩可以找到图像块的质心:C=(m10/m00, m01/m00)

        3.连接图像块的几何中心O 与质心C,得到一个方向向量OC,于是特征点的方向可以定义为 θ = arctan(m01 m10)

        通过以上方法,FAST 角点便具有了尺度与旋转的描述,从而大大提升了其在不同图像之间表述的鲁棒性。所以在 ORB 中,把这种改进后的 FAST 称为 Oriented FAST

     

    BRIEF 描述子

        在提取 Oriented FAST 关键点后,对每个点计算其描述子,ORB 使用改进的BRIEF特征描述。BRIEF 是一种二进制描述子,其描述向量由许多个 0 和 1 组成,这里的 0 和 1 编码了关键点附近两个随机像素(比如p和q)的大小关系:如果 p 比 q 大,则取 1,反之就取 0。如果我们取了 128个这样的 p, q,最后就得到 128 维由 0、1 组成的向量。关于一对随机点的选择方法,ORB论文原作者测试了以下5种方法,发现方法(2)比较好:

    4.jpg

        N=256时的结果(一条线段的两端是一对点):

    5.jpg

        原始的 BRIEF 描述子不具有旋转不变性,因此在图像发生旋转时容易丢失。而 ORB 在 FAST 特征点提取阶段计算了关键点的方向,所以可以利用方向信息,计算了旋转之后的“Steer BRIEF”特征使 ORB 的描述子具有较好的旋转不变性。

     

    特征匹配

        经过上面两步,对于一幅图中的每一个特征点,都得到了一个128的二进制编码。接下来对有相似或重叠部分的两幅图像进行配准。特征配对是利用的汉明距离进行判决:

        1、两个特征编码对应bit位上相同元素的个数小于64的,一定不是配对的。

        2、一幅图上特征点与另一幅图上特征编码对应bit位上相同元素的个数最多的特征点配成一对。

        考虑两个时刻的图像。如果在图像 It 中提取到特征点 xtm,m = 1, 2, ..., M,在图像 It+1 中提取到特征点 xt+1n, n =1, 2, ..., N,如何寻找这两个集合元素的对应关系呢?

        最简单的特征匹配方法就是暴力匹配(Brute Force Matcher)。即对每一个特征点 xtm 与所有的 xt+1n 测量描述子的距离,然后排序,取最近的一个作为匹配点。描述子距离表示了两个特征之间的相似程度,在实际运用中还可以取不同的距离度量范数:对于浮点类型的描述子,使用欧氏距离进行度量即可,而对于二进制的描述子(比如BRIEF),往往使用汉明距离进行度量。

        (汉明距离 Hamming distance,即通过比较向量每一位是否相同,若不同则汉明距离加1。向量相似度越高,对应的汉明距离越小)

        然而,当特征点数量很大时,暴力匹配法的运算量将变得很大。此时快速近似最近邻(FLANN)算法更加适合于匹配点数量极多的情况。

     

     

    代码实现

    CMakeLists.txt

    cmake_minimum_required(VERSION 2.6)
    project(orbtest)
    set( CMAKE_CXX_FLAGS "-std=c++11" )
    #opencv
    find_package( OpenCV REQUIRED )
    include_directories( ${OpenCV_INCLUDE_DIRS} )
    add_executable(orbtest main.cpp)
    target_link_libraries(orbtest ${OpenCV_LIBS} )
    install(TARGETS orbtest RUNTIME DESTINATION bin)

     

    main.cpp

    #include <iostream>
    #include <opencv2/core/core.hpp>
    #include <opencv2/features2d/features2d.hpp>
    #include <opencv2/highgui/highgui.hpp>
    using namespace std;
    using namespace cv;
    
    int main(int argc, char **argv) 
    {
        std::cout << "Hello, world!" << std::endl;    
        //图片读取
        Mat img_1 = imread("1.png", CV_LOAD_IMAGE_COLOR);
        Mat img_2 = imread("2.png", CV_LOAD_IMAGE_COLOR);
        imshow("img_1", img_1);
        imshow("img_2",img_2);
        waitKey(0);
        
        //初始化
        std::vector<KeyPoint> keypoints_1, keypoints_2;
        Mat descriptors_1, descriptors_2;
        Ptr<FeatureDetector> detector = ORB::create();
        Ptr<DescriptorExtractor> descriptor = ORB::create();
        /*匹配算法可选:
            BruteForce (it uses L2 )
            BruteForce-L1
            BruteForce-Hamming
            BruteForce-Hamming(2)
            FlannBased
        */
        Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");
        
        //第一步:检测 Oriented FAST 角点位置
        detector->detect(img_1, keypoints_1);
        detector->detect(img_2, keypoints_2);
        
        //第二步:根据角点位置计算 BRIEF 描述子
        descriptor->compute(img_1, keypoints_1, descriptors_1);
        descriptor->compute(img_2, keypoints_2, descriptors_2);
        
        //绘制特征点
        Mat outimg1;
        drawKeypoints(img_1, keypoints_1, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
        imshow("ORB features", outimg1);
        waitKey(0);
        
        //第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
        vector<DMatch> matches;
        matcher->match(descriptors_1, descriptors_2, matches);
        
        //-- 第四步:匹配点对筛选
        // 计算最小距离    
        double min_d=matches[0].distance;
        for(int i=0;i<matches.size();i++){
            if(min_d>matches[i].distance)
                min_d=matches[i].distance;
        }
        cout<<"min_d= "<<min_d<<endl;
    
        //当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
        std::vector<DMatch> good_matches;
        for (int i = 0; i < descriptors_1.rows; i++) {
            if (matches[i].distance <= max(2 * min_d, 30.0)) 
                good_matches.push_back(matches[i]);
        }
        
        //-- 第五步:绘制匹配结果
        Mat img_match;
        Mat img_goodmatch;
        
        drawMatches(img_1, keypoints_1, img_2, keypoints_2, matches, img_match);
        drawMatches(img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch);
        
        imshow("all matches", img_match);
        imshow("good matches", img_goodmatch);
        waitKey(0);
        return 0;
    }

        提取的ORB特征:

    6.jpg

        所有匹配和过滤后的匹配:

    7.jpg

     

     

     

     

    参考文献

    [0]高翔.视觉SLAM14讲

    [1]hujingshuang.【特征检测】BRIEF特征点描述算法. https://blog.csdn.net/hujingshuang/article/details/46910259  .2015-07-16

     

    展开全文
  • ORB特征点提取

    2017-12-27 10:44:46
    这段代码是自己写的,会比opencv的简单易懂。提取了特征点后,进行了暴力匹配。暴力匹配的代码也是自己写的,看完后应该对特征点的提取和匹配有个直观的认识。
  • GPU加速与L-ORB特征提取的全景视频实时拼接.pdf
  • 基于ORB算法的特征匹配,opencv,C++
  • 文章目录前言一、ORB特征均匀化策略对性能的影响二、ORB特征金字塔三、ORB提取扩展图像四、ORB特征均匀化总结 前言 本博客结合哔哩大学视频ORBSLAM2【ORBSLAM2源码讲解专题一】ORB特征点提取与均匀化策略总结。 ...


    在这里插入图片描述

    前言

    本博客结合哔哩大学视频ORBSLAM2【ORBSLAM2源码讲解专题一】ORB特征点提取与均匀化策略和高翔的《视觉SLAM十四讲》总结。
    代码参照github的ORB_SLAM2_detailed_comments

    ORBSLAM2代码很经典,而且代码量大,会分成多个博客研究,下为slam专题的其他链接:
    代码要求环境做了总结,解决了配置中的一些常见问题:slam的环境配置大全–保姆教学
    代码如何运行记录:ORB_SLAM2代码的简介安装运行


    一、基础知识

    特征点法:我们需要从图像中选取比较有代表性的点,这些点在相机发生少量视角变化时会保持不变,在此基础上讨论相机位姿估计问题,在经典的SLAM模型中我们称他们为路标,在视觉SLAM中路标则是图像特征(feature)。

    特征点: 特征点是图像里一些特别的地方,我们可以把图像中的角点、边缘和区块都当成图像中的代表性的地方。为此,计算机视觉领域研究出了很多特征点的提取方法,如著名的SIFT、SURF、ORB,这些人工设计的特征点有一下特点:

    • 可重复性:形同的“区域”可以在不同的图像中找到。
    • 可区别性:不同的“区域”有不同的表达。
    • 高效率:同一图像中,特征点的数量远小于像素的数量。
    • 本地性:特征仅与一小片图像区域有关。

    关键点和描述子: 特征点由关键点(Key-point)和描述子(Descriptor)两部分组成,比如当要谈论SIFT特征时,是指“提取SIFT关键点,并计算SIFT描述子”两件事情。关键点是指该特征点在图像里的位置,有些特征点还具有朝向、大小等信息。描述子通常是一个向量,按照认为的方式描述该点周围像素的信息。描述子是按照“外观相似的特征应该有相似的描述子”的原则设计的。

    ORB特征: 不同的图像特征特点不同。例如SIFT充分考虑了图像变换中的关照、尺度等变化,很精确但计算量很大,普通的CPU无法实时计算SIFT特征,进行定位建图。ORB适当的降低了精度和健壮性以提升计算速度,在同一图像中提取1000个特征点,ORB要15.3ms,SURF要217.3ms,SIFT约花费5228.7ms。ORB特征也是由关键点和描述子组成,他的关键点称为“Oriented FAST”,Oriented FAST的详细描述在我的另一篇博客Oriented Fast神奇高效的代码实现方式——ORBSLAM2源码讲解(二)。他的描述子称为BRIEF(Binary Robust Independent Elementary Feature)。因此,提取ORB特征分为如下两个步骤:

    • FAST角点提取:找出图像中的“角点”。相较于原版的FAST,ORB中计算了特征点的主方向,为后续的BRIEF描述子增加了旋转不变的特性。

    • BRIEF描述子:对前一步提取出特征点的周围图像区域进行描述。

    其中ORB特征的描述为本博客的主要内容。

    二、ORB特征均匀化策略对性能的影响

    ORB特征提取相比较与其他的特征提取法有什么优势呢?

    ORB-SLAM2中的ORB特征均匀化提取方法 vs. OpenCV中的ORB函数:
    • 提高了ORB-SLAM2的轨迹精度和鲁棒性
    • 两者运行时间差别不大
    • 增加特征提取的均匀性可以提高系统精度
    • 但是可能会降低特征提取的重复性
    

    ORB-SLAM2与opencv对比如图:
    在这里插入图片描述

    有一篇知乎的文章对此做了总结:[ORB-SLAM2] ORB特征提取策略对ORB-SLAM2性能的影响

    综上没有直接用opencv里的提取函数,而是采用ORB提取,如何实现这些优质的特性呢?和opencv有什么区别呢?详见下文。

    三、ORB特征金字塔

    这个图像金字塔的作用是实现尺度的不变性,如下图所示:

    在这里插入图片描述
    蓝色的是图像,每往上走一层就长宽缩小二分之一,左一的红圈是实际中的一个区域在蓝色图中的成像,相机就在虚线位置。若镜头往前推,则这个点就变大了,每一层都跟着变大,但第二列变大后的第二层和第一列的第一层是一样大的,我们做特征匹配时,就可以拿第二列的第二层和第一列的第一层相匹配,这俩差距不大。若拿第一列的第一层和第二列的第一层匹配,因为差距过大是匹配不上的。同样第三列的第一层和第一列的第二层相匹配。由此可以实现无论镜头怎么移动,都可以相匹配。

    四、ORB提取扩展图像

    如下图所示,灰色区域的是原图,FAST特征点提取的时候需要以3pixels的半径的特征点去提取,所以对外扩充一个3的边,形成中间的方框。为了进行高斯模糊,所以又扩充了一个最大的框。
    在这里插入图片描述
    这一扩充功能对应的代码在src包的ORBextractor.cc的1682行。其注释如下:

    void ORBextractor::ComputePyramid(cv::Mat image)
    {
    	//开始遍历所有的图层
        for (int level = 0; level < nlevels; ++level)
        {
    		//获取本层图像的缩放系数
            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是扩展了边界的图像,masktemp并未使用
            Mat temp(wholeSize, image.type()), masktemp;
            // mvImagePyramid 刚开始时是个空的vector<Mat>
    		// 把图像金字塔该图层的图像指针mvImagePyramid指向temp的中间部分(这里为浅拷贝,内存相同)
            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);		//图像缩放的差值算法类型,这里的是线性插值算法
    
                // //!  原代码mvImagePyramid 并未扩充,上面resize应该改为如下
                // resize(image,	                //输入图像
    			// 	   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给出的解释:
    			
    			/*Various border types, image boundaries are denoted with '|'
    			* BORDER_REPLICATE:     aaaaaa|abcdefgh|hhhhhhh
    			* BORDER_REFLECT:       fedcba|abcdefgh|hgfedcb
    			* BORDER_REFLECT_101:   gfedcb|abcdefgh|gfedcba
    			* BORDER_WRAP:          cdefgh|abcdefgh|abcdefg
    			* BORDER_CONSTANT:      iiiiii|abcdefgh|iiiiiii  with some specified 'i'
    			*/
    			
    			//BORDER_ISOLATED	表示对整个图像进行操作
                // https://docs.opencv.org/3.4.4/d2/de8/group__core__array.html#ga2ac1049c2c3dd25c2b41bffe17658a36
    
            }
            else
            {
    			//对于第0层未缩放图像,直接将图像深拷贝到temp的中间,并且对其周围进行边界扩展。此时temp就是对原图扩展后的图像
                copyMakeBorder(image,			//这里是原图像
    						   temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD,
                               BORDER_REFLECT_101);            
            }
            // //! 原代码mvImagePyramid 并未扩充,应该添加下面一行代码
            // mvImagePyramid[level] = temp;
        }
    
    }
    

    五、ORB特征均匀化

    如何实现特征点的均匀化呢?如下图中要提取25个特征点,如何让这个25个特征点更均匀?在这里插入图片描述
    如下长图分五步实现:第一步只有一个node,整个就是一个节点。第二步,我们需要进行分裂,分成四份,总共有四个节点。因为我们想要的到25个特征,每个特征在一个节点内,所以4<25,还要继续分。第三步,继续分,16个节点出现一个节点内没有特征点,无效删除,16<25继续分。第四步继续分,本来有64个,除去无效的,30>25,所以结束分裂。第五步,从每个node里选择一个质量最好的点,比如左上有唯一特征点,不用挑了。左侧第二个,有两个特征点,选择响应值比较高的,最后选出来就是第五步图所示。
    在这里插入图片描述
    此处借助知乎的一篇文章:ORB-SLAM中的ORB特征(提取)

    提取流程概览:

    1. 构造金字塔
    2. 提取FAST角点
    3. 利用灰度质心法,计算旋转角度 [公式]
    4. 计算旋转后的BRIEF描述子

    原理比较简单,代码实现起来比较复杂,上面方法的代码注释解析如下(在src文件下的ORBextractor.cc的715行开始):

    vector<cv::KeyPoint> ORBextractor::DistributeOctTree(const vector<cv::KeyPoint>& vToDistributeKeys, const int &minX,
                                           const int &maxX, const int &minY, const int &maxY, const int &N, const int &level)
    {
        // Compute how many initial nodes
        // Step 1 根据宽高比确定初始节点数目
    	//计算应该生成的初始节点个数,根节点的数量nIni是根据边界的宽高比值确定的,一般是1或者2
        // ! bug: 如果宽高比小于0.5,nIni=0, 后面hx会报错
        const int nIni = round(static_cast<float>(maxX-minX)/(maxY-minY)); //此处的maxX等在“ORB提取扩展图像”的图片上,round为取整。
    
    	//一个初始的节点的x方向有多少个像素
        const float hX = static_cast<float>(maxX-minX)/nIni;
    
    	//存储有提取器节点的链表
        list<ExtractorNode> lNodes;
    
    	//存储初始提取器节点指针的vector
        vector<ExtractorNode*> vpIniNodes;
    
    	//重新设置其大小
        vpIniNodes.resize(nIni);
    
    	// Step 2 生成初始提取器节点
        for(int i=0; i<nIni; i++)
        {      
    		//生成一个提取器节点
            ExtractorNode ni;
    
    		//设置提取器节点的图像边界
    		//注意这里和提取FAST角点区域相同,都是“半径扩充图像”,特征点坐标从0 开始 
            ni.UL = cv::Point2i(hX*static_cast<float>(i),0);    //UpLeft
            ni.UR = cv::Point2i(hX*static_cast<float>(i+1),0);  //UpRight
    		ni.BL = cv::Point2i(ni.UL.x,maxY-minY);		        //BottomLeft
            ni.BR = cv::Point2i(ni.UR.x,maxY-minY);             //BottomRight
    
    		//重设vkeys大小
            ni.vKeys.reserve(vToDistributeKeys.size());
    
    		//将刚才生成的提取节点添加到链表中
    		//虽然这里的ni是局部变量,但是由于这里的push_back()是拷贝参数的内容到一个新的对象中然后再添加到列表中
    		//所以当本函数退出之后这里的内存不会成为“野指针”
            lNodes.push_back(ni);
    		//存储这个初始的提取器节点句柄
            vpIniNodes[i] = &lNodes.back();
        }
    
        //Associate points to childs
        // Step 3 将特征点分配到子提取器节点中
        for(size_t i=0;i<vToDistributeKeys.size();i++)
        {
    		//获取这个特征点对象
            const cv::KeyPoint &kp = vToDistributeKeys[i];
    		//按特征点的横轴位置,分配给属于那个图像区域的提取器节点(最初的提取器节点)
            vpIniNodes[kp.pt.x/hX]->vKeys.push_back(kp);
        }
        
    	// Step 4 遍历此提取器节点列表,标记那些不可再分裂的节点,删除那些没有分配到特征点的节点
        // ? 这个步骤是必要的吗?感觉可以省略,通过判断nIni个数和vKeys.size() 就可以吧
        list<ExtractorNode>::iterator lit = lNodes.begin();
        while(lit!=lNodes.end())
        {
    		//如果初始的提取器节点所分配到的特征点个数为1
            if(lit->vKeys.size()==1)
            {
    			//那么就标志位置位,表示此节点不可再分
                lit->bNoMore=true;
    			//更新迭代器
                lit++;
            }
            ///如果一个提取器节点没有被分配到特征点,那么就从列表中直接删除它
            else if(lit->vKeys.empty())
                //注意,由于是直接删除了它,所以这里的迭代器没有必要更新;否则反而会造成跳过元素的情况
                lit = lNodes.erase(lit);			
            else
    			//如果上面的这些情况和当前的特征点提取器节点无关,那么就只是更新迭代器 
                lit++;
        }
    
        //结束标志位清空
        bool bFinish = false;
    
    	//记录迭代次数,只是记录,并未起到作用
        int iteration = 0;
    
    	//声明一个vector用于存储节点的vSize和句柄对
    	//这个变量记录了在一次分裂循环中,那些可以再继续进行分裂的节点中包含的特征点数目和其句柄
        vector<pair<int,ExtractorNode*> > vSizeAndPointerToNode;
    
    	//调整大小,这里的意思是一个初始化节点将“分裂”成为四个
        vSizeAndPointerToNode.reserve(lNodes.size()*4);
    
        // Step 5 利用四叉树方法对图像进行划分区域,均匀分配特征点
        while(!bFinish)
        {
    		//更新迭代次数计数器,只是记录,并未起到作用
            iteration++;
    
    		//保存当前节点个数,prev在这里理解为“保留”比较好
            int prevSize = lNodes.size();
    
    		//重新定位迭代器指向列表头部
            lit = lNodes.begin();
    
    		//需要展开的节点计数,这个一直保持累计,不清零
            int nToExpand = 0;
    
    		//因为是在循环中,前面的循环体中可能污染了这个变量,所以清空
    		//这个变量也只是统计了某一个循环中的点
    		//这个变量记录了在一次分裂循环中,那些可以再继续进行分裂的节点中包含的特征点数目和其句柄
            vSizeAndPointerToNode.clear();
    
            // 将目前的子区域进行划分
    		//开始遍历列表中所有的提取器节点,并进行分解或者保留
            while(lit!=lNodes.end())
            {
    			//如果提取器节点只有一个特征点,
                if(lit->bNoMore)
                {
                    // If node only contains one point do not subdivide and continue
    				//那么就没有必要再进行细分了
                    lit++;
    				//跳过当前节点,继续下一个
                    continue;
                }
                else
                {
                    // If more than one point, subdivide
    				//如果当前的提取器节点具有超过一个的特征点,那么就要进行继续分裂
                    ExtractorNode n1,n2,n3,n4;
    
    				//再细分成四个子区域,DivideNode函数是进行结点划分
                    lit->DivideNode(n1,n2,n3,n4); 
    
                    // Add childs if they contain points
    				//如果这里分出来的子区域中有特征点,那么就将这个子区域的节点添加到提取器节点的列表中
    				//注意这里的条件是,有特征点即可
                    if(n1.vKeys.size()>0)
                    {
    					//注意这里也是添加到列表前面的
                        lNodes.push_front(n1);   
    
    					//再判断其中子提取器节点中的特征点数目是否大于1
                        if(n1.vKeys.size()>1)
                        {
    						//如果有超过一个的特征点,那么待展开的节点计数加1
                            nToExpand++;
    
    						//保存这个特征点数目和节点指针的信息
                            vSizeAndPointerToNode.push_back(make_pair(n1.vKeys.size(),&lNodes.front()));
    
    						//?这个访问用的句柄貌似并没有用到?
                            // lNodes.front().lit 和前面的迭代的lit 不同,只是名字相同而已
                            // lNodes.front().lit是node结构体里的一个指针用来记录节点的位置
                            // 迭代的lit 是while循环里作者命名的遍历的指针名称
                            lNodes.front().lit = lNodes.begin();
                        }
                    }
                    //后面的操作都是相同的,这里不再赘述
                    if(n2.vKeys.size()>0)
                    {
                        lNodes.push_front(n2);
                        if(n2.vKeys.size()>1)
                        {
                            nToExpand++;
                            vSizeAndPointerToNode.push_back(make_pair(n2.vKeys.size(),&lNodes.front()));
                            lNodes.front().lit = lNodes.begin();
                        }
                    }
                    if(n3.vKeys.size()>0)
                    {
                        lNodes.push_front(n3);
                        if(n3.vKeys.size()>1)
                        {
                            nToExpand++;
                            vSizeAndPointerToNode.push_back(make_pair(n3.vKeys.size(),&lNodes.front()));
                            lNodes.front().lit = lNodes.begin();
                        }
                    }
                    if(n4.vKeys.size()>0)
                    {
                        lNodes.push_front(n4);
                        if(n4.vKeys.size()>1)
                        {
                            nToExpand++;
                            vSizeAndPointerToNode.push_back(make_pair(n4.vKeys.size(),&lNodes.front()));
                            lNodes.front().lit = lNodes.begin();
                        }
                    }
    
                    //当这个母节点expand之后就从列表中删除它了,能够进行分裂操作说明至少有一个子节点的区域中特征点的数量是>1的
                    // 分裂方式是后加的节点先分裂,先加的后分裂
                    lit=lNodes.erase(lit);
    
    				//继续下一次循环,其实这里加不加这句话的作用都是一样的
                    continue;
                }//判断当前遍历到的节点中是否有超过一个的特征点
            }//遍历列表中的所有提取器节点
    
            // Finish if there are more nodes than required features or all nodes contain just one point
            //停止这个过程的条件有两个,满足其中一个即可:
            //1、当前的节点数已经超过了要求的特征点数
            //2、当前所有的节点中都只包含一个特征点
            if((int)lNodes.size()>=N 				//判断是否超过了要求的特征点数
    			|| (int)lNodes.size()==prevSize)	//prevSize中保存的是分裂之前的节点个数,如果分裂之前和分裂之后的总节点个数一样,说明当前所有的
    												//节点区域中只有一个特征点,已经不能够再细分了
            {
    			//停止标志置位
                bFinish = true;
            }
    
            // Step 6 当再划分之后所有的Node数大于要求数目时,就慢慢划分直到使其刚刚达到或者超过要求的特征点个数
            //可以展开的子节点个数nToExpand x3,是因为一分四之后,会删除原来的主节点,所以乘以3
            /**
    		 * //?BUG 但是我觉得这里有BUG,虽然最终作者也给误打误撞、稀里糊涂地修复了
    		 * 注意到,这里的nToExpand变量在前面的执行过程中是一直处于累计状态的,如果因为特征点个数太少,跳过了下面的else-if,又进行了一次上面的遍历
    		 * list的操作之后,lNodes.size()增加了,但是nToExpand也增加了,尤其是在很多次操作之后,下面的表达式:
    		 * ((int)lNodes.size()+nToExpand*3)>N
    		 * 会很快就被满足,但是此时只进行一次对vSizeAndPointerToNode中点进行分裂的操作是肯定不够的;
    		 * 理想中,作者下面的for理论上只要执行一次就能满足,不过作者所考虑的“不理想情况”应该是分裂后出现的节点所在区域可能没有特征点,因此将for
    		 * 循环放在了一个while循环里面,通过再次进行for循环、再分裂一次解决这个问题。而我所考虑的“不理想情况”则是因为前面的一次对vSizeAndPointerToNode
    		 * 中的特征点进行for循环不够,需要将其放在另外一个循环(也就是作者所写的while循环)中不断尝试直到达到退出条件。 
    		 * */
            else if(((int)lNodes.size()+nToExpand*3)>N)
            {
    			//如果再分裂一次那么数目就要超了,这里想办法尽可能使其刚刚达到或者超过要求的特征点个数时就退出
    			//这里的nToExpand和vSizeAndPointerToNode不是一次循环对一次循环的关系,而是前者是累计计数,后者只保存某一个循环的
    			//一直循环,直到结束标志位被置位
                while(!bFinish)
                {
    				//获取当前的list中的节点个数
                    prevSize = lNodes.size();
    
    				//保留那些还可以分裂的节点的信息, 这里是深拷贝
                    vector<pair<int,ExtractorNode*> > vPrevSizeAndPointerToNode = vSizeAndPointerToNode;
    				//清空
                    vSizeAndPointerToNode.clear();
    
                    // 对需要划分的节点进行排序,对pair对的第一个元素进行排序,默认是从小到大排序
    				// 优先分裂特征点多的节点,使得特征点密集的区域保留更少的特征点
                    //! 注意这里的排序规则非常重要!会导致每次最后产生的特征点都不一样。建议使用 stable_sort
                    sort(vPrevSizeAndPointerToNode.begin(),vPrevSizeAndPointerToNode.end());
    
    				//遍历这个存储了pair对的vector,注意是从后往前遍历
                    for(int j=vPrevSizeAndPointerToNode.size()-1;j>=0;j--)
                    {
                        ExtractorNode n1,n2,n3,n4;
    					//对每个需要进行分裂的节点进行分裂
                        vPrevSizeAndPointerToNode[j].second->DivideNode(n1,n2,n3,n4);
    
                        // Add childs if they contain points
    					//其实这里的节点可以说是二级子节点了,执行和前面一样的操作
                        if(n1.vKeys.size()>0)
                        {
                            lNodes.push_front(n1);
                            if(n1.vKeys.size()>1)
                            {
    							//因为这里还有对于vSizeAndPointerToNode的操作,所以前面才会备份vSizeAndPointerToNode中的数据
    							//为可能的、后续的又一次for循环做准备
                                vSizeAndPointerToNode.push_back(make_pair(n1.vKeys.size(),&lNodes.front()));
                                lNodes.front().lit = lNodes.begin();
                            }
                        }
                        if(n2.vKeys.size()>0)
                        {
                            lNodes.push_front(n2);
                            if(n2.vKeys.size()>1)
                            {
                                vSizeAndPointerToNode.push_back(make_pair(n2.vKeys.size(),&lNodes.front()));
                                lNodes.front().lit = lNodes.begin();
                            }
                        }
                        if(n3.vKeys.size()>0)
                        {
                            lNodes.push_front(n3);
                            if(n3.vKeys.size()>1)
                            {
                                vSizeAndPointerToNode.push_back(make_pair(n3.vKeys.size(),&lNodes.front()));
                                lNodes.front().lit = lNodes.begin();
                            }
                        }
                        if(n4.vKeys.size()>0)
                        {
                            lNodes.push_front(n4);
                            if(n4.vKeys.size()>1)
                            {
                                vSizeAndPointerToNode.push_back(make_pair(n4.vKeys.size(),&lNodes.front()));
                                lNodes.front().lit = lNodes.begin();
                            }
                        }
    
                        //删除母节点,在这里其实应该是一级子节点
                        lNodes.erase(vPrevSizeAndPointerToNode[j].second->lit);
    
    					//判断是是否超过了需要的特征点数?是的话就退出,不是的话就继续这个分裂过程,直到刚刚达到或者超过要求的特征点个数
    					//作者的思想其实就是这样的,再分裂了一次之后判断下一次分裂是否会超过N,如果不是那么就放心大胆地全部进行分裂(因为少了一个判断因此
    					//其运算速度会稍微快一些),如果会那么就引导到这里进行最后一次分裂
                        if((int)lNodes.size()>=N)
                            break;
                    }//遍历vPrevSizeAndPointerToNode并对其中指定的node进行分裂,直到刚刚达到或者超过要求的特征点个数
    
                    //这里理想中应该是一个for循环就能够达成结束条件了,但是作者想的可能是,有些子节点所在的区域会没有特征点,因此很有可能一次for循环之后
    				//的数目还是不能够满足要求,所以还是需要判断结束条件并且再来一次
                    //判断是否达到了停止条件
                    if((int)lNodes.size()>=N || (int)lNodes.size()==prevSize)
                        bFinish = true;				
                }//一直进行nToExpand累加的节点分裂过程,直到分裂后的nodes数目刚刚达到或者超过要求的特征点数目
            }//当本次分裂后达不到结束条件但是再进行一次完整的分裂之后就可以达到结束条件时
        }// 根据兴趣点分布,利用4叉树方法对图像进行划分区域
    
        // Retain the best point in each node
        // Step 7 保留每个区域响应值最大的一个兴趣点
        //使用这个vector来存储我们感兴趣的特征点的过滤结果
        vector<cv::KeyPoint> vResultKeys;
    
    	//调整容器大小为要提取的特征点数目
        vResultKeys.reserve(nfeatures);
    
    	//遍历这个节点链表
        for(list<ExtractorNode>::iterator lit=lNodes.begin(); lit!=lNodes.end(); lit++)
        {
    		//得到这个节点区域中的特征点容器句柄
            vector<cv::KeyPoint> &vNodeKeys = lit->vKeys;
    
    		//得到指向第一个特征点的指针,后面作为最大响应值对应的关键点
            cv::KeyPoint* pKP = &vNodeKeys[0];
    
    		//用第1个关键点响应值初始化最大响应值
            float maxResponse = pKP->response;
    
    		//开始遍历这个节点区域中的特征点容器中的特征点,注意是从1开始哟,0已经用过了
            for(size_t k=1;k<vNodeKeys.size();k++)
            {
    			//更新最大响应值
                if(vNodeKeys[k].response>maxResponse)
                {
    				//更新pKP指向具有最大响应值的keypoints
                    pKP = &vNodeKeys[k];
                    maxResponse = vNodeKeys[k].response;
                }
            }
    
            //将这个节点区域中的响应值最大的特征点加入最终结果容器
            vResultKeys.push_back(*pKP);
        }
    
        //返回最终结果容器,其中保存有分裂出来的区域中,我们最感兴趣、响应值最大的特征点
        return vResultKeys;
    }
    
    

    总结

    提示:这里对文章进行总结:例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

    展开全文
  • ORB特征点的理论知识
  • 本发明涉及图像处理技术领域,具体涉及一种改进的ORB特征匹配方法。背景技术:图像匹配技术通过对影像内容、特征、结构、关系、纹理及灰度等的对应关系,分析相似性和一致性,寻求相似影像目标。图像匹配技术广泛...
  • opencv (五十四)ORB特征

    千次阅读 2022-04-10 11:54:23
    orb = ORB::create(500, //特征点数目 1.2f, //金字塔层级之间的缩放比例 8, //金字塔图像层数系数 31, //边缘阈值 0, //原图在金字塔中的层数 2, //生成描述子时需要用的像素点数目 ORB::HARRIS_SCORE...
  • 在ubuntu系统里,编写并调用一个类,实现了图像ORB特征匹配。程序已编译测试通过,即图实现可以参考博文: https://blog.csdn.net/qinqinxiansheng/article/details/106975782
  • 针对在动态环境下受运动物体影响而不能准确进行机器人运动估计的问题,提出一种基于ORB特征区域分割的视觉里程计算法。利用相邻区域特征点三维空间距离不变性,对提取的特征点进行区域分割,将图像中运动物体产生的特征...
  • 文章目录前言一、关键点和描述子1.FAST关键点:2.FAST 描述子:二、 ...随着slam的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习,本文就介绍了orb-slam的基础内容。 一、关键点和描述子 1.FAST关键点
  • 图像特征之ORB特征详解

    千次阅读 2020-07-07 10:57:54
    一般而言,人工设计的特征点(比如SIFT、SURF、ORB等)都具有如下的性质: 可重复性:即相同的“区域“可以在不同的图像中找到(比如将特征点比作一只猫,在图一和图二中都能找到这只猫)。 可区别性:即不同的”...
  • 视觉SLAM--ORB特征简介

    2021-11-03 13:53:53
    视觉SLAM中ORB特征简介 特征点由关键点(Key-point)和描述子(Descriptor)两部分组成。其中,关键点是指该特征点在图像里的位置,有些特征点还具有朝向、大小等信息。描述子通常是一个向量,按照某种人为设计的...
  • ORB特征点匹配

    千次阅读 多人点赞 2020-02-14 20:33:28
    文章目录特征点ORB特征oFAST角点提取BRIEF 描述子(Binary robust independent elementary feature)特征匹配实践参考 特征点 特征点是图像中具有代表性的点,这些点在图像发生变化时,比如图像的旋转、缩放,将...
  • 针对ORB算法特征匹配精度低的缺陷,结合金字塔光流特性,提出一种优化ORB特征匹配的方法。首先,采用区域分块法对待匹配图像进行处理,挑选出最佳匹配子块,缩小无效匹配区域;接着,对子块提取ORB关键字并计算匹配描述子...
  • ORB-SLAM中的ORB特征提取 0.提取流程概览 1.构造金字塔 2.提取FAST角点 3.利用灰度质心法,计算旋转角度 [公式] 4.计算旋转后的BRIEF描述子 1.构造金字塔 2.提取FAST角点 2.1 如何分配每一层提取的特征点数量 金字塔...
  • (八) ORB特征提取

    2022-01-30 14:59:26
    ORB采用FAST(features from accelerated segment test)算法来检测特征点。这个定义基于特征点周围的图像灰度值,检测候选特征点周围一圈的像素值,如果候选点周围领域内有足够多的像素点与该候选点的灰度值差别够...
  • ORB算法最大的特点就是计算速度快,这首先得益于使用FAST检测特征

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,226
精华内容 4,890
关键字:

orb特征

友情链接: MTPA.zip