之前在看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等算子的流程也差不多。
本文作者原创,未经许可不得转载,谢谢配合