ORB特征提取、匹配及实现

Jan 29,2018   4302 words   16 min

Tags: SLAM

之前在看SLAM的时候便看到了ORB特征,用它可以快速的提取图像中的特征点并匹配两幅图像。这里就再简单记录一下其原理和C++的实现代码。

一、ORB特征

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

  • 提取FAST角点(相较于原版FAST角点,ORB中计算了特征点的主方向,为后续的BRIEF描述子增加了旋转不变性)
  • 计算BRIEF描述子

1.FAST角点

名字既然叫“FAST”,那么它的特点也就很明显了,就是快。而它之所以快,原因在于它只比较像素亮度的大小,只进行比较操作,所以很快。它的核心思想是如果一个像素与领域的像素差别较大(过亮或过暗),那么它更可能是角点。 如上图所示。检测过程如下。

  • 在图像中选取像素p,假设它的亮度为Ip。
  • 设置一个阈值T,如Ip的20%。
  • 以p为中心,选取半径为3的圆上的16个像素点。
  • 若圆上有连续的N个点的亮度大于Ip+T或小于Ip-T,那么像素p被认为是特征点。N通常取12,叫做FAST-12。也可以是9或11。
  • 循环上述步骤,对每个像素执行相同操作。

为了使FAST-12算法更高效,可以增加一项预测试操作,以快速地排除绝大多数不是角点的像素。即对每个像素,先判断领域圆上的1,5,9,13像素(也即四个边界点)的亮度。如果这四个像素中有3个同时大于Ip+T或小于Ip-T,当前像素才有可能是个角点,否则直接剔除。

此外,FAST角点经常出现扎堆现象。所以在第一遍检测之后,还需要用非极大值抑制(Non-maximal suppression),在一定区域内仅保留响应极大值的角点。非极大值抑制思想类似于卷积网络中的最大池化操作。选取一定范围内的最大值作为“代表”。

同时,FAST角点不具有方向信息。而且它的领域圆半径固定为3,这样就不具有尺度不变性。针对这两点,在ORB中增加了尺度和旋转的描述。首先对于尺度不变性,通过构建图像金字塔并在金字塔的每一层上检测角点来实现。而特征的旋转由灰度质心法实现。

所谓灰度质心是指以图像块灰度值作为权重的中心。计算如下。

首先在一小块图像B中,定义图像块的矩为:

\[m_{pq} = \sum_{x,y\in B}^{ } x^{p}y^{q}I(x,y)\]

其中 \(p,q = \begin{Bmatrix} 0,1 \end{Bmatrix}\)

写成更通俗易懂的形式如下,也就是说m共有4中情况,这里写出m00,m01,m10三种情况如下。

\[\left\{\begin{matrix} m_{00}=\sum_{x,y\in B}^{ } I(x,y)\\ m_{01}=\sum_{x,y\in B}^{ } yI(x,y)\\ m_{10}=\sum_{x,y\in B}^{ } xI(x,y) \end{matrix}\right.\]

然后,通过矩可以找到图像块B的质心。

\[C = (\frac{m_{10}}{m_{00}},\frac{m_{01}}{m_{00}})\]

最后,连接图像块的几何中心O和质心C,即可得到一个方向向量OC,因此特征点的方向可以定义为

\[\theta =arctan(m_{01}/m_{10})\]

通过以上方法FAST角点便具有了尺度与旋转的描述,在ORB中,把这种改进后的FAST叫做Oriented FAST。

2.BRIEF描述子

BRIEF是一种二进制描述子,用来描述两个二进制串之间的距离,其描述向量由很多0、1组成。这里的0、1表示关键点附近两个像素(如p、q)的大小关系,若p比q大,取1,反之取0。如果选取128对点比较,最后即可得到128维由0、1组成的特征向量。

二、ORB特征匹配实现

在OpenCV中,已经集成好了ORB的相关函数,使用起来十分方便,主要代码如下。

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <string>

using namespace std;
using namespace cv;

double DIST_THRESHOLD = 30;
int NUM_MATCH_POINTS = 500;

//ORB特征提取
//1.读取图像
//2.初始化KeyPoint、Descriptor、ORB对象
//3.检测FAST角点
//4.由角点位置计算BRIEF描述子
//5.新建匹配对象及vector用于存放点对,对两幅图中的BRIEF描述子进行匹配,使用Hamming距离
//6.筛选匹配点对(距离小于最小值的两倍)
//7.绘制匹配结果
vector<vector<Point2f>> orb_match(string path1,string path2,string outPath)
{
  //Step1
  Mat img1 = imread(path1);
  Mat img2 = imread(path2);

  //Step2
  vector<KeyPoint> keyPoint1,keyPoint2;
  Mat descriptor1,descriptor2;
  //!!!新建一个ORB对象,注意create的参数!!!
  Ptr<ORB> orb = ORB::create(NUM_MATCH_POINTS,1.2f,8,31,0,2,ORB::HARRIS_SCORE,31,20);
  
  //Step3
  orb->detect(img1,keyPoint1);
  orb->detect(img2,keyPoint2);
  
  //Step4
  orb->compute(img1,keyPoint1,descriptor1);
  orb->compute(img2,keyPoint2,descriptor2);
  
  //Step5
  //!!!注意表示匹配点对用DMatch类型,以及匹配对象的新建方法!!!
  vector<DMatch> matches;
  BFMatcher matcher = BFMatcher(NORM_HAMMING);
  matcher.match(descriptor1,descriptor2,matches);
  
  //Step6
  double min_dist = 100;
  for(int i=0;i<matches.size();i++)
  {
    if(matches[i].distance<min_dist)
    {
      min_dist = matches[i].distance;
    }
  }
  
  vector<Point2f> points1;
  vector<Point2f> points2;
  vector<DMatch> good_matches;
  for(int i=0;i<matches.size();i++)
  {
    if(matches[i].distance<max(2*min_dist,DIST_THRESHOLD))
    {
      good_matches.push_back(matches[i]);
      //注意这两个Idx是不一样的
      points1.push_back(keyPoint1[matches[i].queryIdx].pt);
      points2.push_back(keyPoint2[matches[i].trainIdx].pt);
    }
  }
  vector<vector<Point2f>> result;
  result.push_back(points1);
  result.push_back(points2);
  
  //Step7
  Mat outImg;
  drawMatches(img1,keyPoint1,img2,keyPoint2,good_matches,outImg);
  imwrite(outPath,outImg);
  cout<<"保留的匹配点对:"<<good_matches.size()<<endl;
  
  return result;
}

int main()
{
  string img1 = "/root/satellite_slam_data/tianjin/jpg/VBZ1_201801051905_002_0026_L1A.jpg";
  string img2 = "/root/satellite_slam_data/tianjin/jpg/VBZ1_201801051905_002_0027_L1A.jpg";
  string outpath = "/root/out.jpg";
  vector<vector<Point2f>> points;
  vector<Point2f> points1;
  vector<Point2f> points2;
  points = orb_match(img1,img2,outpath);
  points1 = points[0];
  points2 = points[1];
  
  for(int i=0;i<points1.size();i++)
  {
    cout<<"第"<<(i+1)<<"对点:";
    cout<<"("<<points1[i].x<<","<<points1[i].y<<")";
    cout<<" - ";
    cout<<"("<<points2[i].x<<","<<points2[i].y<<")";
    cout<<endl;
  }
  
  return 1;
}

运行后,结果如下图所示,一共筛选出了159对匹配点。 绘制出的匹配图像如下所示。 以上便是使用ORB特征进行图像匹配的代码流程,使用其它特征如SIFT、SURF等算子的流程也差不多。

本文作者原创,未经许可不得转载,谢谢配合

返回顶部