在本章中
Brute-Force 匹配器很简单。它将第一组中的一个特征的描述符与第二组中的所有其他特征使用某种距离计算进行匹配。然后返回最接近的一个。
对于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,默认为假。如果它为真,Matcher只返回那些具有(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各一个例子(两者使用不同的距离测量)。
在这里,我们将看到一个简单的例子,说明如何在两个图像之间进行特征匹配。在这种情况下,我有一个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)
接下来我们创建一个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)
# D绘制前10个匹配结果
img3 = cv.drawMatches(img1,kp1,img2,kp2,matches[:10],None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.imshow(img3),plt.show()
matches = bf.match(des1,des2) 行的结果是一个DMatch对象的列表。这个DMatch对象有以下属性:
这一次,我们将使用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
bf = cv.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是Fast Library for Approximate Nearest Neighbors的缩写。它包含一个算法集合,为在大数据集和高维特征中进行快速近邻搜索而优化。对于大数据集,它比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(check=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()