簡介
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代碼
- #include <cv.h>
- #include <opencv2/highgui/highgui.hpp>
- #include <opencv2/core/core.hpp>
- #include <opencv2/nonfree/features2d.hpp>
- #include <opencv2/nonfree/nonfree.hpp>
- #include <Windows.h>
- using namespace cv;
- using namespace std;
- int main()
- {
- //Load Image
- Mat c_src1 = imread( "1.png");
- Mat c_src2 = imread("2.png");
- Mat src1 = imread( "1.png", CV_LOAD_IMAGE_GRAYSCALE);
- Mat src2 = imread( "2.png", CV_LOAD_IMAGE_GRAYSCALE);
- if( !src1.data || !src2.data )
- {
- cout<< "Error reading images " << std::endl;
- return -1;
- }
- //feature detect
- BRISK detector;
- vector<KeyPoint> kp1, kp2;
- double start = GetTickCount();
- detector.detect( src1, kp1 );
- detector.detect( src2, kp2 );
- //cv::BRISK extractor;
- Mat des1,des2;//descriptor
- detector.compute(src1, kp1, des1);
- detector.compute(src2, kp2, des2);
- Mat res1,res2;
- int drawmode = DrawMatchesFlags::DRAW_RICH_KEYPOINTS;
- drawKeypoints(c_src1, kp1, res1, Scalar::all(-1), drawmode);//畫出特徵點
- drawKeypoints(c_src2, kp2, res2, Scalar::all(-1), drawmode);
- cout<<"size of description of Img1: "<<kp1.size()<<endl;
- cout<<"size of description of Img2: "<<kp2.size()<<endl;
- BFMatcher matcher(NORM_HAMMING);
- vector<DMatch> matches;
- matcher.match(des1, des2, matches);
- double end = GetTickCount();
- cout<<"耗時:"<<(end - start) <<"ms"<<endl;
- Mat img_match;
- drawMatches(src1, kp1, src2, kp2, matches, img_match);
- cout<<"number of matched points: "<<matches.size()<<endl;
- imshow("matches",img_match);
- cvWaitKey(0);
- cvDestroyAllWindows();
- return 0;
- }
實驗結果
視頻地址
代碼分析
- // construct the image pyramids(構造圖像金字塔)
- void
- BriskScaleSpace::constructPyramid(const cv::Mat& image)
- {
- // set correct size:
- pyramid_.clear();
- // fill the pyramid:
- pyramid_.push_back(BriskLayer(image.clone()));
- if (layers_ > 1)
- {
- pyramid_.push_back(BriskLayer(pyramid_.back(), BriskLayer::CommonParams::TWOTHIRDSAMPLE));//d0層是2/3
- }
- const int octaves2 = layers_;
- for (uchar i = 2; i < octaves2; i += 2)
- {
- pyramid_.push_back(BriskLayer(pyramid_[i - 2], BriskLayer::CommonParams::HALFSAMPLE));//c?層是前兩層的1/2
- pyramid_.push_back(BriskLayer(pyramid_[i - 1], BriskLayer::CommonParams::HALFSAMPLE));//d?層是前兩層的1/2(除d0層外)
- }
- }
- //提取特徵點
- void
- BriskScaleSpace::getKeypoints(const int threshold_, std::vector<cv::KeyPoint>& keypoints)
- {
- // make sure keypoints is empty
- keypoints.resize(0);
- keypoints.reserve(2000);
- // assign thresholds
- int safeThreshold_ = (int)(threshold_ * safetyFactor_);
- std::vector<std::vector<cv::KeyPoint> > agastPoints;
- agastPoints.resize(layers_);
- // go through the octaves and intra layers and calculate fast corner scores:
- for (int i = 0; i < layers_; i++)
- {
- // call OAST16_9 without nms
- BriskLayer& l = pyramid_[i];
- l.getAgastPoints(safeThreshold_, agastPoints[i]);
- }
- if (layers_ == 1)
- {
- // just do a simple 2d subpixel refinement...
- const size_t num = agastPoints[0].size();
- for (size_t n = 0; n < num; n++)
- {
- const cv::Point2f& point = agastPoints.at(0)[n].pt;
- // first check if it is a maximum:
- if (!isMax2D(0, (int)point.x, (int)point.y))
- continue;
- // let's do the subpixel and float scale refinement:
- BriskLayer& l = pyramid_[0];
- int s_0_0 = l.getAgastScore(point.x - 1, point.y - 1, 1);
- int s_1_0 = l.getAgastScore(point.x, point.y - 1, 1);
- int s_2_0 = l.getAgastScore(point.x + 1, point.y - 1, 1);
- int s_2_1 = l.getAgastScore(point.x + 1, point.y, 1);
- int s_1_1 = l.getAgastScore(point.x, point.y, 1);
- int s_0_1 = l.getAgastScore(point.x - 1, point.y, 1);
- int s_0_2 = l.getAgastScore(point.x - 1, point.y + 1, 1);
- int s_1_2 = l.getAgastScore(point.x, point.y + 1, 1);
- int s_2_2 = l.getAgastScore(point.x + 1, point.y + 1, 1);
- float delta_x, delta_y;
- 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);
- // store:
- keypoints.push_back(cv::KeyPoint(float(point.x) + delta_x, float(point.y) + delta_y, basicSize_, -1, max, 0));
- }
- return;
- }
- float x, y, scale, score;
- for (int i = 0; i < layers_; i++)
- {
- BriskLayer& l = pyramid_[i];
- const size_t num = agastPoints[i].size();
- if (i == layers_ - 1)
- {
- for (size_t n = 0; n < num; n++)
- {
- const cv::Point2f& point = agastPoints.at(i)[n].pt;
- // consider only 2D maxima...
- if (!isMax2D(i, (int)point.x, (int)point.y))
- continue;
- bool ismax;
- float dx, dy;
- getScoreMaxBelow(i, (int)point.x, (int)point.y, l.getAgastScore(point.x, point.y, safeThreshold_), ismax, dx, dy);
- if (!ismax)
- continue;
- // get the patch on this layer:
- int s_0_0 = l.getAgastScore(point.x - 1, point.y - 1, 1);
- int s_1_0 = l.getAgastScore(point.x, point.y - 1, 1);
- int s_2_0 = l.getAgastScore(point.x + 1, point.y - 1, 1);
- int s_2_1 = l.getAgastScore(point.x + 1, point.y, 1);
- int s_1_1 = l.getAgastScore(point.x, point.y, 1);
- int s_0_1 = l.getAgastScore(point.x - 1, point.y, 1);
- int s_0_2 = l.getAgastScore(point.x - 1, point.y + 1, 1);
- int s_1_2 = l.getAgastScore(point.x, point.y + 1, 1);
- int s_2_2 = l.getAgastScore(point.x + 1, point.y + 1, 1);
- float delta_x, delta_y;
- 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);
- // store:
- keypoints.push_back(
- cv::KeyPoint((float(point.x) + delta_x) * l.scale() + l.offset(),
- (float(point.y) + delta_y) * l.scale() + l.offset(), basicSize_ * l.scale(), -1, max, i));
- }
- }
- else
- {
- // not the last layer:
- for (size_t n = 0; n < num; n++)
- {
- const cv::Point2f& point = agastPoints.at(i)[n].pt;
- // first check if it is a maximum:
- if (!isMax2D(i, (int)point.x, (int)point.y))
- continue;
- // let's do the subpixel and float scale refinement:
- bool ismax=false;
- score = refine3D(i, (int)point.x, (int)point.y, x, y, scale, ismax);
- if (!ismax)
- {
- continue;
- }
- // finally store the detected keypoint:
- if (score > float(threshold_))
- {
- keypoints.push_back(cv::KeyPoint(x, y, basicSize_ * scale, -1, score, i));
- }
- }
- }
- }
- }
參考文獻
1、BRISK:binary robust invariant scalable keypoints,2011,ICCV.
2、多種角度比較SIFT、SURF、RISK、ORB、FREAK算法[J],2014.
3、基於顏色不變量的特徵匹配算法研究[碩士論文],2014.
各種特徵提取算子
1 ORB(ORientedBrief):
論文Ethan Rublee and Vincent Rabaud and KurtKonolige and Gary Bradski, ORB:an efficient alternative to SIFT or SURF.點擊下載論文
理論參考:http://www.cnblogs.com/scnucs/archive/2011/12/20/2294189.html
代碼實現效果:http://blog.csdn.net/merlin_q/article/details/7026375
2 Brief(Binary Robust Independent Elementary Features)
由EPFL的Calonder在ECCV2010上提出的。主要思路就是在特徵點附近隨機選取若干點對,將這些點對的灰度值的大小,組合成一個二進制串,並將這個二進制串作爲該特徵點的特徵描述子。詳細算法描述參考如下論文:
開源代碼:這裏。實現效果:http://blog.csdn.net/yangtrees/article/details/7533988
注:在BRIEF eccv2010的文章中,BRIEF描述子中的每一位是由隨機選取的兩個像素點做二進制比較得來的。文章同樣提到,在此之前,需要選取合適的gaussian kernel對圖像做平滑處理。(爲什麼要強調這一點,因爲下述的ORB對此作了改進。)
BRIEF的優點在於速度,缺點也相當明顯:
1:不具備旋轉不變性。
2:對噪聲敏感
3:不具備尺度不變性。
ORB就是試圖解決上述缺點中的1和2.
註明:
1)如何解決旋轉不變性:
在ORB的方案中,是採用了FAST作爲特徵點檢測算子。FAST應用的很多了,是出名的快,以防有人不知道,請看這裏。在Sift的方案中,特徵點的主方向是由梯度直方圖的最大值和次大值所在的bin對應的方向決定的。略嫌耗時。在ORB的方案中,特徵點的主方向是通過矩(moment)計算而來。有了主方向之後,就可以依據該主方向提取BRIEF描述子。但是由此帶來的問題是,由於主方向會發生變化,隨機點對的相關性會比較大,從而降低描述子的判別性。解決方案也很直接,採取貪婪的,窮舉的方法,暴力找到相關性較低的隨機點對。
2)如何解決對噪聲敏感的問題:
BRIEF使用的是pixel跟pixel的大小來構造描述子的每一個bit;這樣的後果就是對噪聲敏感。ORB的方案中,做了這樣的改進,不再使用pixel-pair,而是使用9×9的patch-pair,也就是說,對比patch的像素值之和。(可以通過積分圖快速計算)。
3)關於尺度不變性:
ORB沒有試圖解決尺度不變性,(因爲FAST本身就不具有尺度不變性。)但是這樣只求速度的特徵描述子,一般都是應用在實時的視頻處理中的,這樣的話就可以通過跟蹤還有一些啓發式的策略來解決尺度不變性的問題。
4)關於計算速度:
ORB是sift的100倍,是surf的10倍。
3FREAK
2012出來的對前面BRIEF改進
論文下載:http://infoscience.epfl.ch/record/175537/files/2069.pdf
代碼相關網站:http://www.ivpe.com/freak.htm
FREAK和ORB特徵描述子效果對比
ORB就是BRIEF的改進,BRIEF太簡單了,就不介紹了,有興趣的朋友自己看paper吧。ORB的paper我讀下來,感覺改進主要有以下幾點:用FAST作爲特徵點提取的算法,更快了,添加了特徵點的主方向,這樣就具有了旋轉不變性。最後一點其實我也想到了,當時看BRIEF的時候就想應該可以優化,就是ORB採用貪婪窮舉的方法得到了相關性較低的隨機點對,還有一個改進就是對於隨機點對,受噪聲的影響很大BRIEF的辦法就是對原圖像濾波,降低噪聲的影響,ORB不在使用像素點的直接比較,而是選擇該像素爲中心的一個小patch作爲比較對象,提高了抗噪能力。
FREAK個人理解是這個算法是基於人眼視網膜細胞的分佈,中間密集,四周稀疏,從而在圖像中構建很多的區域,當然越靠近中心的區域採樣更密集,四周區域採樣稀疏,隨機對比各區域的像素得到一組2值特徵,這個算法也關注了尺度和方向的問題,都有對應的解決辦法,還根據了人眼看事物時眼睛不停的轉動,設計了一種級聯的搜索器,總而言之,我感覺這個算法也是受ORB和BRISK這種2值特徵的啓發下的一種改進吧。
OpenCV在2.4.2中,FREAK給出了pattern的訓練代碼。
註明:雖然兩種算法不能直接比較,因爲FREAK沒有提供特徵點位置檢測的算法,個人感覺如果FREAK採用FAST來做detection,確實速度應該要逼ORB要快一些。
代碼:http://download.csdn.net/detail/yang_xian521/4421537
效果圖:http://blog.csdn.net/yang_xian521/article/details/7732835
4 BRISK
BRISK描述子由Stefan Leutenegger等人發表於ICCV11上。(ORB也是ICCV11上出現的)。詳見這篇文章: “BRISK: Binary Robust In variantScalableKeypoints”。
BRISK是也是BRIEF描述子的一種改進,相比於BRIEF特徵,它具有旋轉不變性、尺度不變性和對噪聲的魯棒性。
實現:
http://blog.csdn.NET/jinxueliu31/article/details/18556855
http://blog.csdn.net/xiazhao1234/article/details/7752292
其中
Demo Video
具體代碼可以參考:BRISK特徵提取算法
(這個視頻中的效果的確很好,快速,實時,特徵雖然沒有SIFT多,但是也足夠)
ICCV2011 Paper
Stefan Leutenegger, Margarita Chli andRoland Siegwart, BRISK: Binary Robust Invariant Scalable Keypoints, to appear in Proceedings of the IEEE International Conference onComputer Vision (ICCV) 2011.
Code
Open-source package containing windowsand Linux libraries, a demo application, and a Matlab mex interface:
Downloadbrisk.zip(16
MB)
(v0.1 December 10th 2011)
Provided under the terms and conditionsof the BSD license.
Please contact [email protected], if you have related questions or suggestions.
注:SIFT,SURF描述符數據類型有是float的,而 ORB,BRIEF是uchar的,注重實時性。
原文地址:http://blog.csdn.net/hujingshuang/article/details/47045497
http://blog.csdn.net/tiandijun/article/details/40679581