简介
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