【CV】ORB源码详解-描述子的建立
项目中最近需要在嵌入式上实现orb,由于板子的运算器对之没有优化,所以不能用opencv的代码。为了实现这个需求笔者查阅相关资料,阅读opencv3.2中相关的源码(很幸运能看懂),在此进行总结,以备使用。
首先ORB是一种提取图像特征并通过一定方法计算描述子,以用来进行匹配的方法。注意orb只解决了提取特征并计算描述子的问题。而后续的匹配是有其他方法的,不属于orb的范畴,匹配既可以用orb也可以用sift,surf等其他方法。
ORB这个名字是oFast与rBreaf的缩写。其中oFAST指FAST Keypoint Orientation,rBreaf指Rotation-Aware Brief。如名字,ORB其实是分两个部分的:提特征点,算描述子。而这两个部分在我看来是可以分开的。
提特征点其实就告诉算描述子过程在哪里去算,并为算描述子提供了关键信息——特征点的方向。论文原文中用的是oFast去提取特征点。显然可以用其他方法代替(比如板子上带的硬件加速的Shi-Tomasi-like特征提取API),至于特征点的方向,还是用oFast的灰度质心法。这和特征提取也是分开的。
算描述子应该算是论文中提出的精髓了。也有很多相关的文章去介绍。比如这篇就讲解的很详细。
那么,我的文章就到头了吗?显然不是,文章题名为源码详解。所以本文就OpenCV对ORB的具体实现(只讨论算描述子部分),从源码出发以期对这个算法彻头彻尾的理解透。那就开始正文吧。
OpenCV中ORB的代码调用框架
在开始理描述子计算过程之前,有必要提一提ORB到底是怎么用的,要不然到最后分析了一堆不知道分析的是哪里。
从调用说起
从很多博客上可以看到提取ORB特征进行调用的文章。对于OpenCV 的C++接口来说,其调用套路也就是几步,首先创建一个句柄,这个句柄能够执行特征提取的两大步:提取、算描述子。典型代码片段如下:
Ptr<Feature2D> featurehdl = ORB::create();
当然create()里面可以加入很多参数这里不展开。之后呢就是通过这个句柄(其实也就是个智能指针)去提取特征点,算描述子。
vector<KeyPoint> keypoints;
Mat descriptors;
Mat image= imread("../image/xxxxxx.jpg");
featurehdl->detect(image,keypoints);
featurehdl->compute(image,keypoints,descriptors);
然后就得到了一组描述符descriptors
。在原图上和测试图上分别这么做就得到了一对匹配点。接着调用match相关的接口去匹配就能匹配成功,不展开。本文就仔细分析create
、detect
和compute
这几个调用到底干了些什么。
create
talk is cheap。
Ptr<ORB> ORB::create(int nfeatures, float scaleFactor, int nlevels, int edgeThreshold,
int firstLevel, int wta_k, int scoreType, int patchSize, int fastThreshold)
{
return makePtr<ORB_Impl>(nfeatures, scaleFactor, nlevels, edgeThreshold,
firstLevel, wta_k, scoreType, patchSize, fastThreshold);
}
其实create
的时候,代码new了一个ORB_Impl
类的指针(为何没直接new而是makePtr,其实就是一个模板函数,可以方便地new各种类的指针吧)。各个类的继承关系是这样的:ORB_Impl->ORB->Feature2D
,很明了,想扩展其他描述子只需继承Feature2D
就行。跟进去可以看到构造函数是什么事情都不干的,只是用参数初始化了一些成员变量而已。
detect
void Feature2D::detect( InputArray image,
std::vector<KeyPoint>& keypoints,
InputArray mask )
{
CV_INSTRUMENT_REGION()
if( image.empty() )
{
keypoints.clear();
return;
}
detectAndCompute(image, mask, keypoints, noArray(), false);
}
detect
是一个虚函数,但在ORB
与ORB_Impl
里都没有相关定义,所以走的就是基类的方法,显然其中调用了detectAndCompute
函数。从名字上看这个函数实现了detect
和compute
,想必是从参数上去控制这个函数干什么事情。
compute
既然这两个方法调一个函数那就把这两个调用先写上来最后再分析那个大函数吧。
void Feature2D::compute( InputArray image,
std::vector<KeyPoint>& keypoints,
OutputArray descriptors )
{
CV_INSTRUMENT_REGION()
if( image.empty() )
{
descriptors.release();
return;
}
detectAndCompute(image, noArray(), keypoints, descriptors, true);
}
void ORB_Impl::detectAndCompute( InputArray _image, InputArray _mask,
std::vector<KeyPoint>& keypoints,
OutputArray _descriptors, bool useProvidedKeypoint