1. 程式人生 > >影象特徵描述子之ORB

影象特徵描述子之ORB

  ORB(Oriented FAST and Rotated BRIEF)演算法是對FAST特徵點檢測和BRIEF特徵描述子的一種結合,在原有的基礎上做了改進與優化,使得ORB特徵具備多種區域性不變性,併為實時計算提供了可能。

特徵點檢測

  ORB首先利用FAST演算法檢測特徵點,然後計算每個特徵點的Harris角點響應值,從中篩選出N個最大的特徵點,Harris角點的響應函式如下:

R=detMα(traceM)2
相關內容已在博文FAST角點檢測Harris角點檢測分別做了詳細的介紹。
FAST檢測特徵點不具備尺度不變性,可以像SIFT特徵一樣,藉助尺度空間理論構建影象高斯金字塔,然後在每一層金字塔影象上檢測角點,以實現尺度不變性。對於旋轉不變性,原論文中提出了一種利用影象矩(幾何矩),在半徑為r的鄰域內求取灰度質心的方法,從特徵點到灰度質心的向量,定義為該特徵點的主方向。影象矩定義如下:
m
pq
=Σx,yxpyqI(x,y),x,y[r,r]

I(x,y)表示畫素灰度值,0階矩m00即影象鄰域視窗內所有畫素的灰度和,m10m01分別相對x和相對y的一階矩,因此影象區域性鄰域的中心矩或者質心可定義為
C=(m10m00,m01m00)
特徵點與質心形成的向量與X軸的夾角定義為特徵點的主方向
θ=arctan(m01,m10)

特徵點描述

  ORB採用BRIEF作為特徵描述方法,BRIEF雖然速度優勢明顯,但也存在一些缺陷,例如不具備尺度不變性和旋轉不變性,對噪聲敏感。尺度不變性的問題在利用FAST檢測特徵點時,通過構建高斯金字塔得以解決。BRIEF中採用9×9的高斯卷積核進行濾波降噪,可以在一定程度上緩解噪聲敏感問題;ORB中利用積分影象,在31

×31的Patch中選取隨機點對,並以選取的隨機點為中心,在5×5的視窗內計算灰度平均值(灰度和),比較隨機點對的鄰域灰度均值,進行二進位制編碼,而不是僅僅由兩個隨機點對的畫素值決定編碼結果,可以有效地解決噪聲問題。
  至於旋轉不變性問題,可利用FAST特徵點檢測時求取的主方向,旋轉特徵點鄰域,但旋轉整個Patch再提取BRIEF特徵描述子的計算代價較大,因此,ORB採用了一種更高效的方式,在每個特徵點鄰域Patch內,先選取256對隨機點,將其進行旋轉,然後做判決編碼為二進位制串。n個點對構成矩陣S

S=[x1y1x2y2x2ny2n]
旋轉矩陣Rθ
Rθ=[cosθsinθ
sinθcosθ
]

旋轉後的座標矩陣為
Sθ=RθS

描述子的區分性

  通過上述方法得到的特徵描述子具有旋轉不變性,稱為steered BRIEF(sBRIEF),但匹配效果卻不如原始BRIEF演算法,因為可區分性減弱了。特徵描述子的一個要求就是要儘可能地表達特徵點的獨特性,便於區分不同的特徵點。如下圖所示,為幾種特徵描述子的均值分佈,橫軸為均值與0.5之間的距離,縱軸為相應均值下特徵點的統計數量。可以看出,BRIEF描述子所有位元位的均值接近於0.5,且方差很大;方差越大表明可區分性越好。不同特徵點的描述子表現出較大的差異性,不易造成無匹配。但steered BRIEF進行了座標旋轉,損失了這個特性,導致可區分性減弱,相關性變強,不利於匹配。


sBRIEF-rBRIEF.jpg

  為了解決steered BRIEF可區分性降低的問題,ORB使用了一種基於學習的方法來選擇一定數量的隨機點對。首先建立一個大約300k特徵點的資料集(特徵點來源於PASCAL2006中的影象),對每個特徵點,考慮其31×31的鄰域Patch,為了去除噪聲的干擾,選擇5×5的子視窗的灰度均值代替單個畫素的灰度,這樣每個Patch內就有N=(315+1)×(315+1)=27×27=729個子視窗,從中隨機選取2個非重複的子視窗,一共有M=C2N中方法。這樣,每個特徵點便可提取出一個長度為M的二進位制串,所有特徵點可構成一個300k×M的二進位制矩陣Q,矩陣中每個元素取值為0或1。現在需要從M個點對中選取256個相關性最小、可區分性最大的點對,作為最終的二進位制編碼。篩選方法如下:
- 對矩陣Q的每一列求取均值,並根據均值與0.5之間的距離從小到大的順序,依次對所有列向量進行重新排序,得到矩陣T
- 將T中的第一列向量放到結果矩陣R
- 取出T中的下一列向量,計算其與矩陣R中所有列向量的相關性,如果相關係數小於給定閾值,則將T中的該列向量移至矩陣R中,否則丟棄
- 迴圈執行上一步,直到R中有256個列向量;如果遍歷T中所有列,R中向量列數還不滿256,則增大閾值,重複以上步驟。

這樣,最後得到的就是相關性最小的256對隨機點,該方法稱為rBRIEF。

Experiment & Result

OpenCV實現ORB特徵檢測與描述

#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; 
}

result.png

OpenCV中ORB演算法的部分原始碼實現

//計算Harris角點響應  
static void HarrisResponses(const Mat& img, vector<KeyPoint>& pts, int blockSize, float harris_k)  
{  
    CV_Assert( img.type() == CV_8UC1 && blockSize*blockSize <= 2048 );  

    size_t ptidx, ptsize = pts.size();  

    const uchar* ptr00 = img.ptr<uchar>();  
    int step = (int)(img.step/img.elemSize1());  
    int r = blockSize/2;  

    float scale = (1 << 2) * blockSize * 255.0f;  
    scale = 1.0f / scale;  
    float scale_sq_sq = scale * scale * scale * scale;  

    AutoBuffer<int> ofsbuf(blockSize*blockSize);  
    int* ofs = ofsbuf;  
    for( int i = 0; i < blockSize; i++ )  
        for( int j = 0; j < blockSize; j++ )  
            ofs[i*blockSize + j] = (int)(i*step + j);  

    for( ptidx = 0; ptidx < ptsize; ptidx++ )  
    {  
        int x0 = cvRound(pts[ptidx].pt.x - r);  
        int y0 = cvRound(pts[ptidx].pt.y - r);  

        const uchar* ptr0 = ptr00 + y0*step + x0;  
        int a = 0, b = 0, c = 0;  

        for( int k = 0; k < blockSize*blockSize; k++ )  
        {  
            const uchar* ptr = ptr0 + ofs[k];  
            int Ix = (ptr[1] - ptr[-1])*2 + (ptr[-step+1] - ptr[-step-1]) + (ptr[step+1] - ptr[step-1]);  
            int Iy = (ptr[step] - ptr[-step])*2 + (ptr[step-1] - ptr[-step-1]) + (ptr[step+1] - ptr[-step+1