目标
在这章
- 我们将看到如何将一张图片中的特征与其他图片进行匹配。
- 我们会使用 OpenCV 里的 蛮力匹配器 以及 FLANN 匹配器。
蛮力匹配器的基础
蛮力匹配器很简单。它取一个特征在第一个集合中的描述符,然后去匹配在第二个集合中的所有其他的特征,通过某种距离计算。然后返回距离最近的那个。
对于蛮力匹配器,首先我们必须创建一个蛮力匹配器对象,使用函数 cv.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个描述符,有着最优匹配,反之亦然。也就是说,两个集合中的这两个特性应该相互是匹配的。它提供了一致的结果,这是一个对于比率检定(Ratio Test)来说非常棒的修改,它由 D.Lowe 提出在SIFT的论文里。
它(蛮力匹配器对象)创建完成之后,两个重要的方法需要被提及 BFMatcher.match() 和 BFMatcher.knnMatch() 。第一个返回最佳的匹配,第二个方法返回k个最接近的匹配,k由用户指定(译者注:第一个方法其实就是k=1的情况)。当我们需要在这方面做额外的工作时,它可能是有用的。
就像我们使用过 cv.drawKeypoints() 来画出关键点一样,cv.drawMatches() 帮助我们画出这些匹配。它水平地堆叠两个图像,并从第一个图像到第二个图像绘制线,以显示最佳匹配。同样也有 cv.drawMatchesKnn 个函数来绘制k个最佳匹配关系。如果 k=2,它会为每个关键点画出两条匹配线条。因此我们必须传入一个遮罩图层,如果我们需要有选择的画出来的话。
让我们来分别针对SURF 和 ORB算法来看一个例子(它俩使用不同的距离衡量方式)。
蛮力匹配 ORB 描述符
现在,我们会看到一个关于如何匹配两张图像特征的,简单的例子。在这个例子中,我有一个搜索图像和一个训练图像。我会尝试使用特征匹配从训练图像中找出搜索图像。( 图像是 /samples/c/box.png 和 /samples/c/box_in_scene.png)
(译者附,这个原图在官网的samples上已经找不到了,我帮你们把图偷过来了。)
我们使用 ORB 描述符来匹配特征。让我们从加载图像开始,找出描述符等等。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 = cv.imread('box.png',0) # queryImage
img2 = cv.imread('box_in_scene.png',0) # trainImage
# Initiate ORB detector
orb = cv.ORB_create()
# find the keypoints and descriptors with ORB
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
接下来,我们创建一个蛮力匹配符对象使用 cv.NORM_HAMMING 汉明距离匹配方式。(因为我们使用的算法是ORB) 并且把 crossCheck 参数开启来获取更好的结果。然后我们使用 Matcher.match() 方法来获取两张图像之间的最佳匹配。我们按升序来排列它们,这样最佳的匹配(也就是距离最近的匹配)就排到了前面。然后我们只画出前10个最佳匹配。(只是处于可见性的考虑。你可以按你的喜欢来增加这个数值。)
# create BFMatcher object
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
# Match descriptors.
matches = bf.match(des1,des2)
# Sort them in the order of their distance.
matches = sorted(matches, key = lambda x:x.distance)
# Draw first 10 matches.
img3 = cv.drawMatches(img1,kp1,img2,kp2,matches[:10], flags=2)
plt.imshow(img3),plt.show()
我得到了如下结果:
这个匹配器对象是什么?
这一段代码 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
from matplotlib import pyplot as plt
img1 = cv.imread('box.png',0) # queryImage
img2 = cv.imread('box_in_scene.png',0) # trainImage
# Initiate SIFT detector
sift = cv.SIFT()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# BFMatcher with default params
bf = cv.BFMatcher()
matches = bf.knnMatch(des1,des2, k=2)
# Apply ratio test
good = []
for m,n in matches:
if m.distance < 0.75*n.distance:
good.append([m])
# cv.drawMatchesKnn expects list of lists as matches.
img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,good,flags=2)
plt.imshow(img3),plt.show()
看下面的结果:
基于FLANN匹配器
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(checks=100)。
有了这些信息,我们可以开始了。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img1 = cv.imread('box.png',0) # queryImage
img2 = cv.imread('box_in_scene.png',0) # trainImage
# Initiate SIFT detector
sift = cv.SIFT()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50) # or pass empty dictionary
flann = cv.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)
# Need to draw only good matches, so create a mask
matchesMask = [[0,0] for i in xrange(len(matches))]
# ratio test as per Lowe's paper
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 = 0)
img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,matches,None,**draw_params)
plt.imshow(img3,),plt.show()
看下面的结果: