ORB特征提取、匹配
ORB(Oriented FAST and Rotated BRIEF)是一种局部不变特征,从名字可以看出ORB具有FAST和旋转不变的特性。相比于经典的SIFT、SURF,ORB具备了实时使用能力。我的三维重构任务对于实时性具有较高的要求,且三维重构的最为重要的任务就是如何找到对应的特征点,所以ORB如何应用值得好好研究一下。
ORB的介绍看这里,对于ORB的介绍我不再进行过多的说明,本博客主要看看OpenCV中与ORB相关的库函数是如何应用的。
ORB特征的提取
已知对于ORB特征的描述主要包括两个部分:特征点(keypoint),描述(descriptor)。一般定义为:
vector<KeyPoint> key_points;
Mat descriptor;
而目前我见到的创建ORB的特征对象的方式有三种,如:
Ptr<ORB> orb = ORB::create();
或者:
Ptr<FeatureDetector> orb1 = ORB::create();
Ptr<DescriptorExtractor> orb2 = ORB::create();
上述三种声明的意义是一样的,无论是FeatureDetector
还是DescriptorExtractor
都是类Feature2D
的别名。而类ORB
是类Feature2D
的公有继承。具体OpenCV中的定义如下:
typedef Feature2D FeatureDetector;
typedef Feature2D DescriptorExtractor;
class CV_EXPORTS_W ORB : public Feature2D{}
所以无论哪种声明本质上都是一样的,可能ORB类中存在别的函数实现。类的别名主要是为了开发方便。
声明了Feature2D
的对象之后,就可以直接调用detect
和compute
或者detectAndCompute
进行计算“重要的” keypoint 和descriptor了。
计算方式有两种:
//分开计算
orb->detect(img, key_points);
orb->compute(img, key_points, descriptor);
//一起计算
orb->detectAndCompute(img, noArray(),key_points, descriptor);
至此ORB特征提取完毕。
ORB特征匹配
匹配器matcher
声明与ORB特征对象声明一样也存在多种声明方式如:
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");
或者:
BFMatcher matcher(NORM_L2);
在OpenCV中我们发现BFMatcher
是DescriptorMatcher
的公有继承:
class CV_EXPORTS_W BFMatcher : public DescriptorMatcher{}
这样上述不同的matcher
声明就容易理解了。
匹配器matcher
是用于计算特征点的匹配情况的,而计算的结果通常保存在vector<DMatch> matches
中。
匹配函数的调用如下:
matcher->match(descriptor1, descriptor2, matches);
其中descriptor1, descriptor2
分别代表两张不同的图片的特征点(descriptor用于描述特征点),matches
中保存匹配信息。
此时matches
中保存的匹配点中存在匹配不准确的情况,为了提高匹配的精度,通常需要对这些匹配点进行筛选。筛选的方式多种多样,但基本思想都是保留distance满足一定条件的点对,例如:
找出匹配点之间的最大距离和最小距离,也就是匹配最相似和最不相似的点对。选择一个点对的距离阈值对点对进行筛选。
double min_dist = 10000, max_dist = 0;
//查找距离最小和距离最大的点
for (int i = 0; i < query.rows; i++)
{
double dist = matches[i].distance;
if (dist < min_dist) min_dist = dist;
if (dist > max_dist) max_dist = dist;
}
std::vector< DMatch > good_matches;
//对匹配的点对进行筛选
for (int i = 0; i < query.rows; i++)
{
if (matches[i].distance <= max(2 * min_dist, 30.0))
{
good_matches.push_back(matches[i]);
}
}
good_matches.swap(matches);
或者:
对在knnMatch
中得到的knn_matches
的筛选时,除了distance不能过大,还需要考虑Ratio test(KNN为特征点保留了两个待选匹配点,第一匹配点与第二匹配点的比,越接近1,匹配点越模糊,则被排除)。
vector<vector<DMatch>> knn_matches;
BFMatcher matcher(NORM_L2);
matcher.knnMatch(query, train, knn_matches, 2);
//获取满足Ratio Test的最小匹配的距离
float min_dist = FLT_MAX;
for (int r = 0; r < knn_matches.size(); ++r)
{
//Ratio Test
if (knn_matches[r][0].distance > 0.6*knn_matches[r][1].distance)
continue;
float dist = knn_matches[r][0].distance;
if (dist < min_dist) min_dist = dist;
}
matches.clear();
for (size_t r = 0; r < knn_matches.size(); ++r)
{
//排除不满足Ratio Test的点和匹配距离过大的点
if (
knn_matches[r][0].distance > 0.6*knn_matches[r][1].distance ||
knn_matches[r][0].distance > 5 * max(min_dist, 10.0f)
)
continue;
//保存匹配点
matches.push_back(knn_matches[r][0]);
}
对匹配点进行筛选之后,就解决了三维重构中最为棘手的问题。
tips:
在匹配ORB特征时,我发现网上一般使用的筛选方式都是基于距离的。从我的仿真实验中发现,由于ORB检测到的匹配点较少,使用距离筛选ORB匹配点较为合理,因为使用KNN的方式排除的特征点对较多,导致我的匹配结果无法正常使用SFM三维重构。