目标
学习在图像间进行特征匹配
使用 OpenCV 中的蛮力(Brute-Force)匹配和 FLANN 匹配
Brute-Force
匹配的基础
蛮力匹配器是很简单的。首先在第一幅图像中选取一个关键点然后依次与第二幅图像的每个关键点进行(描述符)距离测试, 最后返回距离最近的关键点。
对于 BF 匹配器,首先要使用 cv2.BFMatcher()
创建一个 BFMatcher
对象。
它有两个可选参数。第一个是 normType
。
它是用来指定要使用的距离测试类型。默认值为 cv2.Norm_L2
。
这很适合 SIFT 和 SURF 等( c2.NORM_L1
也可以)。
对于使用二进制描述符的 ORB,BRIEF,BRISK算法等,要使用 cv2.NORM_HAMMING
,
这样就会返回两个测试对象之间的汉明距离。
如果 ORB 算法的参数设置为 V TA_K==3
或 4,
normType
就应该设置成 cv2.NORM_HAMMING2
。
第二个参数是布尔变量 crossCheck
,默认值为 False。
如果设置为True,匹配条件就会更加严格,
只有到 A 中的第 i 个特征点与 B 中的第 j 个特征点距离最近,
并且 B 中的第 j 个特征点到 A 中的第 i 个特征点也是最近(A 中没有其他点到 j 的距离更近)时才会返回最佳匹配(i,j)。
也就是这两个特征点要互相匹配才行。这样就能提供统一的结果,
这可以用来替代 D.Lowe在 SIFT 文章中提出的比值测试方法。
BFMatcher
对象具有两个方法, BFMatcher.match()
和 BFMatcher.knnMatch()
。
第一个方法会返回最佳匹配。第二个方法为每个关键点返回 k 个最佳匹配(降序排列之后取前 k 个), 其中 k 是由用户设定的。如果除了匹配之外还要做其他事情的话可能会用上(比如进行比值测试)。
就像使用 cv2.drawKeypoints()
绘制关键点一样,
可以使用 cv2.drawMatches()
来绘制匹配的点。
它会将这两幅图像先水平排列,
然后在最佳匹配的点之间绘制直线(从原图像到目标图像)。
如果前面使用的是 BFMatcher.knnMatch()
,
现在可以使用函数 cv2.drawMatchsKnn
为每个关键点和它的 k 个最佳匹配点绘制匹配线。
如果 k 等于 2,就会为每个关键点绘制两条最佳匹配直线。
如果要选择性绘制话就要给函数传入一个掩模。
分别看一个 ORB 和一个 SURF 的例子吧。 (使用不同距离计算方法)。
对 ORB 描述符进行蛮力匹配
现在看一个在两幅图像之间进行特征匹配的简单例子。
在本例中有一个查询图像和一个目标图像。
要使用特征匹配的方法在目标图像中寻找查询图像的位置。
(这两幅图像分别是 /sample/c/box.png
,和 /sample/c/box_in_scene.png
)
使用 ORB 描述符来进行特征匹配。 首先需要加载图像计算描述符。
import numpy as np
import cv2
from matplotlib import pyplot as plt
img1 = cv2.imread('box.png',0) # queryImage
img2 = cv2.imread('box_in_scene.png',0) # trainImage
# Initiate SIFT detector
orb = cv2.ORB()
# find the keypoints and descriptors with SIFT
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) Cell In[4], line 2 1 import numpy as np ----> 2 import cv2 3 from matplotlib import pyplot as plt 5 img1 = cv2.imread('box.png',0) # queryImage ModuleNotFoundError: No module named 'cv2'
下面要创建一个 BFMatcher
对象,并将距离计算设置为 cv2.NORM_HAMMING
(因为使用的是 ORB),
并将 crossCheck
设置为 True。然后使用 Matcher.match()
方法获得两幅图像的最佳匹配。
然后将匹配结果按特征点之间的距离进行降序排列,这样最佳匹配就会排在前面了。
最后只将前 10 个匹配绘制出来(太多了看不清,如果愿意的话可以多画几条)。
# create BFMatcher object
bf = cv2.BFMatcher(cv2.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 = cv2.drawMatches(img1,kp1,img2,kp2,matches[:10], flags=2)
plt.imshow(img3),plt.show()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[5], line 2 1 # create BFMatcher object ----> 2 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) 4 # Match descriptors. 5 matches = bf.match(des1,des2) NameError: name 'cv2' is not defined
下面就是得到的结果:
匹配器对象是什么?
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
from matplotlib import pyplot as plt
img1 = cv2.imread('box.png',0) # queryImage
img2 = cv2.imread('box_in_scene.png',0) # trainImage
# Initiate SIFT detector
sift = cv2.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 = cv2.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])
# cv2.drawMatchesKnn expects list of lists as matches.
img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,good,flags=2)
plt.imshow(img3),plt.show()
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) Cell In[6], line 2 1 import numpy as np ----> 2 import cv2 3 from matplotlib import pyplot as plt 5 img1 = cv2.imread('box.png',0) # queryImage ModuleNotFoundError: No module named 'cv2'
结果如下:
FLANN 匹配器
FLANN 是快速最近邻搜索包(Fast_Library_for_Approximate_Nearest_Neighbors)的简称。 它是一个对大数据集和高维特征进行最近邻搜索的算法的集合,而且这些算法都已经被优化过了。 在面对大数据集时它的效果要好于 BFMatcher。
看看第二个例子使用 FLANN 匹配的效果。
使用 FLANN 匹配,需要传入两个字典作为参数。 这两个用来确定要使用的算法和其他相关参数等。 第一个是 IndexParams。各种不同算法的信息可以在 FLANN 文档中找到。 这里总结一下,对于 SIFT 和 SURF 等,可以传入的参数是:
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
但使用 ORB 时,要传入的参数如下。注释掉的值是文献中推荐使用的, 但是它们并不适合所有情况,其他值的效果可能会更好。
index_params= dict(algorithm = FLANN_INDEX_LSH,
table_number = 6, # 12
key_size = 12, # 20
multi_probe_level = 1) #2
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[7], line 1 ----> 1 index_params= dict(algorithm = FLANN_INDEX_LSH, 2 table_number = 6, # 12 3 key_size = 12, # 20 4 multi_probe_level = 1) #2 NameError: name 'FLANN_INDEX_LSH' is not defined
第二个字典是 SearchParams
。用它来指定递归遍历的次数。值越高结果越准确,
但是消耗的时间也越多。如果想修改这个值,传入参数:
search p arams = dict(checks = 100)
。
有了这些信息,就可以开始了。
import numpy as np
import cv2
from matplotlib import pyplot as plt
img1 = cv2.imread('box.png',0) # queryImage
img2 = cv2.imread('box_in_scene.png',0) # trainImage
# Initiate SIFT detector
sift = cv2.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 = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50) # or pass empty dictionary
flann = cv2.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 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,matches,None,**draw_params)
plt.imshow(img3,),plt.show()
结果如下: