利用ORB/AKAZE特征点进行图像配准
Kp1,kp2都是list類型,兩幅圖都是500個特征點。這和ORB論文中的數據是一樣的。4.4章節
Matches也是list類型,找到325個匹配對。
AKAZE文章中提到一個指標:MS(matching score)=# Correct Matches/# Features,
如果overlap area error 小于40%并且經矩陣變換后兩個對應像素距離小于2.5個像素,就說明是correct match。這里的overlap area error沒有搞懂,應該在Mikolajczyk的A comparison of affine region dectors中找到答案。
還沒搞清楚的還有:代碼中的matches指的應該是correspondence,因為ORB論文中有一句perform brute-force matching to find the best correspondence.代碼中使用的正是BFMatcher。但是文章接下來給出的曲線圖是The results are given in terms of the percentage of correct matches, against the angle of rotation.
the percentage of correctmatches應該等效于圖上的縱坐標percentage of Inliers。那么percentage of和Inliers指的都應該是經過RANSAC提純之后的匹配對。ORB文章中沒有提到提純,即剔除誤匹配對的過程。
img3 = drawMatches(img1, kp1, img2, kp2, matches[:50])#找到50個匹配對
在繪制匹配對的時候,代碼直接對匹配器中的50個進行連線。但是看到其他代碼中還有一個排序的過程:
matches=sorted(matches,key=lambda x:x.distance)首先,Python中有兩種排序方法,一種是sort,一種是Sorted。
L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*
Sort默認升序排序。要想顛倒排序,可將默認的False改為True。In place說明sort是就地排序,直接改變原來list
sorted(iterable, cmp=None, key=None, reverse=False) --> new sorted list
sorted會新建一個list,用來存放排序之后的數據
cmp:用于比較的函數,比較什么由key決定,有默認值,迭代集合中的一項;
key:用列表元素的某個屬性和函數進行作為關鍵字,有默認值,迭代集合中的一項;
cmp和key可以使用lambda表達式。
可以理解為是cmp按照key的比較結果排序,而key直接按照參數排序。因為列表中的元素很可能是元組。簡單來說,lambda返回冒號之后的值。下面兩種方法是等價的
Sorting? cmp:
>>>L = [('b',2),('a',1),('c',3),('d',4)]
>>>print?sorted(L,cmp=lambda x,y:cmp(x[1],y[1]))
[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
Sorting? keys:
>>>L = [('b',2),('a',1),('c',3),('d',4)]
>>>print?sorted(L, key=lambda x:x[1]))
[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
Lamdba是匿名函數,有函數體,沒有函數名,常用作回調函數的值。函數對象能維護狀態,但語法開銷大,而函數指針語法開銷小,卻沒法保存范圍內的狀態。lambda函數結合了兩者的優點。
Lambda還經常和Reduce函數放在一起使用。Reduce函數其實是一個具有遞歸作用的二元函數,對集合中的前兩個執行函數,得到的結果再與第三個元素做運算,以此類推。
Reduce(lambda x,y:x*y,range(1,n+1))? #n的階乘
sum=reduce(lambda x,y:x+y,(1,2,3,4,5,6,7))?? #1+2+…+7
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)#暴力匹配,True判斷交叉驗證先使用cv.BFMatcher創造一個BfMatcher的對象。第一個參數normType,規定了匹配時使用的距離準則,默認是cv2.NORM_L2歐式距離,適用于SIFT、SURF等。對于二進制字符串構成的描述子如ORB、BRIEF、BRISK等,使用cv2.NORM_HAMMING漢明距離。漢明距離可以通過異或快速求解,這也是算法加速的原因。最后,If ORB is using WTA_K == 3 or 4, cv2.NORM_HAMMING2 should be used.
第二個參數crossCheck,是布爾型參數,默認為false。如果改為True,就開啟了交叉驗證,我理解的就是雙向匹配,只有雙向匹配成功的匹配對才被返回。數學上講,有兩個集合A和B,對于集合A中的i,在B中找到的最近點是j,如果對于j,在A中搜索,找到的最近點也是i,那么他們就驗證成功。如下圖,黃色的三角形在圓的集合中找到的最近點是綠色的圓。但是對于綠圓,最近的點的粉色的三角,即交叉驗證失敗。就像是找對象,必須男女雙方互相看對眼才能牽手成功。當然,我們可以直接比較兩個距離的大小,距離小于某個閾值認為匹配正確,SIFT則提出了最近鄰和次近鄰的比值與閾值比較。
It provides consistant result, and is a good alternative to ratio test proposed by D.Lowe in SIFT paper.手冊上說這種交叉驗證的方法可以是SIFT文章所提的最近鄰比率法的一個很好的代替。
在創建BfMatcher的對象之后,有兩種匹配方法,BFMatcher.match() and BFMatcher.knnMatch()。第一種返回的是最好的匹配對,第二種是返回k個最好的匹配對,k的取值由用戶定義。兩種匹配對對應的連線方法cv2.drawMatches()和cv2.drawMatchesKnn。這里再記錄一下drawmatches的參數
void cv::drawMatches ( InputArray img1,const std::vector< KeyPoint > & keypoints1,InputArray img2,const std::vector< KeyPoint > & keypoints2,const std::vector< DMatch > & matches1to2,InputOutputArray outImg,const Scalar & matchColor = Scalar::all(-1),const Scalar & singlePointColor = Scalar::all(-1),const std::vector< char > & matchesMask = std::vector< char >(),int flags = DrawMatchesFlags::DEFAULT )前四個參數分別是兩幅圖像和各自的特征點,matches1to2表明匹配的方向是從圖1到圖2,keypoints1[i] has a corresponding point in keypoints2[matches[i]] .
outImg是輸出的圖像。
matchColor決定了連線和特征點的顏色,如果matchColor==Scalar::all(-1) ,顏色隨機生成。
singlePointColor決定了沒有對應匹配對的孤立特征點的顏色
matchesMask決定哪些匹配對被畫出,如果掩膜為空,則所有匹配對都被畫出
flags是所畫特征點的標志位,? DEFAULT = 0,默認會畫出所有特征點
BFMatcher.knnMatch()主要用于配合SIFT文章中的最近鄰比率法使用。當最近的距離小于次近鄰的0.75時(這個閾值的取值很重要,0.75出自于OpenCV手冊,Lowe的論文中選取0.7),認為是正確匹配對。手冊中是以SIFT算法為例展示了knn的用法,實際在ORB中也可以使用Knn和近鄰比率法進行配對。
比率法在尋找最近距離的過程中綜合考慮了次近鄰,這樣,只有當點很明顯是最接近的情況下才會認為配對。對于一些疑似是對應點,但疑似情況差不多的點就不再認為是匹配點,即便他們與當前特征點的絕對距離都很近。
由KNN算法還可以引入kd數的構建和BBF算法,更快地在搜索空間中尋找k個近鄰點。OpenCV中也集成了一個快速搜索最近鄰的庫FLANN(Fast Library for Approximate Nearest Neighbors),可以在特征點的描述子的維度高,特征點數目大的情況下依然快速找到最近鄰點。寫到這里可以注意到,當KNN中的K取1時就是最近鄰。所以之前的BFMatcher中的BFMatcher.match() and BFMatcher.knnMatch()都可以通過FLANN實現。
基于FLANN的檢測器主要有兩個字典類型的參數,index_params和search_params。
#index_params與所選取的特征點檢測算法有關index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)#kd樹,對應SIFT、SURFindex_params= dict(algorithm = FLANN_INDEX_LSH, table_number = 6, key_size = 12, # 20multi_probe_level = 1) #2 #LSH局部敏感哈希,對應ORB第二個參數search_params決定了迭代的次數。It specifies the number of times the trees in the index should be recursively traversed.迭代次數越多,準確度越高,但是耗時當然也越長。
Index參數和search參數定義好之后就可以創建搜索器并進行匹配了。
# FLANN parameters 16 FLANN_INDEX_KDTREE = 0 17 index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) 18 search_params = dict(checks=50) # or pass empty dictionary 19 20 flann = cv2.FlannBasedMatcher(index_params,search_params) 21 #根據描述子進行k=2的knn搜索 22 matches = flann.knnMatch(des1,des2,k=2) 23 24 # Need to draw only good matches, so create a mask #xrange是一個生成器。這里對matches進行標記,初始化為0.注意每一個match有最近和次近鄰兩個匹配點 25 matchesMask = [[0,0] for i in xrange(len(matches))] 26 27 # ratio test as per Lowe's paper #對于最近鄰m和次近鄰n,如果最近距離小于次近鄰的0.7,那么就將m標記成1,從而得到0,1組成的掩膜 28 for i,(m,n) in enumerate(matches): 29 if m.distance < 0.7*n.distance: 30 matchesMask[i]=[1,0] 31 32 draw_params = dict(matchColor = (0,255,0), 33 singlePointColor = (255,0,0), 34 matchesMask = matchesMask, 35 flags = 0)#利用掩膜,只畫出最好的匹配的對 36 37 img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,matches,None,**draw_params)Matches是list類型的,里面存放了DMatch類型的匹配對,每個DMatch有4個成員變量:distance(float)、imgIdx(int)、queryIdx(int)、trainIdx(int),表示了匹配對中特征點的距離,索引等信息。query是要匹配的描述子,train是被匹配的描述子
這是k=2時KNN的輸出結果,對于每一個query,會匹配前兩個最近鄰的特征點。List中的元素是list,每一個list元素由兩個DMatch構成。
經過最近鄰比率法提純后:
可以看到,直到query的第67個點才達成第一個正確匹配。
利用經過最近鄰比率法匹配得到的匹配對,得到新的描述子,再通過新的描述子尋找單應矩陣,尋找單應矩陣的過程中又可以使用RANSAC等方法提純。
遇到的問題是函數cv2.findHomography的第一第二個參數要求是array或者scalar類型的,srcPoints Coordinates of the points in the original plane, a matrix of the type CV_32FC2.?? or vector\<Point2f\> .。
python中的list是python的內置數據類型,list中的數據類不必相同的,而array的中的類型必須全部相同,the type of objects stored in them is constrained,The type is specified at object creation time by using a type code, which is a single character.。在list中的數據類型保存的是數據的存放的地址,簡單的說就是指針,并非數據,這樣保存一個list就太麻煩了,例如list1=[1,2,3,'a']需要4個指針和四個數據,增加了存儲和消耗cpu。
des1是ndarray類型的,包含383個特征點,每個特征點的描述子的維度是61(AKAZE),所以是一個383x61大小的矩陣。
忙活了半天把初始描述子中屬于good的部分提取出來并轉化成ndarray格式,卻發現cv2.findHomography的前兩個參數是特征點,而不是特征點的描述子,并且OpenCV又已經在鏈接5中給出了示例。
這是最終的圖像配準代碼:
#!/usr/bin/env.python #coding=utf-8 ## coding=gbk 支持中文注釋 import numpy as np import cv2 import math import logging orig_image=cv2.imread("beaver.png",0) skewed_image=cv2.imread("beaver_xform.png",0)cv2.imshow("graf1",orig_image) cv2.imshow("graf2",skewed_image)#定義函數 def mydrawMatches(orig_image, kp1, skewed_image, kp2, matches):rows1=orig_image.shape[0]#height(rows) of imagecols1=orig_image.shape[1]#width(colums) of image#shape[2]#the pixels value is made up of three primary colorsrows2=skewed_image.shape[0]cols2=skewed_image.shape[1]#初始化輸出的新圖像,將兩幅實驗圖像拼接在一起,便于畫出特征點匹配對out=np.zeros((max([rows1,rows2]),cols1+cols2,3),dtype='uint8')out[:rows1,:cols1] = np.dstack([orig_image, orig_image, orig_image])#Python切片特性,初始化out中orig_image,skewed_image的部分out[:rows2,cols1:] = np.dstack([skewed_image, skewed_image, skewed_image])#dstack,對array的depth方向加深for mat in matches:orig_image_idx=mat.queryIdxskewed_image_idx=mat.trainIdx(x1,y1)=kp1[orig_image_idx].pt(x2,y2)=kp2[skewed_image_idx].ptcv2.circle(out,(int(x1),int(y1)),4,(255,255,0),1)#藍綠色點,半徑是4cv2.circle(out, (int(x2) + cols1, int(y2)), 4, (0, 255, 255), 1)#綠加紅得黃色點cv2.line(out, (int(x1), int(y1)), (int(x2) + cols1, int(y2)), (255, 0, 0), 1)#藍色連線return out# 檢測器。ORB AKAZE可以,KAZE需要將BFMatcher中漢明距離換成cv2.NORM_L2 detector = cv2.AKAZE_create()#kp1 = detector.detect(orig_image, None) #kp2 = detector.detect(skewed_image, None) #kp1, des1 = detector.compute(orig_image, kp1)#計算出描述子 #kp2, des2 = detector.compute(skewed_image, kp2)#也可以一步直接同時得到特征點和描述子 # find the keypoints and descriptors with ORBkp1, des1 = detector.detectAndCompute(orig_image, None) kp2, des2 = detector.detectAndCompute(skewed_image, None)bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)#暴力匹配,True判斷交叉驗證 matches = bf.match(des1, des2)#利用匹配器得到相近程度 matches=sorted(matches,key=lambda x:x.distance)#按照描述子之間的距離進行排序 img3=cv2.drawMatches(orig_image, kp1, skewed_image, kp2, matches[:50],None,(255, 0, 0),flags=2)#找到50個匹配對# Apply ratio test bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)#為了得到最近鄰和次近鄰,這里交叉驗證要設置為false matches = bf.knnMatch(des1,des2, k=2) good = [] #lenofgood=0 for m, n in matches:if m.distance < 0.75 * n.distance:good.append([m])#good.append(m)#lenofgood=lenofgood+1#matches2 = sorted(good, key=lambda x: x.distance) # 按照描述子之間的距離進行排序# cv2.drawMatchesKnn expects list of lists as matches. #img4 = cv2.drawMatches(orig_image, kp1, skewed_image, kp2, matches2[:50],None, flags=2) #img4 = cv2.drawMatchesKnn(orig_image, kp1, skewed_image, kp2, good[:50],None, flags=2) img4 = cv2.drawMatchesKnn(orig_image, kp1, skewed_image, kp2, good,None, flags=2) lenofgood=len(good)#good長度為24des1fromgood=np.ones((lenofgood,61),dtype=np.uint8) #des2fromgood=[] des2fromgood=np.ones((lenofgood,61),dtype=np.uint8) for num in range(0,24):for i in good:for j in i:#good是list組成的list,每個子list中是DMatchdes1fromgood[num]=des1[j.queryIdx]#將good中的特征點對應的描述子形成新的描述子#des2fromgood.append(des2[j.trainIdx])des2fromgood[num] = des2[j.trainIdx]cv2.imwrite("AKAZETest.jpg", img3) cv2.imshow('AKAZETest', img3) cv2.imwrite("AKAZETestRatio.jpg", img4) cv2.imshow('AKAZETestRatio', img4)#H,mask=cv2.findHomography(des1fromgood,des2fromgood,cv2.RANSAC,5.0)#,None,None,None,None,None) good = [] for m, n in matches:if m.distance < 0.7 * n.distance:good.append(m) MIN_MATCH_COUNT = 10 if len(good) > MIN_MATCH_COUNT:src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)ss = M[0, 1]sc = M[0, 0]scaleRecovered = math.sqrt(ss * ss + sc * sc)thetaRecovered = math.atan2(ss, sc) * 180 / math.pi#logging打印出通過矩陣計算出的尺度縮放因子和旋轉因子LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMAT)#配置之后將log文件保存到本地logging.info("MAP: Calculated scale difference: %.2f, ""Calculated rotation difference: %.2f" %(scaleRecovered, thetaRecovered))# deskew imageim_out = cv2.warpPerspective(skewed_image, np.linalg.inv(M),(orig_image.shape[1], orig_image.shape[0]))result=cv2.warpPerspective(orig_image,M,(skewed_image.shape[1],skewed_image.shape[0]))#注意warpPerspective與warpAffine的區別,不能互換 #img_warp=cv2.perspectiveTransform(orig_image,M)#,img_warp) #c.shape[1] 為第一維的長度,c.shape[0] 為第二維的長度。 cv2.imshow("fan透視變換",im_out) cv2.imshow("透視變換",result) cv2.waitKey(0)reference:
總結
以上是生活随笔為你收集整理的利用ORB/AKAZE特征点进行图像配准的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 吴恩达作业8:三层神经网络实现手势数字的
- 下一篇: Fast R-CNN整体把握