OpenCV 4.11.0
开源计算机视觉库
加载中…
搜索中…
未找到匹配项
特征匹配

目标

本章内容

  • 我们将学习如何在图像之间进行特征匹配。
  • 我们将使用OpenCV中的暴力匹配器和FLANN匹配器。

暴力匹配器的基础

暴力匹配器很简单。它取第一组特征中的一个描述符,并使用某种距离计算方法与第二组中的所有其他特征进行匹配。然后返回距离最近的匹配。

对于BF匹配器,首先我们必须使用cv.BFMatcher()创建BFMatcher对象。它有两个可选参数。第一个是normType,它指定要使用的距离度量方法。默认值为cv.NORM_L2。它适用于SIFT、SURF等(cv.NORM_L1也是可用的)。对于基于二进制字符串的描述符,如ORB、BRIEF、BRISK等,应使用cv.NORM_HAMMING,它使用汉明距离作为度量。如果ORB使用WTA_K == 3或4,则应使用cv.NORM_HAMMING2

第二个参数是一个布尔变量crossCheck,默认为false。如果为true,匹配器只返回那些满足以下条件的匹配(i,j):集合A中的第i个描述符在集合B中具有第j个描述符作为最佳匹配,反之亦然。也就是说,两组中的两个特征应该相互匹配。它提供一致的结果,并且是D.Lowe在SIFT论文中提出的比率检验的一个很好的替代方案。

创建对象后,两个重要的函数是BFMatcher.match()BFMatcher.knnMatch()。第一个返回最佳匹配。第二个函数返回k个最佳匹配,其中k由用户指定。当我们需要对此进行额外处理时,它可能很有用。

就像我们使用cv.drawKeypoints()绘制关键点一样,cv.drawMatches()帮助我们绘制匹配项。它将两幅图像水平堆叠,并从第一幅图像绘制到第二幅图像的线,显示最佳匹配。还有一个cv.drawMatchesKnn可以绘制所有k个最佳匹配。如果k=2,它将为每个关键点绘制两条匹配线。因此,如果我们想选择性地绘制它,我们必须传递一个掩码。

让我们分别来看一下SIFT和ORB的一个例子(两者使用不同的距离度量)。

使用ORB描述符的暴力匹配

在这里,我们将看到一个关于如何在两幅图像之间匹配特征的简单示例。在本例中,我有一个queryImage和一个trainImage。我们将尝试使用特征匹配在trainImage中找到queryImage。(图像为/samples/data/box.png和/samples/data/box_in_scene.png)

我们使用ORB描述符来匹配特征。因此,让我们从加载图像、查找描述符等开始。

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 = cv.imread('box.png',cv.IMREAD_GRAYSCALE) # queryImage
img2 = cv.imread('box_in_scene.png',cv.IMREAD_GRAYSCALE) # trainImage
# 初始化ORB检测器
orb = cv.ORB_create()
# 使用ORB查找关键点和描述符
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
从文件中加载图像。

接下来,我们使用距离度量cv.NORM_HAMMING(因为我们使用的是ORB)创建一个BFMatcher对象,并打开crossCheck以获得更好的结果。然后我们使用Matcher.match()方法来获取两幅图像中的最佳匹配。我们按距离升序对它们进行排序,以便最佳匹配(距离较小)排在前面。然后我们只绘制前10个匹配项(仅仅是为了可见性。你可以根据需要增加它)

# 创建BFMatcher对象
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
# 匹配描述符。
matches = bf.match(des1,des2)
# 按距离排序。
matches = sorted(matches, key = lambda x:x.distance)
# 绘制前10个匹配项。
img3 = cv.drawMatches(img1,kp1,img2,kp2,matches[:10],None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.imshow(img3),plt.show()
暴力匹配描述符匹配器。
定义 features2d.hpp:1247
void drawMatches(InputArray img1, const std::vector< KeyPoint > &keypoints1, InputArray img2, const std::vector< KeyPoint > &keypoints2, const std::vector< DMatch > &matches1to2, InputOutputArray outImg, const Scalar &matchColor=Scalar::all(-1), const Scalar &singlePointColor=Scalar::all(-1), const std::vector< char > &matchesMask=std::vector< char >(), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT)
绘制两幅图像中找到的关键点匹配。

以下是我的结果

图像

什么是Matcher对象?

matches = bf.match(des1,des2)行的结果是DMatch对象的列表。此DMatch对象具有以下属性:

  • DMatch.distance - 描述符之间的距离。越低越好。
  • DMatch.trainIdx - 训练描述符中描述符的索引
  • DMatch.queryIdx - 查询描述符中描述符的索引
  • DMatch.imgIdx - 训练图像的索引。

使用SIFT描述符和比率测试的暴力匹配

这次,我们将使用BFMatcher.knnMatch()来获得k个最佳匹配。在这个例子中,我们将取k=2,这样我们就可以应用D.Lowe在他的论文中解释的比率测试。

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 = cv.imread('box.png',cv.IMREAD_GRAYSCALE) # queryImage
img2 = cv.imread('box_in_scene.png',cv.IMREAD_GRAYSCALE) # trainImage
# 初始化SIFT检测器
sift = cv.SIFT_create()
# 使用SIFT查找关键点和描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# 使用默认参数的BFMatcher
matches = bf.knnMatch(des1,des2,k=2)
# 应用比率测试
good = []
for m,n in matches
if m.distance < 0.75*n.distance
good.append([m])
# cv.drawMatchesKnn期望匹配项列表为列表列表。
img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.imshow(img3),plt.show()

请参见下面的结果

图像

基于FLANN的匹配器

FLANN代表快速近似最近邻库。它包含一系列针对大型数据集和高维特征的快速最近邻搜索而优化的算法。对于大型数据集,它的速度比BFMatcher更快。我们将使用基于FLANN的匹配器来看第二个例子。

对于基于FLANN的匹配器,我们需要传递两个字典,它们指定要使用的算法、相关参数等。第一个是IndexParams。对于各种算法,FLANN文档中解释了要传递的信息。总而言之,对于SIFT、SURF等算法,您可以传递以下内容:

FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)

使用ORB时,您可以传入以下参数。注释中的值是文档中推荐的值,但在某些情况下未能提供所需的结果。其他值运行良好。

FLANN_INDEX_LSH = 6
index_params= dict(algorithm = FLANN_INDEX_LSH,
table_number = 6, # 12
key_size = 12, # 20
multi_probe_level = 1) #2

第二个字典是SearchParams。它指定应递归遍历索引中树的次数。较高的值可提供更高的精度,但也需要更多时间。如果要更改此值,请传入search_params = dict(checks=100)。

有了这些信息,我们就可以开始了。

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 = cv.imread('box.png',cv.IMREAD_GRAYSCALE) # queryImage
img2 = cv.imread('box_in_scene.png',cv.IMREAD_GRAYSCALE) # trainImage
# 初始化SIFT检测器
sift = cv.SIFT_create()
# 使用SIFT查找关键点和描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# FLANN参数
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50) # 或传入空字典
flann = cv.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)
# 只需绘制良好的匹配项,因此创建一个掩码
matchesMask = [[0,0] for i in range(len(matches))]
# 根据Lowe的论文进行比率测试
for i,(m,n) in enumerate(matches)
if m.distance < 0.7*n.distance
matchesMask[i]=[1,0]
draw_params = dict(matchColor = (0,255,0),
singlePointColor = (255,0,0),
matchesMask = matchesMask,
flags = cv.DrawMatchesFlags_DEFAULT)
img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,matches,None,**draw_params)
plt.imshow(img3,),plt.show()
基于FLANN的描述符匹配器。
定义 features2d.hpp:1294

请参见下面的结果

图像