ORB 算法学习笔记

作者: 开飞机的乔巴 | 来源:发表于2019-08-21 21:14 被阅读34次

    本博客内容来源于网络以及其他书籍,结合自己学习的心得进行重编辑,因为看了很多文章不便一一标注引用,如图片文字等侵权,请告知删除。

    传统2D计算机视觉学习笔记目录------->传送门
    传统3D计算机视觉学习笔记目录------->传送门

    前言

    休息了一天,雨天去了一次颐和园,不幸,我感冒了。所以,如果本文有任何常识性问题,请不要怀疑作者的知识水平,而是脑子有点烧糊涂了。前面我们描述完了sift以及surf算法,本片我们描述一下orb特征检测算法。

    今天一看,竟然已经写了15篇文章了,也为自己的坚持点个赞 ~ (≧▽≦)/ ~ 。的的确确这段时间,对一些算法的理解也有了更深的层次,再接再厉,今晚奖励给我家猫奖励一罐小鱼干。

    ORB简介

    ORB 全称:Oriented FAST and Rotated BRIEF,是一种快速特征点提取和描述的算法,发布于“ORB:An Efficient Alternative to SIFT or SURF” 论文中。从名字中,我们可以看出是由两部分构成,Oriented FAST 和 Rotated BRIEF,这也以最简单的语言描述了ORB算法。ORB算法分为两部分,分别是特征点提取和特征点描述。特征提取是由FAST算法(前面已经学过)发展来的,特征点描述是根据BRIEF特征描述算法改进的。

    ORB算法最大的特点就是计算速度快,计算时间大概只有SIFT的1%,SURF的10%,这主要是因为使用了FAST来加速了特征点的提取。再次是使用BRIEF算法计算描述子,该描述子特有的2进制串的表现形式不仅节约了存储空间,而且大大缩短了匹配的时间。

    当然ORB算法也有一些缺点,比如尺度变换的应对能力比较低。接下来我们来看ORB的流程,来分析一下ORB特征提取的优缺点。

    ORB 算法流程

    1. 关键点提取

    有关FAST算法,可以跳转去看另一篇来详细描述FAST特征点的笔记:FAST角点学习笔记中查看。本节主要描述ORB算法中对FAST算法的改进。

    ORB对FAST的改进或者拓展,主要是为其增加了其尺度不变性以及旋转不变性。接下里来看一看怎么实现的。

    1. 提取FAST特征点

    通过FAST算法提取出FAST特征点,过程不在详细描述,这样我们就找到了一张图片的基本的关键点。

    2. 建立金字塔

    ORB实现尺度不变性,也是通过图像金字塔来实现的。ORB论文本身并没有解决尺度不变性,而是在opencv实现中添加了这部分,具体做法为设置一个比例因子scale(opencv默认取1.2)和金字塔的层数n(通常取8)。将原图像按比例因子缩小成n幅图像。缩放后的图像为:I’= I / scale^k (k=1,2,…, n)。n幅不同比例的图像提取特征点总和作为这幅图像的FAST特征点。

    当然可以通过采用不同的高斯核进行高斯模糊来建立金字塔,但是时间成本就很高了。

    3. 定义特征点方向

    ORB实现旋转不变性,也是通过确定一个特征点的方向来实现的,我们来看看ORB是怎么来确定特征点的方向的?

    ORB的论文中提出了一种利用灰度质心法来解决这个问题,通过计算一个矩来计算特征点以r为半径范围内的质心,特征点坐标到质心形成一个向量作为该特征点的方向。我们来看看具体怎么实现灰度质心法。

    一个图像块(比如5x5的图像块中),对应的2x2的矩的元素表达为:

    x,y分别为坐标值,I(x,y)为像素值

    而该图像窗口的质心就是:

    其实灰度的质心就是对所有的位置进行加权,权重就是像素值在整个图像中像素值之和的比例。

    那么特征点与质心的夹角定义为FAST特征点的方向:

    也就有了特征点的方向,继而实现旋转不变性。

    通过上述步骤我们找到了所有的特征点,并计算出了特征点的方向,下面就看一下怎么描述这些特征点。

    2. 关键点描述

    ORB选择了BRIEF作为特征描述方法,并对其进行改进使其加上旋转不变性并增加其可区分性,首先我们先看看BRIEF描述子。

    • BRIEF描述子

    BRIEF算法计算出来的是一个二进制串的特征描述符。它是在每一个特征点的邻域内,选择n对像素点pi、qi(i=1,2,…,n)。然后比较每个点对的灰度值的大小。如果I(pi)> I(qi),则生成二进制串中的1,否则为0。所有的点对都进行比较,则生成长度为n的二进制串。一般n取128、256或512,通常取256。

    为了增强抗噪性,一般会先对图像进行高斯平滑。ORB算子采用5x5的子窗口进行平滑。

    那么这n个点对如何选取呢?
    在点周围选取点对(p,q)的方法有以下5种:

    1. 在图像块内平均采样;
    2. p和q都符合(0,S2/25)的高斯分布;
    3. p符合(0,S2/25)的高斯分布,而q符合(0,S2/100)的高斯分布;
    4. 在空间量化极坐标下的离散位置随机采样;
    5. 把p固定为(0,0),q在周围平均采样。

    BRIEF作者采用的是第二种。而ORB作者没有选择以上任意方式,而是一种新的方式,我们在下面再说。

    BRIEF流程简单实时性较好,论文中生成512个描述子用时8.18ms,并且其描述子是二进制码,其匹配也比较快。但是,当BRIEF对于旋转过大时,比如超过30度时,匹配正确率迅速下降直到45度时为0。所以需要增加其描述子的旋转不变性。

    • BRIEF算法改进

    在描述基础BRIEF算法时,我们提出了两个问题,一个是增加其旋转不变性,一个是选点方式。

    • steered BRIEF 增加其旋转不变性
    所谓steered BRIEF 就是对挑选出的点对加上一个旋转角度θ。对于任何一个特征点来说,它的BRIEF描述子是一个长度为𝑛的二值码串,这个二值码串是由特征点邻域𝑛个点对生成的,我们现在讲这2𝑛个点(𝑥𝑖,𝑦𝑖),𝑖=1,2,.....,2𝑛组成一个矩阵𝑆: 使用邻域方向𝜃和对应的旋转矩阵𝑅𝜃,构建𝑆的一个校正版本𝑆𝜃: 其中 即我们把坐标轴旋转]𝑡ℎ𝑒𝑡𝑎,计算以主方向为坐标系的匹配点对,如下图:

    steered BRIEF加入了旋转不变性,但同时特征描述量的可区分行就下降了,所以就有了ORB作者提出的rBRIEF。

    • rBRIEF 增加其可区分性

    我们在上边说过ORB没有使用BRIEF 5种选取点对方法中的任意一种,那么ORB用的是什么方式?
    ORB使用统计学习的方法来重新选择点对集合,目的是增大其特征描述量的可区分行。这里我们先不解释其具体的实验流程(主要是我还没有完全理解),但是通过其实验,选出来的点对对特征点描述的区分度变高了。

    至此ORB算法计算完毕。

    OpenCV ORB特征效果展示[代码]

    #include <opencv2/opencv.hpp>
    #include <iostream>
    #include <opencv2/xfeatures2d.hpp>
    #include <opencv2/features2d/features2d.hpp>
    void extracte_orb(cv::Mat input,std::vector<cv::KeyPoint> &keypoint,cv::Mat &descriptor){
        cv::Ptr<cv::ORB> f2d = cv::ORB::create(500);
        f2d->detect(input,keypoint);
        cv::Mat image_with_kp;
        f2d->compute(input,keypoint,descriptor);
        cv::drawKeypoints(input, keypoint, image_with_kp, cv::Scalar::all(-1),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
        cv::imwrite("orb"+std::to_string(random())+".png",image_with_kp);
    }
    
    void match_two_image(cv::Mat image1,cv::Mat image2, std::vector<cv::KeyPoint> keypoint1,std::vector<cv::KeyPoint> keypoint2,cv::Mat descriptor1,cv::Mat descriptor2){
        cv::BFMatcher matcher(cv::NORM_HAMMING);
        std::vector<cv::DMatch> matches;
        matcher.match(descriptor1,descriptor2, matches);
        cv::Mat good_matches_image;
        cv::drawMatches(image1, keypoint1, image2, keypoint2,
                        matches, good_matches_image, cv::Scalar::all(-1), cv::Scalar::all(-1),
                        std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
        cv::imwrite("good_matches_image.png",good_matches_image);
        {
            std::vector <cv::KeyPoint> RAN_KP1, RAN_KP2;
            std::vector<cv::Point2f> keypoints1, keypoints2;
            for (int i = 0; i < matches.size(); i++) {
                keypoints1.push_back(keypoint1[matches[i].queryIdx].pt);
                keypoints2.push_back(keypoint2[matches[i].trainIdx].pt);
                RAN_KP1.push_back(keypoint1[matches[i].queryIdx]);
                RAN_KP2.push_back(keypoint2[matches[i].trainIdx]);
            }
    
            std::vector<uchar> RansacStatus;
            cv::findFundamentalMat(keypoints1, keypoints2, RansacStatus, cv::FM_RANSAC);
            std::vector <cv::KeyPoint> ransac_keypoints1, ransac_keypoints2;
            std::vector <cv::DMatch> ransac_matches;
            int index = 0;
            for (size_t i = 0; i < matches.size(); i++)
            {
                if (RansacStatus[i] != 0)
                {
                    ransac_keypoints1.push_back(RAN_KP1[i]);
                    ransac_keypoints2.push_back(RAN_KP2[i]);
                    matches[i].queryIdx = index;
                    matches[i].trainIdx = index;
                    ransac_matches.push_back(matches[i]);
                    index++;
                }
            }
            cv::Mat after_ransac_sift_match;
            cv::drawMatches(image1, ransac_keypoints1, image2, ransac_keypoints2,
                            ransac_matches, after_ransac_sift_match, cv::Scalar::all(-1), cv::Scalar::all(-1),
                            std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
            cv::imwrite("after_ransac_orb_match.png",after_ransac_sift_match);
        }
    }
    
    int main(int argc, char *argv[])
    {
        cv::Mat image1 = cv::imread(argv[1]);
        cv::Mat image2 = cv::imread(argv[2]);
        std::vector<cv::KeyPoint> keypoint1,keypoint2;
        cv::Mat descriptor1, descriptor2;
        extracte_orb(image1,keypoint1,descriptor1);
        extracte_orb(image2,keypoint2,descriptor2);
        match_two_image(image1,image2,keypoint1,keypoint2,descriptor1,descriptor2);
        return 0;
    }
    

    因为orb难以应对我们类似SIFT或surf那么大的仿射变换,所以不得不换了仿射变换小的照片

    原图1 orb识别效果 原图2 orb识别效果
    初步匹配效果 ransac后匹配效果

    总结

    总体来说ORB在上述实验实例中的效果还是很好的。我们已经讲完SIFT,SURF还有ORB,我们来对比一下这三个算法。
    计算速度: ORB>>SURF>>SIFT(各差一个量级)
    旋转鲁棒性: SURF>ORB~SIFT(~表示差不多)
    模糊鲁棒性: SURF>ORB~SIFT
    尺度变换鲁棒性: SURF>SIFT>ORB(ORB尺度变换性很弱)
    在日常应用中,有SURF基本就不用考虑SIFT,SURF基本就是SIFT的全面升级版,当然也有其他SIFT的改进版比如Affine SIFT的效果就要比SUFR要好更多,但是计算时间也有延长,而ORB的强点在于计算时间。ORB主要还是在VSLAM中应用较多,场景变化不明显,但是需要高速的计算时间,这正好符合ORB。

    特征点到此结束,继续加油

    重要的事情说三遍:

    如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )

    如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )

    如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )

    传统2D计算机视觉学习笔记目录------->传送门
    传统3D计算机视觉学习笔记目录------->传送门

    任何人或团体、机构全部转载或者部分转载、摘录,请保留本博客链接或标注来源。博客地址:开飞机的乔巴

    作者简介:开飞机的乔巴(WeChat:zhangzheng-thu),现主要从事机器人抓取视觉系统以及三维重建等3D视觉相关方面,另外对slam以及深度学习技术也颇感兴趣,欢迎加我微信或留言交流相关工作。

    相关文章

      网友评论

        本文标题:ORB 算法学习笔记

        本文链接:https://www.haomeiwen.com/subject/jlqisctx.html