《视觉SLAM十四讲精品总结》5:VO——ORB特征提取和匹配

一、简介

视觉里程计法:提取、匹配图像特征点,然后估计两帧之间的相机运动,给后端提供较好的初始值。

角点的局限:例如:从远处上看上去是角点的地方,当相机走近后,可能就是角点了。或者,当旋转相机的时候,角点的外观会发生变换。
进而,我们提出了SIFT,SURF,ORB特征。

特征点由提取关键点和计算描述子两部分组成。

关键点:该特征点在图像的位置,具有朝向、大小的信息。

描述子:描述关键点周围像素信息的向量。

SIFT(尺度不变特征变换—Scale-Invariant Feature Transform):充分考虑图像变换过程中光照、尺度、旋转等变换,但计算量大。

FAST:只有关键点没有描述子,降低精度和健壮性,提升计算速度。

ORB:(Oriented FAST and Rotated BRIEF):改进了FAST 不具有方向性的问题,添加了尺度和旋转的特性。采用速度极快的二进制描述子BRIEF,
 

二、ORB特征(Oriented FAST and Rotated BRIEF)

两步骤:FAST角点提取+BRIEF描述子
1、FAST角点:

主要检测局部像素灰度变化明显的地方。思想:如果一个像素与邻域像素差别较大(过亮或者过暗),只需比较像素亮度大小。

1、在图像中选取像素p,假设他的亮度是IP
2、设置一个阈值T,例如T=IP*20%
3、以像素p为中心,选取半径为3的圆上的16个像素点, 这里的3应该是三个像素框
4、如果在选取的圆上面,有连续N个点的亮度值大于IP+T(1.2*IP))或者小于IP-T(0.8*IP),那么像素p通常可以被认为是特征点(N通常取12,FAST-12,通常n取9和11,的时候,称为FAST-9和FAST-11)

FAST-12算法:

添加预测试操作,于每个像素,直接检测在邻域圆上的第1,5,9,13个像素的亮度,只有当这四个像素当中有三个同时大于IP+T或者小于IP-T的时候,当前像素才有可能是是角点。

问题1:FAST特征点的数量很多,并且不是确定,而大多数情况下,我们希望能够固定特征点的数量。

解决方法:在ORB当中,我们可以指定要提取的特征点数量。对原始的FAST角点分别计算Harris的响应值,然后选取前N个点具有最大相应值的角点,作为最终角点的集合。

问题2:FAST角点不具有方向信息和尺度问题。

解决方法:尺度不变性构建的图像的金字塔,并且从每一层上面来检测角点旋转性是由灰度质心法实现

灰度质心法:质心是指以图像块灰度值作为权重的中心。(目标是为找找到方向)

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

2、通过矩找到图像块的质心 

3、连接图像块的几何中心o与质心C,得到一个oc的向量,把这个向量的方向定义特征点的方向 

2、BRIEF描述子

BRIEF是二进制描述子,它的描述向量是由许多个0和1组成,这里的0和1编码了关键点附近的两个像素p和q的大小关系,如果p>q,则取1 ;否则取0;p q 的挑选为随机选点。

 
三、特征匹配

通过对图像与地图之间的描述子进行准确匹配,为后续的姿态估计优化等操作减轻负担。

暴力匹配:对两帧图像中每一个特征点x(t)与所有的特征点x(t+1)测量描述子的距离,然后排序,取最近的一个作为匹配点。

描述子距离表明了两个特征之间的相似距离。

二进制描述子,使用汉明距离,指的是不同位数的个数。

快速近似最邻近算法(FLANN):适合匹配点数量多情况。

 

1、OpenCV中封装了常用的特征点算法(如SIFT,SURF,ORB等),提供了统一的接口,便于调用。

    #include
    #include
    using namespace std;
    using namespace cv;
     
    int main()
    {
        //0读取图像
        Mat img1 = imread("1.png");
        Mat img2 = imread("2.png");
        // 1 初始化特征点和描述子,ORB
        std::vector keypoints1, keypoints2;
        Mat descriptors1, descriptors2;
        Ptr orb = ORB::create();
        // 2 提取 Oriented FAST 特征点
        orb->detect(img1, keypoints1);
        orb->detect(img1, keypoints1);
        // 3 根据角点位置计算 BRIEF 描述子
        orb->compute(img1, keypoints1, descriptors1);
        orb->compute(img2, keypoints2, descriptors2);
        // 4 对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
        vector matches;
        BFMatcher bfmatcher(NORM_HAMMING);
        bfmatcher.match(descriptors1, descriptors2, matches);
        // 5 绘制匹配结果
        Mat img_match;
        drawMatches(img1, keypoints1, img2, keypoints2, matches, img_match);
        imshow("所有匹配点对", img_match);
        waitKey(0);
        return 0;
    }

得到了描述子后,可调用匹配算法进行特征点的匹配。上面代码中,使用了opencv中封装后的暴力匹配算法BFMatcher,该算法在向量空间中,将特征点的描述子一一比较,选择距离(上面代码中使用的是Hamming距离)较小的一对作为匹配点。

KeyPoint类型(比Point多一些数据)

    class KeyPoint
    {
    Point2f  pt;  //特征点坐标
    float  size; //特征点邻域直径
    float  angle; //特征点的方向,值为0~360,负值表示不使用
    float  response; //特征点的响应强度,代表了该点是特征点的程度,可以用于后续处理中特征点排序
    int  octave; //特征点所在的图像金字塔的组
    int  class_id; //用于聚类的id
    }

DMatch类存放匹配结果

    struct DMatch
    {       
              int queryIdx;  //此匹配对应的查询图像的特征描述子索引
              int trainIdx;   //此匹配对应的训练(模板)图像的特征描述子索引
              int imgIdx;    //训练图像的索引(若有多个)
              float distance;  //两个特征向量之间的欧氏距离,越小表明匹配度越高。
              bool operator < (const DMatch &m) const;
    };

匹配函数match (matches中保存着描述子之间的匹配关系)

        vector matches;
        BFMatcher bfmatcher(NORM_HAMMING);
        bfmatcher.match(descriptors1, descriptors2, matches);

画有特征点的图像drawKeypoints

drawKeypoints(img1,keypoints1,outimg1,Scalar::all(-1),DrawMatchesFlags::DEFAULT);

画匹配图像drawMatches

drawMatches(img1, keypoints1, img2, keypoints2, matches, img_match);

 img_match为目标图像。
 

2、筛选:汉明距离小于最小距离的两倍
选择已经匹配的点对的汉明距离小于最小距离的两倍作为判断依据,如果小于该值则认为是一个错误的匹配,过滤掉;大于该值则认为是一个正确的匹配。

        // 匹配对筛选
        double min_dist = 1000, max_dist = 0;
        // 找出所有匹配之间的最大值和最小值
        for (int i = 0; i < descriptors1.rows; i++)
        {
            double dist = matches[i].distance;//汉明距离在matches中
            if (dist < min_dist) min_dist = dist;
            if (dist > max_dist) max_dist = dist;
        }
        // 当描述子之间的匹配大于2倍的最小距离时,即认为该匹配是一个错误的匹配。
        // 但有时描述子之间的最小距离非常小,可以设置一个经验值作为下限

        vector good_matches;
        for (int i = 0; i < descriptors1.rows; i++)
        {
            if (matches[i].distance <= max(2 * min_dist, 30.0))
                good_matches.push_back(matches[i]);
        }
 

你可能感兴趣的:(视觉SLAM)