OpenCV-Python系列之特征匹配
之前我们讨论过了诸多的特征检测算法,这次我们来讨论如何运用相关的方法进行特征匹配。本次教程完全为实战教程,没有相关的算法原理介绍,大家可以轻松一下了。
蛮力匹配(ORB匹配)
Brute-Force匹配非常简单,首先在第一幅图像中选择一个关键点然后依次与第二幅图像的每个关键点进行(改变)距离测试,最后返回距离最近的关键点。
对于BF匹配器,首先我们必须使用cv2.BFMatcher()创建BFMatcher对象。它需要两个可选的参数。
1. 第一个是normType,它指定要使用的距离测量,或在其他情况下,它是cv2.NORM_L2。它适用于SIFT,SURF等(cv2.NORM_L1也在那里)。对于基于二进制串行的替代,如ORB,BRIEF,BRISK等,应使用cv2.NORM_HAMMING,使用汉明距离作为度量,如果ORB使用WTA_K == 3or4,则应使用cv2.NORM_HAMMING2。
2. crossCheck:最小数值为假。如果设置为True,匹配条件就会更加严格,只有到A中的第i个特征点与B中的第j个特征点距离最近,并且B中的第j个特征点到A中的第i个特征点也是最近时才会返回最佳匹配,即这两个特征点要互相匹配才行。
两个重要的方法是BFMatcher.match()和BFMatcher.knnMatch(),第一个返回最佳匹配,第二种方法返回k个最佳匹配,其中k由用户指定。
使用cv2.drawMatches()来对齐匹配的点,它可以将两幅图像先行水平划分,然后在最佳匹配的点之间对齐直线。如果前面使用的BFMatcher.knnMatch(),现在可以使用函数cv2.drawMatchsKnn为每个关键点和它的一个最佳匹配如果要选择性替换就要给函数重新定义一个指针。
我们来看代码:
def BruteForce(img1,img2): # Initiate ORB detector orb = cv2.ORB_create() # find the keypoints and descriptors with ORB kp1, des1 = orb.detectAndCompute(img1, None) kp2, des2 = orb.detectAndCompute(img2, None) # 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], None, flags=2) plt.imshow(img3), plt.show()
输出:
SIFT的特征匹配
关于SIFT的概念我们之前已经讨论过,现在来看相关的实战代码:
def SIFT(img1, img2): # Initiate SIFT detector sift = cv2.xfeatures2d.SIFT_create() # 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.6 * n.distance: good.append([m]) # cv.drawMatchesKnn expects list of lists as matches. img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2) plt.imshow(img3), plt.show()
结果:
事实上我们可以看到,与蛮力匹配相比,SIFT算法的特征匹配是十分强大的,效果显而易见。
SURF的特征匹配
def SURF(img1, img2): # Initiate SIFT detector surf = cv2.xfeatures2d.SURF_create() # find the keypoints and descriptors with SIFT kp1, des1 = surf.detectAndCompute(img1, None) kp2, des2 = surf.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.6 * n.distance: good.append([m]) # cv.drawMatchesKnn expects list of lists as matches. img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2) plt.imshow(img3), plt.show()
SIFT算法的特征匹配相比较SURF来说,基本上效果差不多,但是速度不同。
基于FLANN的匹配器
FLANN代表近似最近邻居的快速库。它包含一组算法,这些算法针对大型数据集中的快速最近邻搜索和高维特征进行了优化。对于大型数据集,它比BFMatcher工作得更快。
代码:
def FLANN(img1, img2): # Initiate SIFT detector sift = cv2.xfeatures2d.SIFT_create() # 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 = 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 range(len(matches))] # ratio test as per Lowe's paper for i, (m, n) in enumerate(matches): if m.distance < 0.6 * 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()
输出结果:
FLANN属于单应性匹配,单应性指的是图像在投影发生了畸变后仍然能够有较高的检测和匹配准确率,它可以大概率上避免旋转和放缩带来的影响。