二维特征框架——使用 FLANN 进行特征匹配 OpenCV v4.8.0

上一个教程特征描述

下一个教程利用特征 2D + 同源性查找已知对象

原作者Ana Huamán
兼容性OpenCV >= 3.0

目标

在本教程中,您将学习如何

使用 cv::FlannBasedMatcher 接口,以便通过使用 多维空间中的聚类和搜索模块 执行快速高效的匹配

警告
您需要使用 OpenCV contrib 模块才能使用 SURF 特征(替代方法有 ORB、KAZE…特征)。

理论

经典特征描述符(SIFT、SURF…)通常使用欧氏距离(或 L2-norm)进行比较和匹配。由于 SIFT 和 SURF 描述符表示邻域中的定向梯度直方图(对于 SURF 来说是 Haar 小波响应的直方图),因此欧氏距离的替代方法是基于直方图的度量( χ 2 \chi^{2} χ2、地球移动距离(EMD)…)。

Arandjelovic 等人在文献 [13] 中提出了扩展到 RootSIFT 描述符的方法:

用平方根(海林格)内核代替标准欧几里得距离来衡量 SIFT 描述符之间的相似性,可显著提高管道各阶段的性能。

二进制描述符(ORB、BRISK…)使用汉明距离进行匹配。这个距离相当于计算二进制字符串中不同元素的数量(应用 XOR 运算后的人口数量):

d h a m m i n g ( a , b ) = ∑ i = 0 n − 1 ( a i ⊕ b i ) d_{hamming} \left ( a,b \right ) = \sum_{i=0}^{n-1} \left ( a_i \oplus b_i \right ) dhamming(a,b)=i=0n1(aibi)
为了过滤匹配结果,Lowe 在 [161] 中提出使用距离比测试来尝试消除错误匹配。计算一个关键点的两个最近匹配点之间的距离比,当这个值低于阈值时,它就是一个好的匹配点。事实上,这一比率有助于区分模糊匹配(两个最近邻点之间的距离比接近于 1)和良好匹配。来自 SIFT 论文的下图说明了基于最近邻距离比测试的匹配正确概率。

在这里插入图片描述

其他或额外的过滤测试包括

  • 交叉检查测试(如果特征 fb 是 Ib 中 fa 的最佳匹配项,且特征 fa 是 Ia 中 fb 的最佳匹配项,则匹配 (fa,fb) 良好)
  • 几何测试(剔除不符合几何模型的匹配,例如平面物体的 RANSAC 或鲁棒性同构模型)

代码

C++
本教程代码如下所示。您也可以从此处下载

#include <iostream>
#include "opencv2/core.hpp"
#ifdef HAVE_OPENCV_XFEATURES2D
#include "opencv2/highgui.hpp"
#include "opencv2/features2d.hpp"
#include "opencv2/xfeatures2d.hpp"
using namespace cv;
using namespace cv::xfeatures2d;
using std::cout;
using std::endl;
const char* keys =
 "{ help h | | Print help message. }"
 "{ input1 | box.png | Path to input image 1. }"
 "{ input2 | box_in_scene.png | Path to input image 2. }";
int main( int argc, char* argv[] )
{
 CommandLineParser parser( argc, argv, keys );
 Mat img1 = imread( samples::findFile( parser.get<String>("input1") ), IMREAD_GRAYSCALE );
 Mat img2 = imread( samples::findFile( parser.get<String>("input2") ), IMREAD_GRAYSCALE );
 if ( img1.empty() || img2.empty() )
 {
 cout << "Could not open or find the image!\n" << endl;
 parser.printMessage();
 return -1;
 }
 //-- 第 1 步:使用 SURF 检测器检测关键点,计算描述符
 int minHessian = 400;
 Ptr<SURF> detector = SURF::create( minHessian );
 std::vector<KeyPoint> keypoints1, keypoints2;
 Mat descriptors1, descriptors2;
 detector->detectAndCompute( img1, noArray(), keypoints1, descriptors1 );
 detector->detectAndCompute( img2, noArray(), keypoints2, descriptors2 )//-- 第 2 步:使用基于 FLANN 的匹配器匹配描述符向量
 // 由于 SURF 是浮点描述符,因此使用 NORM_L2
 Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create(DescriptorMatcher::FLANNBASED);
 std::vector< std::vector<DMatch> > knn_match;
 matcher->knnMatch( descriptors1, descriptors2, knn_matches, 2 )//-- 使用洛氏比率测试筛选匹配项
 const float ratio_thresh = 0.7f;
 std::vector<DMatch> good_matches;
 for (size_t i = 0; i < knn_matches.size(); i++)
 {
 if (knn_matches[i][0].distance < ratio_thresh * knn_matches[i][1].distance)
 {
 good_matches.push_back(knn_matches[i][0])}
 }
 //-- 绘制匹配结果
 Mat img_matches;
 drawMatches( img1, keypoints1, img2, keypoints2, good_matches, img_matches, Scalar::all(-1)Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS )//-- 显示检测到的匹配结果
 imshow("Good Matches", img_matches );
 waitKey();
 return 0;
}
#else
int main()
{
 std::cout << "This tutorial code needs the xfeatures2d contrib module to be run." << std::endl;
 return 0;
}
#endif

Java
本教程代码如下所示。您也可以从此处下载

import java.util.ArrayList;
import java.util.List;
import org.opencv.core.Core;
import org.opencv.core.DMatch;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.core.Scalar;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.Features2d;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.xfeatures2d.SURF;
class SURFFLANNMatching {
 public void run(String[] args) {
 String filename1 = args.length > 1 ? args[0] : "../data/box.png";
 String filename2 = args.length > 1 ? args[1] : "../data/box_in_scene.png";
 Mat img1 = Imgcodecs.imread(filename1, Imgcodecs.IMREAD_GRAYSCALE);
 Mat img2 = Imgcodecs.imread(filename2, Imgcodecs.IMREAD_GRAYSCALE);
 if (img1.empty() || img2.empty()) {
 System.err.println("Cannot read images!");
 System.exit(0);
 }
 //-- 第 1 步:使用 SURF 检测器检测关键点,计算描述符
 double hessianThreshold = 400;
 int nOctaves = 4, nOctaveLayers = 3;
 boolean extended = false, upright = false;
 SURF detector = SURF.create(hessianThreshold, nOctaves, nOctaveLayers, extended, upright);
 MatOfKeyPoint keypoints1 = new MatOfKeyPoint(), keypoints2 = new MatOfKeyPoint();
 Mat descriptors1 = new Mat(), descriptors2 = new Mat();
 detector.detectAndCompute(img1, new Mat(), keypoints1, descriptors1);
 detector.detectAndCompute(img2, new Mat(), keypoints2, descriptors2);
 //-- 第 2 步:使用基于 FLANN 的匹配器匹配描述符向量
 // 由于 SURF 是浮点描述符,因此使用 NORM_L2
 DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.FLANNBASED);
 List<MatOfDMatch> knnMatches = new ArrayList<>();
 matcher.knnMatch(descriptors1, descriptors2, knnMatches, 2);
 //-- 使用洛氏比率测试筛选匹配项
 float ratioThresh = 0.7f;
 List<DMatch> listOfGoodMatches = new ArrayList<>();
 for (int i = 0; i < knnMatches.size(); i++) {
 if (knnMatches.get(i).rows() > 1) {
 DMatch[] matches = knnMatches.get(i).toArray();
 if (matches[0].distance < ratioThresh * matches[1].distance) {
 listOfGoodMatches.add(matches[0]);
 }
 }
 }
 MatOfDMatch goodMatches = new MatOfDMatch();
 goodMatches.fromList(listOfGoodMatches);
 //-- 绘制匹配
 Mat imgMatches = new Mat()Features2d.drawMatches(img1, keypoints1, img2, keypoints2, goodMatches, imgMatches, Scalar.all(-1)new MatOfByte(), Features2d.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)//-- 显示检测到的匹配
 HighGui.imshow("Good Matches", imgMatches);
 HighGui.waitKey(0);
 System.exit(0);
 }
}
public class SURFFLANNMatchingDemo {
 public static void main(String[] args) {
 // 加载本地 OpenCV 库
 System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
 new SURFFLANNMatching().run(args);
 }
}

Python
本教程代码如下所示。您也可以从此处下载

from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
parser = argparse.ArgumentParser(description='Code for Feature Matching with FLANN tutorial.')
parser.add_argument('--input1', help='Path to input image 1.', default='box.png')
parser.add_argument('--input2', help='Path to input image 2.', default='box_in_scene.png')
args = parser.parse_args()
img1 = cv.imread(cv.samples.findFile(args.input1), cv.IMREAD_GRAYSCALE)
img2 = cv.imread(cv.samples.findFile(args.input2), cv.IMREAD_GRAYSCALE)
if img1 is None or img2 is None:
 print('Could not open or find the images!')
 exit(0)
#-- 第 1 步:使用 SURF 检测器检测关键点,计算描述符
minHessian = 400
detector = cv.xfeatures2d_SURF.create(hessianThreshold=minHessian)
keypoints1, descriptors1 = detector.detectAndCompute(img1, None)
keypoints2, descriptors2 = detector.detectAndCompute(img2, None)
#-- 第 2 步:使用基于 FLANN 的匹配器匹配描述符向量
# 由于 SURF 是浮点描述符,因此使用 NORM_L2
matcher = cv.DescriptorMatcher_create(cv.DescriptorMatcher_FLANNBASED)
knn_matches = matcher.knnMatch(descriptors1, descriptors2, 2)
#-- 使用洛氏比率测试筛选匹配项
ratio_thresh = 0.7
good_matches = []
for m,n in knn_matches:
 if m.distance < ratio_thresh * n.distance:
 good_matches.append(m)
#-- 绘制匹配结果
img_matches = np.empty((max(img1.shape[0], img2.shape[0]), img1.shape[1]+img2.shape[1], 3), dtype=np.uint8)
cv.drawMatches(img1, keypoints1, img2, keypoints2, good_matches, img_matches, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
#-- 显示检测到的匹配
cv.imshow('Good Matches', img_matches)
cv.waitKey()

结果

下面是使用距离比率测试进行 SURF 特征匹配的结果:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值