OpenCV2:特征匹配及其优化
在OpenCV2简单的特征匹配中对使用OpenCV2进行特征匹配的步骤做了一个简单的介绍,其匹配出的结果是非常粗糙的,在这篇文章中对使用OpenCV2进行匹配的细化做一个简单的总结。主要包括以下几个内容:
- DescriptorMatcher
- DMatcher
- KNN匹配
- 计算两视图的基础矩阵F,并细化匹配结果
- 计算两视图的单应矩阵H,并细化匹配结果
DescriptorMatcher 和 DMatcher
DescriptorMatcher是匹配特征向量的抽象类,在OpenCV2中的特征匹配方法都继承自该类(例如:BFmatcher,FlannBasedMatcher)。该类主要包含了两组匹配方法:图像对之间的匹配以及图像和一个图像集之间的匹配。
用于图像对之间匹配的方法的声明
- // Find one best match for each query descriptor (if mask is empty).
- CV_WRAP void match( const Mat& queryDescriptors, const Mat& trainDescriptors,
- CV_OUT vector<DMatch>& matches, const Mat& mask=Mat() ) const;
- // Find k best matches for each query descriptor (in increasing order of distances).
- // compactResult is used when mask is not empty. If compactResult is false matches
- // vector will have the same size as queryDescriptors rows. If compactResult is true
- // matches vector will not contain matches for fully masked out query descriptors.
- CV_WRAP void knnMatch( const Mat& queryDescriptors, const Mat& trainDescriptors,
- CV_OUT vector<vector<DMatch> >& matches, int k,
- const Mat& mask=Mat(), bool compactResult=false ) const;
- // Find best matches for each query descriptor which have distance less than
- // maxDistance (in increasing order of distances).
- void radiusMatch( const Mat& queryDescriptors, const Mat& trainDescriptors,
- vector<vector<DMatch> >& matches, float maxDistance,
- const Mat& mask=Mat(), bool compactResult=false ) const;
方法重载,用于图像和图像集匹配的方法声明
- CV_WRAP void match( const Mat& queryDescriptors, CV_OUT vector<DMatch>& matches,
- const vector<Mat>& masks=vector<Mat>() );
- CV_WRAP void knnMatch( const Mat& queryDescriptors, CV_OUT vector<vector<DMatch> >& matches, int k,
- const vector<Mat>& masks=vector<Mat>(), bool compactResult=false );
- void radiusMatch( const Mat& queryDescriptors, vector<vector<DMatch> >& matches, float maxDistance,
- const vector<Mat>& masks=vector<Mat>(), bool compactResult=false );
DMatcher 是用来保存匹配结果的,主要有以下几个属性
- CV_PROP_RW int queryIdx; // query descriptor index
- CV_PROP_RW int trainIdx; // train descriptor index
- CV_PROP_RW int imgIdx; // train image index
- CV_PROP_RW float distance;
在图像匹配时有两种图像的集合,查找集(Query Set)和训练集(Train Set),对于每个Query descriptor,DMatch中保存了和其最好匹配的Train descriptor。另外,每个train image会生成多个train descriptor。
如果是图像对之间的匹配的话,由于所有的train descriptor都是由一个train image生成的,所以在匹配结果DMatch中所有的imgIdx是一样的,都为0.
KNNMatch
匹配过程中很可能发生错误的匹配,错误的匹配主要有两种:匹配的特征点事错误的,图像上的特征点无法匹配。常用的删除错误的匹配有
- 交叉过滤
如果第一幅图像的一个特征点和第二幅图像的一个特征点相匹配,则进行一个相反的检查,即将第二幅图像上的特征点与第一幅图像上相应特征点进行匹配,如果匹配成功,则认为这对匹配是正确的。
OpenCV中的BFMatcher已经包含了这种过滤 BFMatcher matcher(NORM_L2,true),在构造BFMatcher是将第二个参数设置为true。
- 比率测试
KNNMatch,可设置K = 2 ,即对每个匹配返回两个最近邻描述符,仅当第一个匹配与第二个匹配之间的距离足够小时,才认为这是一个匹配。
在抽象基类DescriptorMatcher中封装了knnMatch方法,具体使用方法如下:
- void FeatureMatchTest::knnMatch(vector<DMatch>& matches) {
- const float minRatio = .f / 1.5f;
- const int k = ;
- vector<vector<DMatch>> knnMatches;
- matcher->knnMatch(leftPattern->descriptors, rightPattern->descriptors, knnMatches, k);
- for (size_t i = ; i < knnMatches.size(); i++) {
- const DMatch& bestMatch = knnMatches[i][];
- const DMatch& betterMatch = knnMatches[i][];
- float distanceRatio = bestMatch.distance / betterMatch.distance;
- if (distanceRatio < minRatio)
- matches.push_back(bestMatch);
- }
- }
RASIC方法计算基础矩阵,并细化匹配结果
如果已经知道了两视图(图像)间的多个点的匹配,就可以进行基础矩阵F的计算了。OpenCV2中可以使用findFundamentalMat方法,其声明如下:
- //! finds fundamental matrix from a set of corresponding 2D points
- CV_EXPORTS_W Mat findFundamentalMat( InputArray points1, InputArray points2,
- int method=FM_RANSAC,
- double param1=., double param2=0.99,
- OutputArray mask=noArray());
参数说明:
points1,points2 两幅图像间相匹配的点,点的坐标要是浮点数(float或者double)
第三个参数method是用来计算基础矩阵的具体方法,是一个枚举值。
param1,param2保持默认值即可。
主要来说下mask参数,有N个匹配点用来计算基础矩阵,则该值有N个元素,每个元素的值为0或者1.值为0时,代表该匹配点事错误的匹配(离群值),只在使用RANSAC和LMeds方法时该值有效,
可以使用该值来删除错误的匹配。
另外,在匹配完成后使用得到的匹配点来计算基础矩阵时,首先需要将特征点对齐,并且将特征点转换为2D点,具体实现如下:
- //Align all points
- vector<KeyPoint> alignedKps1, alignedKps2;
- for (size_t i = ; i < matches.size(); i++) {
- alignedKps1.push_back(leftPattern->keypoints[matches[i].queryIdx]);
- alignedKps2.push_back(rightPattern->keypoints[matches[i].trainIdx]);
- }
- //Keypoints to points
- vector<Point2f> ps1, ps2;
- for (unsigned i = ; i < alignedKps1.size(); i++)
- ps1.push_back(alignedKps1[i].pt);
- for (unsigned i = ; i < alignedKps2.size(); i++)
- ps2.push_back(alignedKps2[i].pt);
使用RANSAC方法计算基础矩阵后可以得到一个status向量,用来删除错误的匹配
- //优化匹配结果
- vector<KeyPoint> leftInlier;
- vector<KeyPoint> rightInlier;
- vector<DMatch> inlierMatch;
- int index = ;
- for (unsigned i = ; i < matches.size(); i++) {
- if (status[i] != ){
- leftInlier.push_back(alignedKps1[i]);
- rightInlier.push_back(alignedKps2[i]);
- matches[i].trainIdx = index;
- matches[i].queryIdx = index;
- inlierMatch.push_back(matches[i]);
- index++;
- }
- }
- leftPattern->keypoints = leftInlier;
- rightPattern->keypoints = rightInlier;
- matches = inlierMatch;
计算单应矩阵H,并细化匹配结果
同基础矩阵类似,得到匹配的特征点后也可以计算单应矩阵。
- //! computes the best-fit perspective transformation mapping srcPoints to dstPoints.
- CV_EXPORTS_W Mat findHomography( InputArray srcPoints, InputArray dstPoints,
- int method=, double ransacReprojThreshold=,
- OutputArray mask=noArray());
参数说明:
srcPoints,dstPoints是两视图中匹配的点
method 是计算单应矩阵所使用的方法,是一个枚举值。
ransacReprojThreshold 是允许的最大反投影错误,只在使用RANSAC方法时有效。
mask 同findFundamentalMat 类似,指出匹配的点是不是离群值,用来优化匹配结果。
- void FeatureMatchTest::refineMatcheswithHomography(vector<DMatch>& matches, double reprojectionThreshold, Mat& homography){
- const int minNumbermatchesAllowed = ;
- if (matches.size() < minNumbermatchesAllowed)
- return;
- //Prepare data for findHomography
- vector<Point2f> srcPoints(matches.size());
- vector<Point2f> dstPoints(matches.size());
- for (size_t i = ; i < matches.size(); i++) {
- srcPoints[i] = rightPattern->keypoints[matches[i].trainIdx].pt;
- dstPoints[i] = leftPattern->keypoints[matches[i].queryIdx].pt;
- }
- //find homography matrix and get inliers mask
- vector<uchar> inliersMask(srcPoints.size());
- homography = findHomography(srcPoints, dstPoints, CV_FM_RANSAC, reprojectionThreshold, inliersMask);
- vector<DMatch> inliers;
- for (size_t i = ; i < inliersMask.size(); i++){
- if (inliersMask[i])
- inliers.push_back(matches[i]);
- }
- matches.swap(inliers);
- }
匹配结果对比
基础矩阵后的过滤 | 单应矩阵后的过滤 |
![]() |
![]() |
交叉过滤 | KNNMatch |
![]() |
![]() |
代码说明
定义了Pattern结构用来保存匹配过程中需要用到的数据
- struct Pattern
- {
- cv::Mat image;
- std::vector<cv::KeyPoint> keypoints;
- cv::Mat descriptors;
- Pattern(cv::Mat& img) :
- image(img) {}
- };
将各种匹配方法封装到了一个类中,在该类的构造函数中填充Pattern取得匹配所需的数据
- FeatureMatchTest::FeatureMatchTest(std::shared_ptr<Pattern> left, std::shared_ptr<Pattern> right, std::shared_ptr<cv::DescriptorMatcher> matcher) :
- leftPattern(left), rightPattern(right), matcher(matcher) {
- //step1:Create detector
- int minHessian = ;
- SurfFeatureDetector detector(minHessian);
- //step2:Detecte keypoint
- detector.detect(leftPattern->image, leftPattern->keypoints);
- detector.detect(rightPattern->image, rightPattern->keypoints);
- //step3:Compute descriptor
- detector.compute(leftPattern->image, leftPattern->keypoints, leftPattern->descriptors);
- detector.compute(rightPattern->image, rightPattern->keypoints, rightPattern->descriptors);
- }
看评论好多想参看下这部分代码的,终于从硬盘上扒拉出来了,上传到了CSDN上。 下载地址: http://download.csdn.net/detail/brookicv/9729163
OpenCV2:特征匹配及其优化的更多相关文章
- OpenCV2简单的特征匹配
特征的匹配大致可以分为3个步骤: 特征的提取 计算特征向量 特征匹配 对于3个步骤,在OpenCV2中都进行了封装.所有的特征提取方法都实现FeatureDetector接口,DescriptorEx ...
- OpenCV探索之路(二十三):特征检测和特征匹配方法汇总
一幅图像中总存在着其独特的像素点,这些点我们可以认为就是这幅图像的特征,成为特征点.计算机视觉领域中的很重要的图像特征匹配就是一特征点为基础而进行的,所以,如何定义和找出一幅图像中的特征点就非常重要. ...
- (三)ORB特征匹配
ORBSLAM2匹配方法流程 在基于特征点的视觉SLAM系统中,特征匹配是数据关联最重要的方法.特征匹配为后端优化提供初值信息,也为前端提供较好的里程计信息,可见,若特征匹配出现问题,则整个视觉SLA ...
- opencv学习之路(34)、SIFT特征匹配(二)
一.特征匹配简介 二.暴力匹配 1.nth_element筛选 #include "opencv2/opencv.hpp" #include <opencv2/nonfree ...
- 第十六节、基于ORB的特征检测和特征匹配
之前我们已经介绍了SIFT算法,以及SURF算法,但是由于计算速度较慢的原因.人们提出了使用ORB来替代SIFT和SURF.与前两者相比,ORB有更快的速度.ORB在2011年才首次发布.在前面小节中 ...
- OpenCV-Python sift/surf特征匹配与显示
import cv2 import numpy as np def drawMatchesKnn_cv2(img1_gray,kp1,img2_gray,kp2,goodMatch): h1, w1 ...
- OpenCV-Python 特征匹配 | 四十四
目标 在本章中, 我们将看到如何将一个图像中的特征与其他图像进行匹配. 我们将在OpenCV中使用Brute-Force匹配器和FLANN匹配器 Brute-Force匹配器的基础 蛮力匹配器很简单. ...
- OpenCV 之 特征匹配
OpenCV 中有两种特征匹配方法:暴力匹配 (Brute force matching) 和 最近邻匹配 (Nearest Neighbors matching) 它们都继承自 Descriptor ...
- 特征提取(Detect)、特征描述(Descriptor)、特征匹配(Match)的通俗解释
特征匹配(Feature Match)是计算机视觉中很多应用的基础,比如说图像配准,摄像机跟踪,三维重建,物体识别,人脸识别,所以花一些时间去深入理解这个概念是不为过的.本文希望通过一种通俗易懂的方式 ...
随机推荐
- sql查询
结果集是 id name 1 张三2 张三3 李四4 王五5 王五我想查询出有多少不重名的人的数量,并显示在每一行结果集里面结果如下:num id name 3 1 张三3 2 ...
- Android ANR 分析解决方法
一:什么是ANR ANR:Application Not Responding,即应用无响应 二:ANR的类型 ANR一般有三种类型: 1. KeyDispatchTimeout(5 seconds) ...
- java.sql.SQLException: 关闭的连接
在Dao接口实现类里面的conn.close()之类的关闭数据库连接的代码注释掉就可以了. 可能还有别的解决方法,不过这样改比较方便.
- WPF+通过配置文件生成菜单(Menu)+源码
这个月做项目,遇到过一个通过配置文件来生成菜单的解决方案,感觉挺优雅的,特地放到博客园来,以飨读者. 说来惭愧,以前做的项目都没有这样用过,都是固定死了.如果后续有需要加入菜单,还得在重新修改UI,然 ...
- C# 6.0 功能预览 (一)
一.索引的成员和元素初始化 1.1 原始初始化集合 Dictionary 1.2 键值初始化集合 Dictionary 1.3 运算符 $ 初始化集合 Dictionary 二.自动属性的初始化 一不 ...
- QParserGenerator的文法文件介绍
在沉默了数月之后,博主心血来潮想继续介绍QParserGenerator,在这里我们将不再继续介绍任何有关于LALR(1)的算法(那东西只会把你的脑子变成一团浆糊),让我们来看一下QParserGen ...
- Html5绘制时钟
最近在对Html5比较感兴趣,就用空闲时间做一些小例子进行练习,今天绘制一个走动的时钟,具体如下图所示: 具体思路在上图已有说明,代码如下: <script type="text/ja ...
- HTML5入门以及新标签
HTML5 学习总结(一)--HTML5入门与新增标签 目录 一.HTML5概要 1.1.为什么需要HTML5 1.2.什么是HTML5 1.3.HTML5现状及浏览器支持 1.4.HTML5特性 ...
- 【WP开发】读写剪贴板
在WP 8.1中只有Silverlight App支持操作剪贴板的API,Runtime App并不支持.不过,在WP 10中也引入了可以操作剪贴板的API. 顺便说点题外话,有人会说,我8.1的开发 ...
- Makefile
原文链接:http://www.orlion.ga/816/ 一.基本规则 对于一个拥有多个文件的c项目,编译时可能是这样的指令: gcc main.c stack.c -o main 如果编译之后又 ...