之前我們已經介紹了SIFT算法,以及SURF算法,但是由於計算速度較慢的原因。人們提出了使用ORB來替代SIFT和SURF。與前兩者相比,ORB有更快的速度。ORB在2011年才首次發布。在前面小節中,我們已經提到了ORB算法。ORB算法將基於FAST關鍵點的技術和基於BRIEF描述符的技術相結合,關於FAST和BRIEF相關內容可以參考博客第十四節、FAST角點檢測(附源碼)和第十六節、特征描述符BRIEF(附源碼)。
一 ORB算法原理
ORB算法將FAST特征點的檢測方法和BRIEF特征描述子結合起來,並在它們的基礎上做了改進與優化。
首先,它利用FAST特征點檢測的方法來檢測特征點,然后利用Harris角點的度量方法,從FAST特征點中挑選出Harris角點響應值最大的N個特征點。其中Harris角點的響應函數定義為:
$$R=detM-k(trace(M))^2$$
關於$M$的含義和響應函數可以參考第十一節、Harris角點檢測原理(附源碼)這篇博客。
1、旋轉不變性
在現在生活中,我們從不同的距離,不同的方向、角度、不同的光照條件下觀察一個物體時,物體的大小、形狀,明暗都會有所不同。但是我們仍然可以判斷它是一個物體。理想的特征描述子應該具備這些性質,即在大小、方向、明暗不同的圖像中,同一特征點應具有足夠相似的描述子,稱之為描述子的可復現性。
但是ORB並沒有解決尺度不一致的問題,在OpenCV的ORB實現中采用了圖像金字塔來改善這方面的性能,我們通過構建高斯金字塔,然后在每一層金字塔圖像上檢測角點,來實現尺度不變性。ORB主要解決了BRIEF描述子不具備旋轉不變性的問題,ORB論文種提出了一種利用灰度質心法來解決這個問題,灰度質心法假設角點的灰度與質心之間存在一個偏移,這個向量可以用於表示一個方向。對於任意一個特征點$p$來說,我們定義$p$的鄰域像素的矩為:
$$m_{ij}=\sum\limits_{x=-r}^{r}\sum\limits_{y=-r}^{r}x^iy^jI(x,y)$$
其中$I(x,y)$為點$(x,y)$處的灰度值,$q$為質心,$i,j=0,1$。那么我們可以得到圖像的質心為:
$$C=(\frac{m_{10}}{m_{00}},\frac{m_{01}}{m_{00}})$$
那么特征點與質心的夾角定義為FAST特征點的方向:
$$\theta=arctan(m_{01},m_{10})$$
為了提高算法的旋轉不變性,需要確保$x$和$y$在半徑為$r$的圓形區域內,即$x,y\in[-r,r]$,$r$等於鄰域半徑。
2、特征點的描述
ORB選擇了BRIEF作為特征描述方法,但是我們知道BRIEF不具備旋轉不變性,所以我們要給BRIEF加上旋轉不變性,把這種方法稱為"Steer BRIEF"。對於任何一個特征點來說,它的BRIEF描述子是一個長度為$n$的二值碼串,這個二值碼串是由特征點鄰域$n$個點對生成的,我們現在講這$2n$個點$(x_i,y_i),i=1,2,.....,2n$組成一個矩陣$S$:
$$S=\begin{bmatrix} x_1 & x_2 & ... &x_{2n} \\ y_1 & y_2 & ... & y_{2n} \end{bmatrix}$$
Calonder建議為每個塊的旋轉和投影集合分別計算BRIEF描述子,但代價昂貴。ORB中采用了一個更有效的方法:使用鄰域方向$\theta$和對應的旋轉矩陣$R_{\theta}$,構建$S$的一個校正版本$S_{\theta}$:
$$S_{\theta}=R_{\theta}S$$
其中:
$$R_{\theta}=\begin{bmatrix} cos\theta & sin\theta \\ -sin\theta & cos\theta \end{bmatrix}$$
而$\theta$即我們為特征點求得的主方向。
即我們把坐標軸旋轉$]theta$,計算以主方向為坐標系的匹配點對,如下圖:
實際上,我們可以把角度離散化,即把360度分為12份,每一份是30度,然后我們對這個12個角度分別求得一個$S_{\theta}$,這樣我們就創建了一個查找表,對於每一個$\theta$,我們只需要查表即可快速得到它的點的集合$S_{\theta}$。
3、解決描述子的區分性
BRIEF令人驚喜的特性之一是:對於$n$維的二值串的每個特征位,所有特征點在該位上的值都滿足一個均值接近於0.5,而方差很大的高斯分布。方差越大,說明區分性越強,那么不同特征點的描述子就表現出來越大差異性,對匹配來說不容易誤配。但是當我們把BRIEF沿着特征點的方向調整為Steered BRIEF時,均值就漂移到一個更加分散式的模式。可以理解為有方向性的角點關鍵點對二值串則展現了一個更加均衡的表現。而且論文中提到經過PCA對各個特征向量進行分析,得知Steered BRIEF的方差很小,判別性小,各個成分之間相關性較大。
為了減少Steered BRIEF方差的虧損,並減少二進制碼串之間的相關性,ORB使用了一種學習的方法來選擇一個較小的點對集合。方法如下:
首先建立一個大約300k關鍵點的測試集,這些關鍵點來自於PASCAL2006集中的圖像。
對於這300k個關鍵點中的每一個特征點,考慮它的$31\times{31}$的鄰域,我們將在這個鄰域內找一些點對,不同於BRIEF中要先對這個Patch內的點做平滑,再用以Patch中心為原點的高斯分布選擇點對的方法。ORB為了去除某些噪聲點的干擾,選擇了一個$5\times{5}$大小的區域的平均灰度來代替原來一個單點的灰度,這里$5\times{5}$區域內圖像平均灰度的計算可以用積分圖的方法。我們知道$31\times{31}$的Patch里共有$N=(31-5+1)\times{(31-5+1)}$個這種窗口,那么我們要$N$個子窗口中選擇2個子窗口的話,共有$C_N^2$種方法。所以對於300k中每一個特征點,我們都可以從它的$31\times{31}$大小的鄰域中提取一個很長的二進制串,長度為$M=C_N^2$,表示為:
$$binArray = \begin{bmatrix} p_1 & p_2 & ... &p_M \end{bmatrix},p_i\in\{0,1\} $$
那么當300k個關鍵點全部進行上面的特征提取之后,我們就得到了一個$300k\times{M}$的矩陣,矩陣中的每個元素值為0或者1.
對該矩陣的每個列向量,也就是每個點對在300k個特征點上的測試結果,計算其均值。把所有的列向量按均值進行重新排序。排好后,組成了一個向量$T$,$T$的每一個元素都是一個列向量。
進行貪婪搜索,從$T$中把排在第一的那個列放到$R$中,$T$中就沒有這個點對的測試結果了,然后把$T$中的排在下一個的列與$R$中的所有元素比較,計算它們的相關性,如果相關超過了某一事先設定好的閾值,就扔了它,否則就把它方到$R$里面。重復上面的步驟,直到$R$中有256個列向量位置。如果把$T$全部找完也沒有找到256個,那么我們可以把相關的閾值調高一些,再嘗試一遍。
這樣,我們就得到了256個點對。上面這個過程我們稱它為rBRIEF。
二 OpenCV實現
ORB中有很多參數可以設置,在OpenCV中它可以通過ORB來創建一個ORB檢測器。
cv2.ORB_create([,nfeatues[,scaleFactor[,nlevels[,edgeThreshold[,firstLevel[,WTA_K[,[scoreType,[patchSize,fastThreshold]]]]]]]]])
參數說明:
- nfeatures :最多提取的特征點的數量;
- scaleFactor : 金字塔圖像之間的尺度參數,類似於SIFT中的$k$;
- nlevels: 高斯金字塔的層數;
- edgeThreshold :邊緣閾值,這個值主要是根據后面的patchSize來定的,靠近邊緣edgeThreshold以內的像素是不檢測特征點的。
- firstLevel-:看過SIFT都知道,我們可以指定第一層的索引值,這里默認為0。
- WET_K : 用於產生BIREF描述子的點對的個數,一般為2個,也可以設置為3個或4個,那么這時候描述子之間的距離計算就不能用漢明距離了,而是應該用一個變種。OpenCV中,如果設置WET_K = 2,則選用點對就只有2個點,匹配的時候距離參數選擇NORM_HAMMING,如果WET_K設置為3或4,則BIREF描述子會選擇3個或4個點,那么后面匹配的時候應該選擇的距離參數為NORM_HAMMING2。
- scoreType :用於對特征點進行排序的算法,你可以選擇HARRIS_SCORE,也可以選擇FAST_SCORE,但是它也只是比前者快一點點而已。
- patchSize :用於計算BIREF描述子的特征點鄰域大小。
# -*- coding: utf-8 -*- """ Created on Mon Sep 10 22:37:36 2018 @author: zy """ ''' ORB特征匹配 ''' import numpy as np import cv2 def orb_test(): #加載圖片 灰色 img1 = cv2.imread('./image/orb1.jpg') gray1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY) img2 = cv2.imread('./image/orb2.jpg') img2 = cv2.resize(img2,dsize=(450,300)) gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY) image1 = gray1.copy() image2 = gray2.copy() ''' 1.使用ORB算法檢測特征點、描述符 ''' orb = cv2.ORB_create(128) keypoints1,descriptors1 = orb.detectAndCompute(image1,None) keypoints2,descriptors2 = orb.detectAndCompute(image2,None) #在圖像上繪制關鍵點 image1 = cv2.drawKeypoints(image=image1,keypoints = keypoints1,outImage=image1,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) image2 = cv2.drawKeypoints(image=image2,keypoints = keypoints2,outImage=image2,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) #顯示圖像 cv2.imshow('orb_keypoints1',image1) cv2.imshow('orb_keypoints2',image2) cv2.waitKey(20) ''' 2、匹配 ''' matcher = cv2.BFMatcher_create(cv2.HAMMING_NORM_TYPE,crossCheck=True) matchePoints = matcher.match(descriptors1,descriptors2) print(type(matchePoints),len(matchePoints),matchePoints[0]) #按照距離從小到大排序,選取最優匹配的 sorted(matchePoints,key=lambda x:x.distance) #繪制最優匹配點 outImg = None outImg = cv2.drawMatches(img1,keypoints1,img2,keypoints2,matchePoints[:10],outImg,matchColor=(0,255,0),flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT) cv2.imshow('matche',outImg) cv2.waitKey(0) cv2.destroyAllWindows() cv2.waitKey(0) cv2.destroyAllWindows() if __name__ == '__main__': orb_test()
參考文章: