单目视觉(5):SFM之特征点匹配(四)

SFM之特征点匹配(四)


引入

在经过对每幅图像进行特征提取之后,可以发现在一幅图像中存在非常多的特征点(特殊情况下可能特征点很少)。那么如何去找出不同图像中的哪些特征点反应在现实世界中是同一个物理坐标呢?这需要做的工作就是对两幅图像中的特征点进行匹配。

相似性

如何判别分别在两幅图像中的特征点是同一个呢? 也就是说需要着一种方法或者标准来衡量两个特征点之间的相似程度。我们称这种标准或方法为相似性度量。这个是可以通过自己定义的方法,并没有一个统一的标准。因此,不同的度量标准对结果的精确性也有一定的影响。常用的相似性度量有各种距离,角度等。

可以参看:常用的相似性度量。


匹配

匹配过程解决的是:在参考图像中的一个特征点,如何从目标图像中的所有特征点中找出与之相对应的特征点。假设以常见的欧氏距离作为相似性度量标准。我们使用提取的特征点(用特征向量来描述),那么计算欧式距离就是两个点的距离。其”暴力“计算过程中,对于任意的两个点,每一维都进行运算。那么当维度很低,特征点较少时,暴力计算也是可行的。但是,一旦维度很高,加之由于复杂场景导致的更多的特征点数量,“暴力”计算是一个非常不明智的方法。因此,需要选在一种能有效减少计算复杂度和计算耗时的算法。快速近邻匹配(Approximate Nearest Neighbors Match)的有一系列的办法可以实现。其中很有效的方法是随机K-d树和优先搜索K均值树。在OpenCV中的FLANN(fast library for approximate nearest neighbors)集成了包括这两种算法在内的多种算法。

K-d树(K-dimensional Tree)

a. 经典KD树
经典的kd树是将数据集排列成一个类似二叉树,每个节点表示超平面的一个区域范围。在理想的情况下,二叉查找树的父节点与节点是靠近比较近的,而与其他节点的距离离得比较远。那么,在大量的数据中查找某个特殊的值,只需要依靠二叉查找树就能很好的解决搜索问题。因此,如何将所有的数据构造成一个较理想的类似于二叉查找树的结构?

一维的情况下,可以直接构造一个二叉查找树。在多维的情况下,我们不能直接的进行比较了。但是我们可以选择依据其中的某一个维度先进行划分,然后一次按照各个维度划分下去,那么就可以构造一个类似于二叉树的结构。只不过此时构造的结构是在一个超平面(二维的话就是一个普通平面)中。

那么,如何选择先哪个维度再哪个维度呢?也就是维度的先后顺序问题。各个维度的数据值我们可以看成一个数据子集,如果数据分布的比较“散”,那么各个数据的区分度就很明显,那么也更容易将这个子集进行划分开来。回顾在统计理论中的知识,描述数据分布集中程度的概念是方差。方差越大,数据分布越“发散”;方差越小,数据分布越“集中”。因此,可以参考这个方案,使用各个维度的方差来作为维度顺序的选择依据。因此,这个方法也称为:最大方差法(max variance)。

接下来,选定好了维度的顺序,该如何确定某个确定的值来作为“根(父)结点”呢?如果都选择最大值,那么所有数据都会在“根节点”的同一侧。那么也就这样也会使得算法的性能下降。回想在平衡二叉树,其左子树和右子树的节点数目差不多,其性能较前一种二叉树要好。因此,是否可以通过某种方法来确定一个值,是的最后划分的结果中,两侧的数据量是差不多的,尽量避免出现“一边偏”的情况。回顾统计理论中的知识,中值可以作为一个大概的分界线。因此,可以采用各个维度的中值来作为划分的另一个依据。

以上,就可以归纳出经典K-d树的基本算法:
(1) 在数据集合中选择具有最大方差的维度,然后在该维度上选择中值对该数据集合进行划分,得到两个子集合;同时创建一个”父结点“,用于存储相关值;

(2)对两个子集合重复(1)步骤的过程,直至所有子集合都不能再划分为止;如果某个子集合不能再划分时,则将该子集合中的数据保存到”叶结点“。

b. 随机K-d森林
建立多棵随机k-d树,从具有最高方差的N_d维中随机选取若干维度,用来做划分。在对随机k-d森林进行搜索时候,所有的随机k-d树将共享一个优先队列。 增加树的数量能加快搜索速度,但由于内存负载的问题,树的数量只能控制在一定范围内,比如20,如果超过一定范围,那么搜索速度不会增加甚至会减慢。

c.基于K-d树最邻近查找算法
(1)将查询数据从根结点开始,与各个结点的比较结果向下访问Kd-Tree,直至达到叶子结点。

其中该数据与结点的比较指的是将对应于结点中的k维度上的值与中值进行比较,若该数据小于中值,则访问左子树,否则访问右子树。达到叶子结点时,计算该数据与叶子结点上保存的数据之间的距离,记录下最小距离对应的数据点,记为当前“最近邻点”和最小距离。

(2)进行回溯操作,该操作是为了找到离该数据更近的“最近邻点”。即判断未被访问过的分支里是否还有离该数据更近的点,它们之间的距离小于。

如果该数据与其父结点下的未被访问过的分支之间的距离小于最小距离,则认为该分支中存在更近的数据,进入该结点,进行(1)步骤一样的查找过程,如果找到更近的数据点,则更新为当前的“最近邻点”,并更新最小距离。

如果该数据与其父结点下的未被访问过的分支之间的距离大于最小距离,则说明该分支内不存在与该数据更近的点。

误匹配

错误匹配的出现会印象算法的性能。因此在SFM中,常使用多种方式或约束来剔除误匹配。常见的方法有RANSAC(随机抽样一致)。详见:随机抽样一致性(RANSAC: Random Sample Consensus)


FLANN

FLANN(Fast Library for Approximate Nearest Neighbors)是一个执行快速近似最近邻搜索的库。FLANN使用C++写成。他能够很容易地通过C,MTALAB和Python等绑定提供的库,用在很多环境中。

利用FLANN进行特征点匹配

官方示例程序,使用 FlannBasedMatcher 接口以及函数 FLANN 实现快速高效匹配。
这段代码的主要流程分为以下几部分:

1)使用SURF特征提取关键点
2)计算SURF特征描述子
3)使用FLANN匹配器进行描述子向量匹配

OpenCV提供了 两种KeyPoint Matching的方式 :

 Brute-force matcher (cv::BFMatcher) //Brute-force matcher就是用暴力方法找到点集一中每个descriptor在点集二中距离最近的 descriptor;
 Flann-based matcher (cv::FlannBasedMatcher) //Flann-based matcher 使用快速近似最近邻搜索算法寻找。

为了提高检测速度,你可以调用matching函数前,先训练一个matcher。训练过程可以首先使用cv:: FlannBasedMatcher来优化,为 descriptor建立索引树,这种操作将在匹配大量数据时发挥巨大作用(比如在上百幅图像的数据集中查找匹配图像)。而 Brute-force matcher在这个过程并不进行操作,它只是将train descriptors保存在内存中。

#include <stdio.h>
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/nonfree/features2d.hpp"

using namespace cv;

/** @function main */
int main( int argc, char** argv )
{
    Mat img_1 = imread("box.png", CV_LOAD_IMAGE_GRAYSCALE );
    Mat img_2 = imread("box_in_scene.png", CV_LOAD_IMAGE_GRAYSCALE );

    if( !img_1.data || !img_2.data )
    { std::cout<< " --(!) Error reading images " << std::endl; return -1; }

    //-- Step 1: Detect the keypoints using SURF Detector
    int minHessian = 400;

    SurfFeatureDetector detector( minHessian );

    std::vector<KeyPoint> keypoints_1, keypoints_2;

    detector.detect( img_1, keypoints_1 );
    detector.detect( img_2, keypoints_2 );

    //-- Step 2: Calculate descriptors (feature vectors)
    SurfDescriptorExtractor extractor;

    Mat descriptors_1, descriptors_2;

    extractor.compute( img_1, keypoints_1, descriptors_1 );
    extractor.compute( img_2, keypoints_2, descriptors_2 );

    //-- Step 3: Matching descriptor vectors using FLANN matcher
    FlannBasedMatcher matcher;
    std::vector< DMatch > matches;
    matcher.match( descriptors_1, descriptors_2, matches );

    double max_dist = 0; double min_dist = 100;

    //-- Quick calculation of max and min distances between keypoints
    for( int i = 0; i < descriptors_1.rows; i++ )
    { double dist = matches[i].distance;
    if( dist < min_dist ) min_dist = dist;
    if( dist > max_dist ) max_dist = dist;
    }

    printf("-- Max dist : %f \n", max_dist );
    printf("-- Min dist : %f \n", min_dist );

    //-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist )
    //-- PS.- radiusMatch can also be used here.
    std::vector< DMatch > good_matches;

    for( int i = 0; i < descriptors_1.rows; i++ )
    { if( matches[i].distance < 2*min_dist )
    { good_matches.push_back( matches[i]); }
    }

    //-- Draw only "good" matches
    Mat img_matches;
    drawMatches( img_1, keypoints_1, img_2, keypoints_2,
        good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
        vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );

    //-- Show detected matches
    imshow( "Good Matches", img_matches );

    for( int i = 0; i < good_matches.size(); i++ )
    { printf( "-- Good Match [%d] Keypoint 1: %d  -- Keypoint 2: %d  \n", i, good_matches[i].queryIdx, good_matches[i].trainIdx ); }

    waitKey(0);

    return 0;
}

Reference

[1] 高维数据的快速最近邻算法FLANN
[2] k-d tree算法
[3] KD-树介绍
[4] 近似最近邻搜索方法FLANN简介
[5] 计算机视觉:OpenCV的最近邻开源库FLANN

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值