Sift算法的优点是特征稳定,对旋转、尺度变换、亮度保持不变性,对视角变换、噪声也有一定程度的稳定性;缺点是实时性不高,并且对于边缘光滑目标的特征点提取能力较弱。
Surf(Speeded Up Robust Features)改进了特征的提取和描述方式,用一种更为高效的方式完成特征的提取和描述。
ORB在2015年提出,通过FAST算法定位角点,但是FAST不具备方向信息,因此作者提出使用灰度质心法来获得方向,而描述符方面采用的是128位的二进制描述子,通过在x,y方向按照高斯分布来随机选取128个点对,随后比较每对点的大小来决定该位是1还是0,大大加快了特征描述计算和匹配的速度,大名鼎鼎的ORB-SLAM就是使用ORB特征来做前端提取。
对于一个特征点提取算法,为了实现方向旋转和尺度变换的的不变性,输出结果应当包括三个方面的内容,特征点的位置信息,方向信息,特征描述。这里的位置信息包括特征点在图像上的位置x,y以及特征点所在的尺度。
在关键点的定位上,SURF使用积分图和Boxfilter计算hessian矩阵,通过比较hessian矩阵行列式的大小来选择特征点的位置和尺度,而不是SIFT所使用的DOG算子,金字塔构建时通过改变boxfilter的尺寸和滤波器的方差来实现不同的尺度图像的获取。
获得积分图主要是为了方便计算某一个区域的像素和。I(x,y)对应f(1,1)和以f(x,y)为对角线的矩阵内的所有像素点的累加和
使用盒式滤波器卷积时,可以查找积分图中的元素来加快计算。
第一行的三个图是高斯滤波器
第二行的三个图便是我们要用的三个盒子模型滤波器,分别是Dxx,Dyy,Dxy,就是对x的二阶偏导,对y的二阶偏导,还有对xy的混合偏导。
图中边缘的灰色部分的值为0,白色是1,黑色的是-2
不同于SIFT的对原始图像进行降采样以构建金字塔,SURF则保持原始图像尺寸不变,不断扩大滤波。在Surf中,不同组间图像的尺寸都是一致的,但不同组间使用的盒式滤波器的模板尺寸逐渐增大,同一组间不同层间使用相同尺寸的滤波器,但是滤波器的模糊系数逐渐增大。
在sift中,通过不断地减小图像尺寸来模拟远近变化,而SURF是通过改变fiter的尺寸来实现不同尺度的获得,不会因为filter尺寸的变大而使得计算量变大,因为在卷积是仅仅是查找积分图中的四个角处的数值,所有层的计算代价都是相同的。
与SIFT算法类似,我们需要将尺度空间划分为若干组(Octaves)。一个组代表了逐步放大的滤波模板对同一输入图像进行滤波的一系列响应图。每个组又由若干固定的层组成。由于积分图像离散化的原因,两个层之间的最小尺度变化量是由高斯二阶微分滤波器在微分方向上对正负斑点响应长度L0决定的,它是盒子滤波器模板尺寸的1/3。对于9×9的模板,它的L0=3。下一层的响应长度至少应该在L0的基础上增加2个像素,以保证一边一个像素,即L0=5。这样模板的尺寸就为15×15。以此类推,我们可以得到一个尺寸增大模板序列,它们的尺寸分别为:9×9,15×15,21×21,27×279×9,15×15,21×21,27×27,黑色、白色区域的长度增加偶数个像素,以保证一个中心像素的存在。
SURF的尺度空间中不同层所用的filter size如上图所示。
横的每一行表示每组内部的四个滤波器的尺寸大小,细心的话会发现相邻组之间的滤波器尺寸会有重叠,这个下节就会解释。
每次我们要对一个图像用三个不同滤波器处理(如上图所示),之后再进行处理后得到Hessiam行列式图像。
得到Hessiam行列式图像具体的处理方式是:
每个像素的Hessian矩阵行列式的近似值:
在Dxy上乘了一个加权系数0.9,目的是为了平衡因使用盒式滤波器近似所带来的误差:
这样经过滤波之后我们就会得到一系列经过滤波处理后的图像
a.非极大值抑制过程
在每一组中选取相邻的三层Hessian行列式图像,对于中间层的每一个Hessian行列式值都可以做为待比较的点,在空间中选取该点周围的26个点进行比较大小,若该点大于其他26个点,则该点为特征点。从上诉过程可以知道,当尺度空间每组由四层构成时,非极大值抑制只会在中间两层进行,相邻的组之间不进行比较。
b.设定Hessian行列式的阀值
低于Hessian行列式阀值的点不能作为最终的特征点。在实际选择阀值时,根据实际应用中对特征点数量和精确度的要求改变阀值。阀值越大,得到的特征点的鲁棒性越好。在处理场景简单的图像时,其阀值可以适当的调低。在复杂的图像中,图像经旋转或者模糊后特征点变化的数量较大,测试需要适当提高阀值。
想象一下,我们现在已经得到了很多层的Hessiam行列式图像,一共有很多组,每组内部有四层,
这里我们只对每组组内中间的两层内的所有点进行极值检测,最顶的一层因为缺少上一层而最底部一层因为缺少下层而不进行极值检测(这就解释了为什么相邻的组之间的各滤波器尺寸会有重叠部分)
极值检测就是中心点与自己周围的26个点进行比较,检测其是否为最大值点或者是最小值点,除此之外,还要将其与我们设置的极值阈值进行比较,进行筛选,只留下一些强特征点,到此为止,我们的特征点就寻找完毕了。
Sift特征点方向分配是采用在特征点邻域内统计其梯度直方图,而在Surf中,采用的是统计特征点圆形邻域内的harr小波特征。
在特征点的圆形邻域内,统计60度扇形内所有点的水平、垂直harr小波特征总和,然后扇形以一定间隔进行旋转并再次统计该区域内harr小波特征值之后,最后将值最大的那个扇形的方向作为该特征点的主方向。
具体:
我们需要为每个特征点分配一个主方向,这就是主方向的选取问题,以特征点为圆心,以6*s(s=1.2∗L/9为特征点的尺度)为半径,对图像进行Haar小波响应运算(这里的图像指的是原始图像f(x,y)),不过进行小波运算的时候还会用到积分图像。
黑色表示-1,白色表示+1。用其对圆形领域进行处理后,就得到了该领域内每个点对应的x,y方向的响应,然后用以兴趣点为中心的高斯函数(σ =2s)对这些响应进行加权。
为了求取主方向值,需要设计一个以特征点为中心,张角为60度的扇形滑动窗口,统计这个扇形区域内的haar小波特征总和。以步长为0.2弧度左右,旋转这个滑动窗口,再统计小波特征总和。小波特征总和最大的方向为主方向。特征总和的求法是对图像Harr小波响应值dx、dy进行累加,得到一个矢量。(在圆形邻域内,检测各个扇形范围内水平、垂直方向上的Haar小波响应,找到模值最大的扇形指向,且该算法的方向只有一个)
主方向为最大Harr响应累加值所对应的方向,也就是最长矢量所对应的方向。
在SURF中,我们在关键点周围选取一个正方形框,方向为关键点的主方向,边长为20S。将其划分为16个区域(边长为5S),每个区域统计25个像素的水平方向和垂直方向的Haar小波特性(均相对于正方形框的主方向确定的。
该小波特征包括水平方向值之和,水平方向绝对值之和,垂直方向值之和和垂直方向绝对值之和(为了把强度变化的极性信息也包括描述符中,所以对绝对值进行累加)。这样每个区域有4个值,则每个正方形框有维,即每个关键点描述是64维,比SIFT描述少了一半
(在SIFT中关键点描述是选取了关键点周围1616的领域,又将其划分为44的区域,每个区域统计8个方向梯度,最后得到448=128维度的描述向量。)
这样,每个特征点可以用一个1 * 64 的向量来表示(444 = 64)
随后对两幅图像的特征向量进行匹配
首先是构建金字塔的目的
两个待匹配图像中的两个物体的尺寸因为拍摄距离等原因可能在图像上面是不同的,例如同一个水杯,站在近处拍摄,得到A图上面的水杯尺寸大小是20×80,站在远处拍,B图上拍出来的尺寸是10×40,但是它们确实是同一个水杯啊,但是若是没有金字塔模型,两个图片中的水杯部分的特征点的特征向量很有可能匹配不上(具体原因我也说不清楚),但是有了金字塔之后就不一样了,拿SIFT算法来说,我们要对原始图像不断进行降采样处理来得到金字塔,A图经过一次降采样之后,图片整体尺寸变为原来的一半(假设为一半),里面的水杯也就自然变成了10×40的大小,这样这一降采样之后的图像就可以刚好和B图的原始图像匹配上,当然也不是说必须要尺寸完全一样才可以,但是我们总能找到尺寸最相似的一组
另一个是主方向的确立
还拿上面的例子,一个杯子可以横着拍,也可以竖着拍,当然也可以斜着拍,如果不进行方向归一化,很可能因为特征向量的内部错位而导致匹配失败,这就需要统一一个标准方向。这就为SIFT算法和SURF算法实现旋转不变性提供了可能
(1)在生成尺度空间方面,SIFT算法利用的是差分高斯金字塔与不同层级的空间图像相互卷积生成。SURF算法采用的是不同尺度的box filters与原图像卷积
(2)在特征点检验时,SIFT算子是先对图像进行非极大值抑制,再去除对比度较低的点。然后通过Hessian矩阵去除边缘的点。
而SURF算法是先通过Hessian矩阵来检测候选特征点,然后再对非极大值的点进行抑制
(3)在特征向量的方向确定上,SIFT算法是在正方形区域内统计梯度的幅值的直方图,找到最大梯度幅值所对应的方向。SIFT算子确定的特征点可以有一个或一个以上方向,其中包括一个主方向与多个辅方向。
SURF算法则是在圆形邻域内,检测各个扇形范围内水平、垂直方向上的Haar小波响应,找到模值最大的扇形指向,且该算法的方向只有一个。
(4)SIFT算法生成描述子时,是将的采样点划分为的区域,从而计算每个分区种子点的幅值并确定其方向,共计维。
SURF算法在生成特征描述子时将的正方形分割成的小方格,每个子区域25个采样点,计算小波haar响应,一共维。
综上,SURF算法在各个步骤上都简化了一些繁琐的工作,仅仅计算了特征点的一个主方向,生成的特征描述子也与前者相比降低了维数。
#include
#include
#include
void extracte_surf(cv::Mat input,std::vector<cv::KeyPoint> &keypoint,cv::Mat &descriptor){
cv::Ptr<cv::Feature2D> f2d = cv::xfeatures2d::SURF::create();
f2d->detect(input,keypoint);
cv::Mat image_with_kp;
f2d->compute(input,keypoint,descriptor);
cv::drawKeypoints(input, keypoint, image_with_kp, cv::Scalar::all(-1),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::imwrite("surf"+std::to_string(keypoint.size())+".png",image_with_kp);
}
void match_two_image(cv::Mat image1,cv::Mat image2, std::vector<cv::KeyPoint> keypoint1,std::vector<cv::KeyPoint> keypoint2,cv::Mat descriptor1,cv::Mat descriptor2){
cv::FlannBasedMatcher matcher;
std::vector<cv::DMatch> matches, goodmatches;
matcher.match(descriptor1,descriptor2, matches);
cv::Mat good_matches_image;
double max_dist = 0; double min_dist = 1000;
for (int i = 0; i < descriptor1.rows; i++) {
if (matches[i].distance > max_dist) {
max_dist = matches[i].distance;
}
if (matches[i].distance < min_dist) {
min_dist = matches[i].distance;
}
}
for (int i = 0; i < descriptor1.rows; i++) {
if (matches[i].distance < 6 * min_dist) {
goodmatches.push_back(matches[i]);
}
}
cv::drawMatches(image1, keypoint1, image2, keypoint2,
goodmatches, good_matches_image, cv::Scalar::all(-1), cv::Scalar::all(-1),
std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
cv::imwrite("good_matches_image.png",good_matches_image);
{
std::vector <cv::KeyPoint> RAN_KP1, RAN_KP2;
std::vector<cv::Point2f> keypoints1, keypoints2;
for (int i = 0; i < goodmatches.size(); i++) {
keypoints1.push_back(keypoint1[goodmatches[i].queryIdx].pt);
keypoints2.push_back(keypoint2[goodmatches[i].trainIdx].pt);
RAN_KP1.push_back(keypoint1[goodmatches[i].queryIdx]);
RAN_KP2.push_back(keypoint2[goodmatches[i].trainIdx]);
}
std::vector<uchar> RansacStatus;
cv::findFundamentalMat(keypoints1, keypoints2, RansacStatus, cv::FM_RANSAC);
std::vector <cv::KeyPoint> ransac_keypoints1, ransac_keypoints2;
std::vector <cv::DMatch> ransac_matches;
int index = 0;
for (size_t i = 0; i < goodmatches.size(); i++)
{
if (RansacStatus[i] != 0)
{
ransac_keypoints1.push_back(RAN_KP1[i]);
ransac_keypoints2.push_back(RAN_KP2[i]);
goodmatches[i].queryIdx = index;
goodmatches[i].trainIdx = index;
ransac_matches.push_back(goodmatches[i]);
index++;
}
}
cv::Mat after_ransac_sift_match;
cv::drawMatches(image1, ransac_keypoints1, image2, ransac_keypoints2,
ransac_matches, after_ransac_sift_match, cv::Scalar::all(-1), cv::Scalar::all(-1),
std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
cv::imwrite("after_ransac_surf_match.png",after_ransac_sift_match);
}
}
int main(int argc, char *argv[])
{
cv::Mat image1 = cv::imread(argv[1]);
cv::Mat image2 = cv::imread(argv[2]);
std::vector<cv::KeyPoint> keypoint1,keypoint2;
cv::Mat descriptor1, descriptor2;
extracte_surf(image1,keypoint1,descriptor1);
extracte_surf(image2,keypoint2,descriptor2);
match_two_image(image1,image2,keypoint1,keypoint2,descriptor1,descriptor2);
return 0;
}
surf如何保证那些sift类似的那些特点的,按照sift的想法还是很容易分析的,这里就不一一分析。
从实验结果上看,surf的特征点的提取效果还是不错的,特征点的数目也很多,而且时间上有大大的优化。论文《A comparison of SIFT, PCA-SIFT and SURF 》对三种方法给出了性能上的比较,结论是:SIFT在尺度和旋转变换的情况下效果最好,SURF在亮度变化下匹配效果最好,在模糊方面优于SIFT,而尺度和旋转的变化不及SIFT,旋转不变上比SIFT差很多。速度上看,SURF是SIFT速度的3倍。