VSLAM系列原创01讲 | 深入理解ORB关键点提取:原理+代码

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达
本文转自 | 计算机视觉life

ORB特征提取

小白:师兄,ORB-SLAM我一直都有听过,ORB应该是一种特征点,这个开源算法能够用ORB这种特征点命名,一定是因为这种特征点具有非常大的优势吧?

师兄:没错。ORB(Oriented FAST and Rotated BRIEF)特征点出自美国的Willow Garage公司在2012年发表的一篇论文,题目为“ORB: an efficient alternative to SIFT or SURF”。ORB-SLAM整个框架确实得益于该特征点,下面我们就来具体了解一下ORB。

ORB特征

师兄:我们知道特征点是由关键点(keypoint)和描述子(descriptor)两部分组成的。关键点是指该特征点在图像中的位置,有些还包括方向、大小等信息。而描述子是用来量化描述该关键点周围的像素信息的,这里的量化一般是人为设计的某种方式,设计的原则就是“外观相似的特征应该具有相似的描述子”,这样我们想要判断两个不同位置的关键点是否相似,就可以通过计算他们之间描述子的距离来确定了。

小白:那ORB特征点也有关键点和描述子咯?

师兄:是的。ORB的关键点是在FAST(Features from Accelerated Segments Test)关键点基础上进行了改进,主要是增加了特征点的主方向,称之为Oriented FAST。描述子是在BRIEF(Binary Robust Independent Elementary Features)描述子基础上加入了上述方向信息,称之为Rotated BRIEF。

关键点 Oriented  FAST

FAST角点

师兄:我们先来了解一下FAST关键点,它是一种检测角点的方法。和它的英文缩写意义一样,FAST确定关键点的速度非常快。

小白:请问什么是角点呢?

师兄:我们前面提到的特征点其实是图像里一些比较特殊的地方。我们可以大致分为3类:平坦区域、边缘和角点。如下图所示。我们在图像中取一个小窗口来判断局部区域的类型。从图中可以看到,左边平坦区域内部沿所有方向灰度都是没有变化的;而中间的边缘沿水平方向灰度是有变化的,但是垂直方向没有变化;右边的角点则沿所有方向都有灰度变化。这个角点就是图像中有辨识度的点。而FAST就是一种高效的角点判定方法。


小白:那FAST也要这样用一个窗口来统计里面所有像素灰度的变化吗?

师兄:不需要,那样太慢了。FAST的思想是这样的:如果一个像素和它周围的像素灰度差别较大(超过设定的阈值),并且达到一定的数目,那么这个像素很可能就是角点。具体检测过程如下:

  • 第1步:在图像中选择某个像素 ,它的灰度值记为

  • 第2步:设定一个阈值 ,用于判断两个像素灰度值差异大小,为了能够自适应不同的图像,一般采用相对百分比例,比如设置为 的20%。

  • 第3步:以像素 为中心,选取半径为3的圆上的16个像素点。选取方式见下图右所示。

  • 第4步:如果16个像素点中有连续的 个点的灰度大于 或者小于 ,那么可以将像素 确定为关键点。在ORB的论文中,作者说 时效果较好,称之为FAST-9。实际操作中为了加速,我们可以把第1,5,9,13个像素点当做锚点,在FAST-9算法中,只有当这4个锚点中有3个及以上灰度值同时大于 或者小于 ,当前像素才可能是一个关键点进入到下一个阶段的判断,否则就可以排除掉,这大大加速了关键点检测的速度。

  • 第5步:遍历图像中每个像素点,循环执行以上四个步骤。

此外,由于FAST关键点很容易扎堆出现,所以第一次遍历完图像后还需要用到非极大值抑制,在一定范围内保留响应值最大的作为该范围内的FAST关键点。

 FAST关键点
为什么需要图像金字塔?

小白:根据上面的描述,FAST固定选取的是半径为3的圆,那这个关键点是不是就和相机拍摄物体的分辨率有关了?如下图所示,我在初始位置(下图(a)中 Original position)通过FAST-9判定是关键点,但是当相机靠近物体进行拍摄(下图(b)中 Forward),此时这个角点占用的像素数目就会变多,固定的半径3的圆就检测不到角点了?或者当相机远离物体进行拍摄(下图(c)中 Backward),此时这个角点占用的像素数目就会变少,固定的半径3的圆也检测不到角点?

相机运动带来的尺度问题

师兄:是的,这个就是FAST存在的问题,我们称之为尺度问题,ORB特征点使用图像金字塔来确保特征点的尺度不变性。图像金字塔是计算机视觉领域中常用的一种方法。如下图所示,金字塔底层是原始图像,在ORB-SLAM2里对应的金字塔层级是 。每往上一层,就对图像做一个固定倍率的缩放,得到不同分辨率的图像。在提取ORB特征点的时候,我们会在每一个金字塔层级上进行特征提取,这样不管相机拍摄距离物体是远还是近,我们可以在某个金字塔层级提取到真正的角点。我们在对不同图像特征点进行特征匹配时,就可以匹配不同图像里不同层级的金字塔上提取的特征点,实现尺度不变性。

图像金字塔
灰度质心法

小白:原来如此。那么ORB特征点的旋转不变性是怎么实现的呢?

师兄:所谓旋转不变性,思路也比较直观,就是先想办法计算每个关键点的“主方向”,然后统一将像素旋转到这个“主方向”,这样就使得每个特征点的描述子不受旋转的影响了。

小白:那怎么计算关键点的“主方向”呢?

师兄:我们使用灰度质心(Intensity Centroid)法来计算关键点的“主方向”。这里的灰度质心就是一个图像区域内像素灰度值作为权重的中心,是需要我们计算的。这里的图像区域一般是圆形区域,在ORB-SLAM2里面设定的是直径为31的圆形。这个圆形的圆心叫做形心,也就是几何中心。这个形心指向质心的向量就代表这个关键点的“主方向”。

下面重点说一下如何计算灰度质心。

  • 第1步:我们定义该区域图像的矩为:

    式中, 取0或者1, 表示在像素坐标 处图像的灰度值, 表示图像的矩。

    在半径为 的圆形图像区域,沿两个坐标轴 方向的图像矩分别为:

    圆形区域内所有像素的灰度值总和为:

  • 第2步:图像的质心为:

  • 第3步:然后关键点的“主方向”就可以表示为从圆形图像形心 指向质心 的方向向量 ,于是关键点的旋转角度记为

以上是灰度质心法求关键点旋转角度的原理。

ORB-SLAM2代码实现时,使用了一些技巧来加速计算灰度质心。下面讲一下背后的原理和流程。

  • 第1步:我们要处理的是一个圆形图像区域,而圆形是具有对称性的,加速的原理就是根据对称性一次索引多行像素。因此首先我们把索引基准点放在圆形的中心像素点,记为

  • 第2步:圆形半径记为 ,先计算圆形区域内水平坐标轴上的一行像素灰度(下图中红色区域),对应的坐标范围是 ,这一行对应的图像矩分别为:

  • 第3步:然后以水平坐标轴为对称轴,一次性索引与水平轴上下对称的两行像素(下图中绿色区域),上下某两个对称的像素分别记为:

    则这两个点对应的图像矩分别为:

    最后累加即可。


/**
 * @brief 这个函数用于计算特征点的方向,这里是返回角度作为方向。
 * 计算特征点方向是为了使得提取的特征点具有旋转不变性。
 * 方法是灰度质心法:以几何中心和灰度质心的连线作为该特征点方向
 * @param[in] image     要进行操作的某层金字塔图像
 * @param[in] pt        当前特征点的坐标
 * @param[in] u_max     图像块的每一行的坐标边界 u_max
 * @return float        返回特征点的角度,范围为[0,360)角度,精度为0.3°
 */
static float IC_Angle(const Mat& image, Point2f pt,  const vector<int> & u_max)
{
 //图像的矩,前者是按照图像块的y坐标加权,后者是按照图像块的x坐标加权
    int m_01 = 0, m_10 = 0;

 //获得这个特征点所在的图像块的中心点坐标灰度值的指针center
    const uchar* center = &image.at<uchar> (cvRound(pt.y), cvRound(pt.x));

 //这条v=0中心线的计算需要特殊对待
    //由于是中心行+若干行对,所以PATCH_SIZE应该是个奇数
    for (int u = -HALF_PATCH_SIZE; u <= HALF_PATCH_SIZE; ++u)
  //注意这里的center下标u可以是负的!中心水平线上的像素按x坐标(也就是u坐标)加权
        m_10 += u * center[u];

 //这里的step1表示这个图像一行包含的字节总数
    int step = (int)image.step1();
 //注意这里是以v=0中心线为对称轴,然后对称地每成对的两行之间进行遍历,这样处理加快了计算速度
    for (int v = 1; v <= HALF_PATCH_SIZE; ++v)
    {
        // Proceed over the two lines
  //本来m_01应该是一列一列地计算的,但是由于对称以及坐标x,y正负的原因,可以一次计算两行
        int v_sum = 0;
  // 获取某行像素横坐标的最大范围,注意这里的图像块是圆形的!
        int d = u_max[v];
  //在坐标范围内挨个像素遍历,实际是一次遍历2个
        // 假设每次处理的两个点坐标,中心线下方为(x,y),中心线上方为(x,-y) 
        // 对于某次待处理的两个点:m_10 = Σ x*I(x,y) =  x*I(x,y) + x*I(x,-y) = x*(I(x,y) + I(x,-y))
        // 对于某次待处理的两个点:m_01 = Σ y*I(x,y) =  y*I(x,y) - y*I(x,-y) = y*(I(x,y) - I(x,-y))
        for (int u = -d; u <= d; ++u)
        {
   //得到需要进行加运算和减运算的像素灰度值
   //val_plus:在中心线下方x=u时的的像素灰度值
            //val_minus:在中心线上方x=u时的像素灰度值
            int val_plus = center[u + v*step], val_minus = center[u - v*step];
   //在v(y轴)上,2行所有像素灰度值之差
            v_sum += (val_plus - val_minus);
   //u轴(也就是x轴)方向上用u坐标加权和(u坐标也有正负符号),相当于同时计算两行
            m_10 += u * (val_plus + val_minus);
        }
        //将这一行上的和按照y坐标加权
        m_01 += v * v_sum;
    }

    //为了加快速度还使用了fastAtan2()函数,输出为[0,360)角度,精度为0.3°
    return fastAtan2((float)m_01, (float)m_10);
}

(左右滑动看完整代码)

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。

下载2:Python视觉实战项目52讲

在「小白学视觉」公众号后台回复:Python视觉实战项目即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。

下载3:OpenCV实战项目20讲

在「小白学视觉」公众号后台回复:OpenCV实战项目20讲即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值