vSLAMNet(三)-特征点-ORB特征

1. 概述

  • ORB特征由关键点和描述子两部分组成。它的关键点称为“Oriented Fast”,是一种改进的FAST角点。描述子称为“BRIEF(Binary Robust Independent Elementary Feature)”
  • 提取FAST角点(相较于原版FAST角点,ORB中计算了特征点的主方向,为后续的BRIEF描述子增加了旋转不变性)
  • 计算BRIEF描述子

2. 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),在一定区域内仅保留响应极大值的角点。非极大值抑制思想类似于卷积网络中的最大池化操作。选取一定范围内的最大值作为“代表”

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

$m_{pq} = \sum_{x, y\in B}x^py^qI(x, y)$,

其中$p, q = \{0, 1\}$。

由此得到:

$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)$。

图像块B的质心为:

$C = (\frac{m_{10}}{m_{00}}, \frac{m_{01}}{m_{00}})$。

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

$\theta = \arctan(\frac{m_{01}}{m_{10}})$.

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

3. BRIEF描述子

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

现在我们有2n个点,组成一个矩阵S:

$S = (x_1 x_2 \cdots x_{2n})$,

使用邻域方向$\theta$和对应的旋转矩阵$R_{\theta}$,构建S的一个校正版本$S_{\theta}$:

$S_{\theta} = R_{\theta}S$,

其中,$R_{\theta} = \left[\begin{matrix} \cos\theta & \sin\theta \\ -\sin\theta & \cos\theta \end{matrix}\right]$,$\theta$为特征点求得的主方向。

4. 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#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 = "1.jpg";
string img2 = "2.jpg";
string outpath = "3.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;
}

(以上代码参考:ORB特征提取、匹配及实现))