BRISK特征提取算法


簡介

        BRISK算法是2011年ICCV上《BRISK:Binary Robust Invariant Scalable Keypoints》文章中,提出來的一種特征提取算法,也是一種二進制的特征描述算子。

       它具有較好的旋轉不變性、尺度不變性,較好的魯棒性等。在圖像配准應用中,速度比較:SIFT<SURF<BRISK<FREAK<ORB,在對有較大模糊的圖像配准時,BRISK算法在其中表現最為出色。

BRISK算法

特征點檢測

        BRISK算法主要利用FAST9-16進行特征點檢測(為什么是主要?因為用到一次FAST5-8),可參見博客:FAST特征點檢測算法。要解決尺度不變性,就必須在尺度空間進行特征點檢測,於是BRISK算法中構造了圖像金字塔進行多尺度表達。

建立尺度空間

        構造n個octave層(用ci表示)和n個intra-octave層(用di表示),文章中n=4,i={0,1,...,n-1}。假設有圖像img,octave層的產生:c0層就是img原圖像,c1層是c0層的2倍下采樣,c2層是c1層的2倍下采樣,以此類推。intra-octave層的產生:d0層是img的1.5倍下采樣,d1層是d0層的2倍下采樣(即img的2*1.5倍下采樣),d2層是d1層的2倍下采樣,以此類推。

則ci、di層與原圖像的尺度關系用t表示為:

ci、di層與原圖像大小關系為:

        由於n=4,所以一共可以得到8張圖,octave層之間尺度(縮放因子)是2倍關系,intra-octave層之間尺度(縮放因子)也是2倍關系。

特征點檢測

        對這8張圖進行FAST9-16角點檢測,得到具有角點信息的8張圖,對原圖像img進行一次FAST5-8角點檢測(當做d(-1)層,虛擬層),總共會得到9幅有角點信息的圖像。

非極大值抑制

        對這9幅圖像,進行空間上的非極大值抑制(同SIFT算法的非極大值抑制):特征點在位置空間(8鄰域點)和尺度空間(上下層2x9個點),共26個鄰域點的FAST的得分值要最大,否則不能當做特征點;此時得到的極值點還比較粗糙,需要進一步精確定位。

亞像素插值

        進過上面步驟,得到了圖像特征點的位置和尺度,在極值點所在層及其上下層所對應的位置,對FAST得分值(共3個)進行二維二次函數插值(x、y方向),得到真正意義上的得分極值點及其精確的坐標位置(作為特征點位置);再對尺度方向進行一維插值,得到極值點所對應的尺度(作為特征點尺度)。

特征點描述

高斯濾波

       現在,我們得到了特征點的位置和尺度(t)后,要對特征點賦予其描述符。均勻采樣模式:以特征點為中心,構建不同半徑的同心圓,在每個圓上獲取一定數目的等間隔采樣點(所有采樣點包括特征點,一共N個),由於這種鄰域采樣模式會引起混疊效應,所以需要對同心圓上的采樣點進行高斯濾波。

       采樣模式如下圖,藍圈表示;以采樣點為中心,為方差進行高斯濾波,濾波半徑大小與高斯方差的大小成正比,紅圈表示。最終用到的N個采樣點是經過高斯平滑后的采樣點。下圖是t=1時的。(文章中:N=60)

局部梯度計算

         由於有N個采樣點,則采樣點兩兩組合成一對,共有N(N-1)/2鍾組合方式,所有組合方式的集合稱作采樣點對,用集合表示,其中像素分別是,δ表示尺度。用表示特征點局部梯度集合,則有:

定義短距離點對子集、長距離點對子集(L個):

其中,,t是特征點所在的尺度。

現在要利用上面得到的信息,來計算特征點的主方向(注意:此處只用到了長距離子集),如下:

特征描述符

         要解決旋轉不變性,則需要對特征點周圍的采樣區域進行旋轉到主方向,旋轉后得到新的采樣區域,采樣模式同上。BRISK描述子是二進制的特征,由采樣點集合可得到N(N-1)/2對采樣點對,就可以得到N(N-1)/2個距離的集合(包含長、短距離子集),考慮其中短距離子集中的512個短距離點對,進行二進制編碼,判斷方式如下:

其中,帶有上標,表示經過旋轉a角度后的,新的采樣點。由此可得到,512Bit的二進制編碼,也就是64個字節(BRISK64)。

匹配方法

漢明距離進行比較,與其他二進制描述子的匹配方式一樣。

實驗

opencv代碼

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. #include <cv.h>  
  2. #include <opencv2/highgui/highgui.hpp>  
  3. #include <opencv2/core/core.hpp>  
  4. #include <opencv2/nonfree/features2d.hpp>  
  5. #include <opencv2/nonfree/nonfree.hpp>  
  6. #include <Windows.h>  
  7.   
  8. using namespace cv;  
  9. using namespace std;  
  10.   
  11. int main()  
  12. {  
  13.     //Load Image  
  14.     Mat c_src1 =  imread( "1.png");  
  15.     Mat c_src2 = imread("2.png");  
  16.     Mat src1 = imread( "1.png", CV_LOAD_IMAGE_GRAYSCALE);  
  17.     Mat src2 = imread( "2.png", CV_LOAD_IMAGE_GRAYSCALE);  
  18.     if( !src1.data || !src2.data )  
  19.     {  
  20.         cout<< "Error reading images " << std::endl;  
  21.         return -1;  
  22.     }  
  23.     //feature detect  
  24.     BRISK detector;  
  25.     vector<KeyPoint> kp1, kp2;  
  26.     double start = GetTickCount();  
  27.     detector.detect( src1, kp1 );  
  28.     detector.detect( src2, kp2 );  
  29.     //cv::BRISK extractor;  
  30.     Mat des1,des2;//descriptor  
  31.     detector.compute(src1, kp1, des1);  
  32.     detector.compute(src2, kp2, des2);  
  33.     Mat res1,res2;  
  34.     int drawmode = DrawMatchesFlags::DRAW_RICH_KEYPOINTS;  
  35.     drawKeypoints(c_src1, kp1, res1, Scalar::all(-1), drawmode);//畫出特征點  
  36.     drawKeypoints(c_src2, kp2, res2, Scalar::all(-1), drawmode);  
  37.     cout<<"size of description of Img1: "<<kp1.size()<<endl;  
  38.     cout<<"size of description of Img2: "<<kp2.size()<<endl;  
  39.   
  40.     BFMatcher matcher(NORM_HAMMING);  
  41.     vector<DMatch> matches;  
  42.     matcher.match(des1, des2, matches);  
  43.     double end = GetTickCount();  
  44.     cout<<"耗時:"<<(end - start) <<"ms"<<endl;  
  45.     Mat img_match;  
  46.     drawMatches(src1, kp1, src2, kp2, matches, img_match);  
  47.     cout<<"number of matched points: "<<matches.size()<<endl;  
  48.     imshow("matches",img_match);  
  49.     cvWaitKey(0);  
  50.     cvDestroyAllWindows();  
  51.     return 0;  
  52. }  

 

實驗結果

視頻地址

代碼分析

由於代碼都很長,只列出了brisk類的兩個方法,其余詳見:..\opencv\sources\modules\features2d\src\brisk.c
[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. // construct the image pyramids(構造圖像金字塔)  
  2. void  
  3. BriskScaleSpace::constructPyramid(const cv::Mat& image)  
  4. {  
  5.   
  6.   // set correct size:  
  7.   pyramid_.clear();  
  8.   
  9.   // fill the pyramid:  
  10.   pyramid_.push_back(BriskLayer(image.clone()));  
  11.   if (layers_ > 1)  
  12.   {  
  13.     pyramid_.push_back(BriskLayer(pyramid_.back(), BriskLayer::CommonParams::TWOTHIRDSAMPLE));//d0層是2/3  
  14.   }  
  15.   const int octaves2 = layers_;  
  16.   
  17.   for (uchar i = 2; i < octaves2; i += 2)  
  18.   {  
  19.     pyramid_.push_back(BriskLayer(pyramid_[i - 2], BriskLayer::CommonParams::HALFSAMPLE));//c?層是前兩層的1/2  
  20.     pyramid_.push_back(BriskLayer(pyramid_[i - 1], BriskLayer::CommonParams::HALFSAMPLE));//d?層是前兩層的1/2(除d0層外)  
  21.   }  
  22. }  
[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. //提取特征點  
  2. void  
  3. BriskScaleSpace::getKeypoints(const int threshold_, std::vector<cv::KeyPoint>& keypoints)  
  4. {  
  5.   // make sure keypoints is empty  
  6.   keypoints.resize(0);  
  7.   keypoints.reserve(2000);  
  8.   
  9.   // assign thresholds  
  10.   int safeThreshold_ = (int)(threshold_ * safetyFactor_);  
  11.   std::vector<std::vector<cv::KeyPoint> > agastPoints;  
  12.   agastPoints.resize(layers_);  
  13.   
  14.   // go through the octaves and intra layers and calculate fast corner scores:  
  15.   for (int i = 0; i < layers_; i++)  
  16.   {  
  17.     // call OAST16_9 without nms  
  18.     BriskLayer& l = pyramid_[i];  
  19.     l.getAgastPoints(safeThreshold_, agastPoints[i]);  
  20.   }  
  21.   
  22.   if (layers_ == 1)  
  23.   {  
  24.     // just do a simple 2d subpixel refinement...  
  25.     const size_t num = agastPoints[0].size();  
  26.     for (size_t n = 0; n < num; n++)  
  27.     {  
  28.       const cv::Point2f& point = agastPoints.at(0)[n].pt;  
  29.       // first check if it is a maximum:  
  30.       if (!isMax2D(0, (int)point.x, (int)point.y))  
  31.         continue;  
  32.   
  33.       // let's do the subpixel and float scale refinement:  
  34.       BriskLayer& l = pyramid_[0];  
  35.       int s_0_0 = l.getAgastScore(point.x - 1, point.y - 1, 1);  
  36.       int s_1_0 = l.getAgastScore(point.x, point.y - 1, 1);  
  37.       int s_2_0 = l.getAgastScore(point.x + 1, point.y - 1, 1);  
  38.       int s_2_1 = l.getAgastScore(point.x + 1, point.y, 1);  
  39.       int s_1_1 = l.getAgastScore(point.x, point.y, 1);  
  40.       int s_0_1 = l.getAgastScore(point.x - 1, point.y, 1);  
  41.       int s_0_2 = l.getAgastScore(point.x - 1, point.y + 1, 1);  
  42.       int s_1_2 = l.getAgastScore(point.x, point.y + 1, 1);  
  43.       int s_2_2 = l.getAgastScore(point.x + 1, point.y + 1, 1);  
  44.       float delta_x, delta_y;  
  45.       float max = subpixel2D(s_0_0, s_0_1, s_0_2, s_1_0, s_1_1, s_1_2, s_2_0, s_2_1, s_2_2, delta_x, delta_y);  
  46.   
  47.       // store:  
  48.       keypoints.push_back(cv::KeyPoint(float(point.x) + delta_x, float(point.y) + delta_y, basicSize_, -1, max, 0));  
  49.   
  50.     }  
  51.   
  52.     return;  
  53.   }  
  54.   
  55.   float x, y, scale, score;  
  56.   for (int i = 0; i < layers_; i++)  
  57.   {  
  58.     BriskLayer& l = pyramid_[i];  
  59.     const size_t num = agastPoints[i].size();  
  60.     if (i == layers_ - 1)  
  61.     {  
  62.       for (size_t n = 0; n < num; n++)  
  63.       {  
  64.         const cv::Point2f& point = agastPoints.at(i)[n].pt;  
  65.         // consider only 2D maxima...  
  66.         if (!isMax2D(i, (int)point.x, (int)point.y))  
  67.           continue;  
  68.   
  69.         bool ismax;  
  70.         float dx, dy;  
  71.         getScoreMaxBelow(i, (int)point.x, (int)point.y, l.getAgastScore(point.x, point.y, safeThreshold_), ismax, dx, dy);  
  72.         if (!ismax)  
  73.           continue;  
  74.   
  75.         // get the patch on this layer:  
  76.         int s_0_0 = l.getAgastScore(point.x - 1, point.y - 1, 1);  
  77.         int s_1_0 = l.getAgastScore(point.x, point.y - 1, 1);  
  78.         int s_2_0 = l.getAgastScore(point.x + 1, point.y - 1, 1);  
  79.         int s_2_1 = l.getAgastScore(point.x + 1, point.y, 1);  
  80.         int s_1_1 = l.getAgastScore(point.x, point.y, 1);  
  81.         int s_0_1 = l.getAgastScore(point.x - 1, point.y, 1);  
  82.         int s_0_2 = l.getAgastScore(point.x - 1, point.y + 1, 1);  
  83.         int s_1_2 = l.getAgastScore(point.x, point.y + 1, 1);  
  84.         int s_2_2 = l.getAgastScore(point.x + 1, point.y + 1, 1);  
  85.         float delta_x, delta_y;  
  86.         float max = subpixel2D(s_0_0, s_0_1, s_0_2, s_1_0, s_1_1, s_1_2, s_2_0, s_2_1, s_2_2, delta_x, delta_y);  
  87.   
  88.         // store:  
  89.         keypoints.push_back(  
  90.             cv::KeyPoint((float(point.x) + delta_x) * l.scale() + l.offset(),  
  91.                          (float(point.y) + delta_y) * l.scale() + l.offset(), basicSize_ * l.scale(), -1, max, i));  
  92.       }  
  93.     }  
  94.     else  
  95.     {  
  96.       // not the last layer:  
  97.       for (size_t n = 0; n < num; n++)  
  98.       {  
  99.         const cv::Point2f& point = agastPoints.at(i)[n].pt;  
  100.   
  101.         // first check if it is a maximum:  
  102.         if (!isMax2D(i, (int)point.x, (int)point.y))  
  103.           continue;  
  104.   
  105.         // let's do the subpixel and float scale refinement:  
  106.         bool ismax=false;  
  107.         score = refine3D(i, (int)point.x, (int)point.y, x, y, scale, ismax);  
  108.         if (!ismax)  
  109.         {  
  110.           continue;  
  111.         }  
  112.   
  113.         // finally store the detected keypoint:  
  114.         if (score > float(threshold_))  
  115.         {  
  116.           keypoints.push_back(cv::KeyPoint(x, y, basicSize_ * scale, -1, score, i));  
  117.         }  
  118.       }  
  119.     }  
  120.   }  
  121. }  

參考文獻

 

1、BRISK:binary robust invariant scalable keypoints,2011,ICCV.

2、多種角度比較SIFT、SURF、RISK、ORB、FREAK算法[J],2014.

3、基於顏色不變量的特征匹配算法研究[碩士論文],2014.

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM