Oriented FAST and Rotated BRIEF
這篇文章我們將介紹一種新的具有局部不變性的特征 —— ORB特征,從它的名字中可以看出它是對FAST特征點與BREIF特征描述子的一種結合與改進,這個算法是由Ethan Rublee,Vincent Rabaud,Kurt Konolige以及Gary R.Bradski在2011年一篇名為“ORB:An Efficient Alternative to SIFT or SURF”的文章中提出。就像文章題目所寫一樣,ORB是除了SIFT與SURF外一個很好的選擇,而且它有很高的效率,最重要的一點是它是免費的,SIFT與SURF都是有專利的,你如果在商業軟件中使用,需要購買許可。
如果你對FAST特征點與BRIEF特征描述子不了解,請先閱讀以下兩篇文章。本文不打算對它們作詳細的算法說明。
1. ORB的算法原理
ORB特征是將FAST特征點的檢測方法與BRIEF特征描述子結合起來,並在它們原來的基礎上做了改進與優化。
首先,它利用FAST特征點檢測的方法來檢測特征點,然后利用Harris角點的度量方法,從FAST特征點從挑選出Harris角點響應值最大的$N$個特征點。其中Harris角點的響應函數定義為:
$$R=det \boldsymbol{M} - \alpha(trace\boldsymbol{M})^2$$
關於$M$的含義和響應函數的由來可以參考Harris角點檢測這篇文章。
1.1 旋轉不變性
我們知道FAST特征點是沒有尺度不變性的,所以我們可以通過構建高斯金字塔,然后在每一層金字塔圖像上檢測角點,來實現尺度不變性。那么,對於局部不變性,我們還差一個問題沒有解決,就是FAST特征點不具有方向,ORB的論文中提出了一種利用灰度質心法來解決這個問題,灰度質心法假設角點的灰度與質心之間存在一個偏移,這個向量可以用於表示一個方向。對於任意一個特征點$p$來說,我們定義$p$的鄰域像素的矩為:
$$m_{pq} = \sum_{x,y}x^py^qI(x,y)$$
其中$I(x,y)$為點$(x,y)$處的灰度值。那么我們可以得到圖像的質心為:
$$C = \left(\frac{m_{10}}{m_{00}},\frac{m_{01}}{m_{00}}\right)$$
那么特征點與質心的夾角定義為FAST特征點的方向:
$$\theta = arctan(m_{01},m_{10})$$
為了提高方法的旋轉不變性,需要確保$x$和$y$在半徑為$r$的圓形區域內,即$x,y\in [-r,r]$,$r$等於鄰域半徑。
1.2 特征點的描述
ORB選擇了BRIEF作為特征描述方法,但是我們知道BRIEF是沒有旋轉不變性的,所以我們需要給BRIEF加上旋轉不變性,把這種方法稱為“Steer BREIF”。對於任何一個特征點來說,它的BRIEF描述子是一個長度為$n$的二值碼串,這個二值串是由特征點周圍$n$個點對($2n$個點)生成的,現在我們將這$2n$個點$(x_i,y_i),i = 1,2,\cdots,2n$組成一個矩陣$S$
$$S =\begin{pmatrix}x_1&x_2&\cdots&x_{2n}\\y_1&y_2&\cdots&y_{2n}\end{pmatrix} $$
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$即我們在1.2中為特征點求得的主方向。
實際上,我們可以把角度離散化,即把360度分為12份,每一份是30度,然后我們對這個12個角度分別求得一個$S_{\theta}$,這樣我們就創建了一個查找表,對於每一個$\theta$,我們只需查表即可快速得到它的點對的集合$S_{\theta}$。
1.3 解決描述子的區分性
BRIEF令人驚喜的特性之一是:對於$n$維的二值串的每個比特征位,所有特征點在該位上的值都滿足一個均值接近於0.5,而方差很大的高斯分布。方差越大,說明區分性越強,那么不同特征點的描述子就表現出來越大差異性,對匹配來說不容易誤配。但是當我們把BRIEF沿着特征點的方向調整為Steered BRIEF時,均值就漂移到一個更加分散式的模式。可以理解為有方向性的角點關鍵點對二值串則展現了一個更加均衡的表現。而且論文中提到經過PCA對各個特征向量進行分析,得知Steered BRIEF的方差很小,判別性小,各個成分之間相關性較大。
為了減少Steered BRIEF方差的虧損,並減少二進制碼串之間的相關性,ORB使用了一種學習的方法來選擇一個較小的點對集合。方法如下:
首先建立一個大約300k關鍵點的測試集,這些關鍵點來自於PASCAL2006集中的圖像。
對於這300k個關鍵點中的每一個特征點,考慮它的$31\times31$的鄰域,我們將在這個鄰域內找一些點對。不同於BRIEF中要先對這個Patch內的點做平滑,再用以Patch中心為原點的高斯分布選擇點對的方法。ORB為了去除某些噪聲點的干擾,選擇了一個$5\times5$大小的區域的平均灰度來代替原來一個單點的灰度,這里$5\times5$區域內圖像平均灰度的計算可以用積分圖的方法。我們知道$31\times31$的Patch里共有$N = (31-5+1)\times(31-5+1)$個這種子窗口,那么我們要$N$個子窗口中選擇2個子窗口的話,共有$C_N^2$種方法。所以,對於300k中的每一個特征點,我們都可以從它的$31\times31$大小的鄰域中提取出一個很長的二進制串,長度為$M = C_N^2$,表示為$$binArray = [p_1,p_2,\cdots,p_M],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。
2. OpenCV中的ORB
ORB中有很多參數可以設置,在OpenCV中它可以通過ORB來創建一個ORB檢測器。
ORB::ORB(int nfeatures=500, float scaleFactor=1.2f, int nlevels=8, int edgeThreshold=31, int firstLevel=0, int WTA_K=2, int scoreType=ORB::HARRIS_SCORE, int patchSize=31)
下面介紹一下各個參數的含義:
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描述子的特征點鄰域大小。
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/features2d/features2d.hpp> using namespace cv; int main(int argc, char** argv) { Mat img_1 = imread("box.png"); Mat img_2 = imread("box_in_scene.png"); // -- Step 1: Detect the keypoints using STAR Detector std::vector<KeyPoint> keypoints_1,keypoints_2; ORB orb; orb.detect(img_1, keypoints_1); orb.detect(img_2, keypoints_2); // -- Stpe 2: Calculate descriptors (feature vectors) Mat descriptors_1, descriptors_2; orb.compute(img_1, keypoints_1, descriptors_1); orb.compute(img_2, keypoints_2, descriptors_2); //-- Step 3: Matching descriptor vectors with a brute force matcher BFMatcher matcher(NORM_HAMMING); std::vector<DMatch> mathces; matcher.match(descriptors_1, descriptors_2, mathces); // -- dwaw matches Mat img_mathes; drawMatches(img_1, keypoints_1, img_2, keypoints_2, mathces, img_mathes); // -- show imshow("Mathces", img_mathes); waitKey(0); return 0; }
3. 參考資料
[1] Ethan Rublee, Vincent Rabaud, Kurt Konolige, Gary R. Bradski: ORB: An efficient alternative to SIFT or SURF. ICCV 2011: 2564-2571.
[2] 看ORB特征,一些理解和解釋
[3] OpenCV Tutorials