OpenCV 中有两种特征匹配方法:暴力匹配 (Brute force matching) 和 最近邻匹配 (Nearest Neighbors matching)

它们都继承自 DescriptorMatcher,是基于特征描述符距离的匹配,根据描述符的不同,距离可以是 欧氏距离,也可以是 汉明距

1  暴力匹配

首先,任取图像 A 的一个特征描述符,计算它到图像 B 中所有特征描述符的距离;然后,将所得到的距离进行排序;最后,选择距离最短的特征,作为 A-B 的匹配点

1.1  BFMatcher

BFMatcher 属于 features2d 模块,继承自 DescriptorMatcher,其 create() 函数如下:

    static Ptr<BFMatcher> create(
int normType = NORM_L2, // normType, One of NORM_L1, NORM_L2, NORM_HAMMING, NORM_HAMMING2.
bool crossCheck = false // crossCheck
);

    1) normType 距离类型

SIFT和SURF 的 HOG 描述符,对应欧氏距离 L1 和 L2;ORB 和 BRISK 的 BRIEF 描述符,对应汉明距 HAMMING;HAMMING2 则对应当 WTA_K = 3或4 时的 ORB 算法

- 欧氏距离:最常用的一种距离定义,指的是 n 维空间中,两点之间的实际距离

$L1 = \sum_I | \texttt{src1} (I) - \texttt{src2}|$

$L2 = \sqrt{\sum_I (\texttt{src1}(I) - \texttt{src2}(I))^2}$

- 汉明距离:实际是计算机的异或操作,适用于二进制串描述符,如 BRIEF 描述符,定义如下:

$ Hamming \left ( a,b \right ) = \sum\limits_{i=0}^{n-1} \left ( a_i \oplus b_i \right ) $

    2) crossCheck 交叉核对

- 如果在图像 B 中,特征 $f_{b}$ 是特征 $f_{a}$ 的最佳匹配,并且在图像 A 中,特征 $f_{a}$ 也是特征 $f_{b}$ 的最佳匹配,则称 $(f_{a}, f_{b})$ 为 "good match"

1.2  代码示例

特征匹配步骤如下:读图 -> 提取特征 -> 计算特征描述符 -> 暴力匹配 -> 显示匹配结果

#include "opencv2/highgui.hpp"
#include "opencv2/features2d.hpp" using namespace cv; int main()
{
// 1) read
Mat img1 = imread("box.png", IMREAD_GRAYSCALE);
Mat img2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);
if (img1.empty() || img2.empty())
return -1; // 2) detect and compute
Ptr<SIFT> sift = SIFT::create();
std::vector<KeyPoint> kps1, kps2;
Mat desc1, desc2;
sift->detectAndCompute(img1, Mat(), kps1, desc1);
sift->detectAndCompute(img2, Mat(), kps2, desc2); // 3) match
Ptr<BFMatcher> bfmatcher = BFMatcher::create(NORM_L2, true);
std::vector<DMatch> matches;
bfmatcher->match(desc1, desc2, matches); // 4) draw and show
Mat img_matches;
drawMatches(img1, kps1, img2, kps2, matches, img_matches);
imshow("BFMatcher", img_matches); waitKey();
}

crosscheck 分别为 true 和 false:

    

2  最近邻匹配

FLANN 是一个开源库,全称 Fast Library for Approximate Nearest Neighbors,它实现了一系列高维向量的近似最近邻搜索算法

基于 FLANN 库的最近邻匹配算子 FlannBasedMatcher,在特征数据集较大或一些实时处理领域,其运行效率要远高于 BFMatcher

OpenCV 中 FlannBasedMatcher 的定义如下:

// This matcher trains cv::flann::Index on a train descriptor collection and calls its nearest search methods to find the best matches. 
// So, this matcher may be faster when matching a large train collection than the brute force matcher.
class FlannBasedMatcher : public DescriptorMatcher
{
public:
FlannBasedMatcher( const Ptr<flann::IndexParams>& indexParams=makePtr<flann::KDTreeIndexParams>(),
const Ptr<flann::SearchParams>& searchParams=makePtr<flann::SearchParams>() ); static Ptr<FlannBasedMatcher> create();

2.1  距离比

为了进一步提高特征匹配精度,David Lowe 提出了一种最近邻次近邻距离比的方法:

- 取图像 A 的一个特征,搜索它到图像 B 距离最近的两个特征,距离分别记为 $d_{1}$ 和 $d_{2}$,只有当 $\displaystyle{\frac{d_{1}}{d_{2}}}$ 小于某个阈值时,才认为是 "good match"

"good match" 的概率密度函数 PDF (Probability Density Function) 与最近邻次近邻距离比的关系,如下:

2.2  代码示例

取 distance ratio = 0.7,对比使用和不使用距离比滤波的匹配效果,代码如下:

#include "opencv2/highgui.hpp"
#include "opencv2/features2d.hpp" using namespace cv; const float kRatioThresh = 0.7f; int main()
{
// 1) read
Mat img1 = imread("box.png", IMREAD_GRAYSCALE);
Mat img2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);
if (img1.empty() || img2.empty())
return -1; // 2) detect feature and compute descriptor
Ptr<SIFT> sift = SIFT::create();
std::vector<KeyPoint> kps1, kps2;
Mat desc1, desc2;
sift->detectAndCompute(img1, Mat(), kps1, desc1);
sift->detectAndCompute(img2, Mat(), kps2, desc2); // 3) FLANN based matcher
Ptr<FlannBasedMatcher> knnmatcher = FlannBasedMatcher::create();
std::vector<std::vector<DMatch> > matches;
knnmatcher->knnMatch(desc1, desc2, matches, 2); // 4) filter matches using Lowe's distance ratio test
std::vector<DMatch> good_matches;
for (size_t i = 0; i < matches.size(); i++)
{
if (matches[i][0].distance < kRatioThresh*matches[i][1].distance)
{
good_matches.push_back(matches[i][0]);
}
}
// 5) draw and show matches
Mat img_matches;
drawMatches(img1, kps1, img2, kps2, good_matches, img_matches);
imshow("Good Matches", img_matches);

waitKey();
}

匹配效果对比如下:

    

3  应用示例

特征匹配 + 平面单应性,在计算机视觉中有很多应用,如:透视校正,目标定位等

3.1  透视校正

OpenCV 之 平面单应性 4.1 中的示例,并不是标准的透视校正,因为是人拿着标定板旋转不同角度,使相机和标定板产生了相对的视角变换,而不是相机和整个场景之间

多视图几何中,严格意思的透视校正,是指相机在不同的视角下,对同一场景成不同的像而进行的视角校正,如下图:

在得到匹配点对 good_matches 之后,再执行如下代码,便可用于透视校正

    // Localize the object
std::vector<Point2f> obj;
std::vector<Point2f> scene;
for (size_t i = 0; i < good_matches.size(); i++)
{
// Get the keypoints from the good matches
obj.push_back(kps1[good_matches[i].queryIdx].pt);
scene.push_back(kps2[good_matches[i].trainIdx].pt);
} // estimate H
Mat H = findHomography(scene, obj, RANSAC); // warp scene
Mat scene_warp;
warpPerspective(img2, scene_warp, H, Size(1.35*img2.cols, img2.rows)); // show
imshow("scene_warp", scene_warp);

校正前后的结果如下:

    

3.2  目标定位

得到匹配点对 good_matches 后,再执行如下代码,便可用于目标定位

    // Localize the object
std::vector<Point2f> obj;
std::vector<Point2f> scene;
for (size_t i = 0; i < good_matches.size(); i++)
{
// Get the keypoints from the good matches
obj.push_back(kps1[good_matches[i].queryIdx].pt);
scene.push_back(kps2[good_matches[i].trainIdx].pt);
}
// estimate H
Mat H = findHomography(obj,scene, RANSAC); // get the corners from the image_1 ( the object to be "detected" )
std::vector<Point2f> obj_corners(4);
obj_corners[0] = Point2f(0, 0);
obj_corners[1] = Point2f((float)img1.cols, 0);
obj_corners[2] = Point2f((float)img1.cols, (float)img1.rows);
obj_corners[3] = Point2f(0, (float)img1.rows); std::vector<Point2f> scene_corners(4);
perspectiveTransform(obj_corners, scene_corners, H); // draw lines between the corners (the mapped object in the scene - image_2 )
line(img_matches, scene_corners[0] + Point2f((float)img1.cols, 0), scene_corners[1] + Point2f((float)img1.cols, 0), Scalar(0,255,0));
line(img_matches, scene_corners[1] + Point2f((float)img1.cols, 0), scene_corners[2] + Point2f((float)img1.cols, 0), Scalar(0,255,0));
line(img_matches, scene_corners[2] + Point2f((float)img1.cols, 0), scene_corners[3] + Point2f((float)img1.cols, 0), Scalar(0,255,0));
line(img_matches, scene_corners[3] + Point2f((float)img1.cols, 0), scene_corners[0] + Point2f((float)img1.cols, 0), Scalar(0,255,0)); // show detected matches
imshow("Object detection", img_matches);

目标定位结果如下:

参考资料

OpenCV-Python Tutorials / Feature Detection and Description / Feature Matching

OpenCV Tutorials / 2D Features framework (feature2d module) / Feature Matching with FLANN

OpenCV Tutorials / 2D Features framework (feature2d module) / Features2D + Homography to find a known object

OpenCV 之 特征匹配的更多相关文章

  1. [OpenCV]基于特征匹配的实时平面目标检测算法

    一直想基于传统图像匹配方式做一个融合Demo,也算是对上个阶段学习的一个总结. 由此,便采购了一个摄像头,在此基础上做了实时检测平面目标的特征匹配算法. 代码如下: # coding: utf-8 ' ...

  2. OpenCV探索之路(二十三):特征检测和特征匹配方法汇总

    一幅图像中总存在着其独特的像素点,这些点我们可以认为就是这幅图像的特征,成为特征点.计算机视觉领域中的很重要的图像特征匹配就是一特征点为基础而进行的,所以,如何定义和找出一幅图像中的特征点就非常重要. ...

  3. opencv学习之路(34)、SIFT特征匹配(二)

    一.特征匹配简介 二.暴力匹配 1.nth_element筛选 #include "opencv2/opencv.hpp" #include <opencv2/nonfree ...

  4. opencv surf特征点匹配拼接源码

    http://blog.csdn.net/huixingshao/article/details/42672073 /** * @file SURF_Homography * @brief SURF ...

  5. OpenCV2:特征匹配及其优化

    在OpenCV2简单的特征匹配中对使用OpenCV2进行特征匹配的步骤做了一个简单的介绍,其匹配出的结果是非常粗糙的,在这篇文章中对使用OpenCV2进行匹配的细化做一个简单的总结.主要包括以下几个内 ...

  6. 第十六节、基于ORB的特征检测和特征匹配

    之前我们已经介绍了SIFT算法,以及SURF算法,但是由于计算速度较慢的原因.人们提出了使用ORB来替代SIFT和SURF.与前两者相比,ORB有更快的速度.ORB在2011年才首次发布.在前面小节中 ...

  7. OpenCV-Python 特征匹配 + 单应性查找对象 | 四十五

    目标 在本章节中,我们将把calib3d模块中的特征匹配和findHomography混合在一起,以在复杂图像中找到已知对象. 基础 那么我们在上一环节上做了什么?我们使用了queryImage,找到 ...

  8. OpenCV-Python 特征匹配 | 四十四

    目标 在本章中, 我们将看到如何将一个图像中的特征与其他图像进行匹配. 我们将在OpenCV中使用Brute-Force匹配器和FLANN匹配器 Brute-Force匹配器的基础 蛮力匹配器很简单. ...

  9. OpenCV2简单的特征匹配

    特征的匹配大致可以分为3个步骤: 特征的提取 计算特征向量 特征匹配 对于3个步骤,在OpenCV2中都进行了封装.所有的特征提取方法都实现FeatureDetector接口,DescriptorEx ...

随机推荐

  1. 8、linux常用命令

    8.1.pwd: 显示当前的路径: -L:显示逻辑路径,即快捷方式的路径(默认的参数): -P :显示物理路径,真实的路径: 8.2.man: 命令的查看: 8.3.help: 命令的查看: 8.4. ...

  2. nginx日志分析及其统计PV、UV、IP

    一.nginx日志结构 nginx中access.log 的日志结构: $remote_addr 客户端地址 211.28.65.253 $remote_user 客户端用户名称 -- $time_l ...

  3. Quartz:Quartz定时代码实现

    1.添加pom.xml <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId> ...

  4. docker挂载数据卷

    1.Docker中的数据可以存储在类似于虚拟机磁盘的介质中,在Docker中称为数据卷,简单的理解就是将数据持久化的工具. 2.在使用docker容器的时候,会产生一系列的数据文件,这些数据文件在我们 ...

  5. linux驱动之获取设备树信息

    上一篇文章学习了字符设备的注册,操作过的小伙伴都知道上一篇文章中测试驱动时是通过手动创建设备节点的,现在开始学习怎么自动挂载设备节点和设备树信息的获取,这篇文章中的源码将会是我以后编写字符驱动的模板. ...

  6. Java程序设计当堂测试 9.20

    /*Java当堂测试 ATM机模拟系统由于学习的知识有限,不能完成所有课上项目,文件的应用没有完成,汇款转账功能也没有写,一些要求该退出的地方也没有写,基本功能还算完善*/ 1 package acc ...

  7. Python入门学习指南

    对于初学者,入门至关重要,这关系到初学者是从入门到精通还是从入门到放弃.以下是结合Python的学习经验,整理出的一条学习路径,主要有四个阶段 NO.1 新手入门阶段,学习基础知识 总体来讲,找一本靠 ...

  8. C语言中函数的返回值

    规则 除局部变量的内存地址不能作为函数的返回值外,其他类型的局部变量都能作为函数的返回值. 我总结出下面这些规则: int.char等数据类型的局部变量可以作为函数返回值. 在函数中声明的指针可以作为 ...

  9. NDT匹配: The Normal Distributions Transform: A New Approach to Laser Scan

    介绍 大多数激光匹配算法都是基于点或者线的特征匹配,该论文提出一种2D激光扫描匹配算法,方法类似于占据栅格,将2D平面分为一个个cell,对于每个cell,设定其一个正态分布,表示该网格测量到每个点的 ...

  10. vue实现menu菜单懒加载

    本文将在vue+element ui项目中简单实现menu菜单的懒加载. 最近接到这样的需求:菜单的选项不要固定的,而是下一级菜单选项需要根据上级菜单调接口来获取.what? 这不就是懒加载吗?翻了一 ...