OpenCV  4.10.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-th 个描述符具有集合 B 中的第 j-th 个描述符作为最佳匹配,反之亦然。也就是说,两组中的两个特征应相互匹配。它提供了一致的结果,是 D.Lowe 在 SIFT 论文中提出的比率测试的良好替代方案。

创建此匹配器后,两种重要的方法是 BFMatcher.match()BFMatcher.knnMatch()。第一个方法返回最佳匹配。第二个方法返回 k 个最佳匹配,其中 k 由用户指定。在我们有此类需求时可能会用到此方法。

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

让我们看一个 SIFT 和 ORB(两者使用不同的距离测量)的示例。

使用 ORB 描述符进行蛮力匹配

此处,我们将了解如何在两幅图像中匹配特征的简单示例。在这个示例中,我有一个查询图像和一个训练图像。我们尝试使用特征匹配在训练图像中找到查询图像。(该图像为 /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)
从文件中加载图像。

接下来,我们使用距离度量创建一个 BFMatcher 对象 cv.NORM_HAMMING(因为我们使用的是 ORB),并启用交叉检查以获得更好的结果。然后,我们使用 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)
绘制两幅图像中关键点的匹配项。

以下是我获得的结果

image

这个 Matcher 对象是什么?

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

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

使用SIFT描述符和比率检验进行蛮力匹配

这次,我们将使用BFMatcher.knnMatch()获取最佳匹配。在此示例中,我们将取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()

请参阅以下结果

image

基于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

请参阅以下结果

image

其他资源

练习题