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屬於單應性匹配,單應性指的是影象在投影發生了畸變後仍然能夠有較高的檢測和匹配準確率,它可以大概率上避免旋轉和放縮帶來的影響。
天道酬勤 循序漸進 技壓群雄