在基础篇里面讲模板匹配的时候已经介绍过,图像匹配主要有基于灰度和基于特征两种方法。基于特征匹配的方法有很多种如:FAST、HARRIS、SIFT、SURF、SUSAN等。其中SIFT算法由D.G.Lowe于1999年提出,2004年完善总结。SIFT是一种鲁棒性好的尺度不变特征描述方法,但SIFT算法计算数据量大、时间复杂度高、算法耗时长。针对上述缺点许多研究者对SIFT算法做了不同的改进,Yanke等人提出用PCA-SIFT方法对特征描述进行数据降维,但在没有任何先验知识的情况下反而增加了计算量;Delpont等人提出用SVD方法进行特征匹配,但匹配过程计算复杂,且不能用于宽基线匹配;Grabner等人用积分图像虽提高了SIFT的计算速度,但是降低了SIFT方法的优越性。
Herbert Bay等人于2006年提出了SIFT算法的改进算法SURF算法,其性能超过了SIFT算法且能够获得更快的速度。SURF在光照变化和视角变化不变性方面的性能接近SIFT算法,尤其对图像严重模糊和旋转处理得非常好。且标准的SURF算子比SIFT算子快好几倍,SURF算法最大的特征在于采用了harr特征以及积分图像的概念,这大大加快了程序的运行速度。
SURF算法和SIFT算法在opencv中是一种很高级的算法,opencv提供了SURF算法的API接口。需要说明的是SURF和SIFT算法在OpenCV提供的nonfree中,而且在OpenCV3.x后不再集成到OpenCV发行版中,同意存放到opencv_contrib中,需要去opencv github页面手动下载编译。点击跳转,编译方法网上有很多教程。
特征点的提取基于尺度空间理论,我们通过检测图像局部极值点来锁定特征点坐标,即局部的最亮点或最暗点。
SURF算法检测特征点是基于Hessian矩阵实现的,Hessian矩阵是SURF算法的核心。设f(x,y)为二阶可微函数,Hessian矩阵H是函数、偏导数组成如下:
Hessian矩阵判别式为:
判别式的值是H矩阵的特征值,可以利用判别式的符号确定是否是极值,若det(H)<0则可判断(x, y)不是局部极值点,若det(H)>0则可判断点(x, y)为局部极值点。
将上述方法应用到图像中,给出图像中的一个点,其像素可表示为I(x, y),在尺度为σ其Hessian矩阵定义如下:
式中:L_xx是高斯滤波二阶导同I=(x,y)卷积的结果,其中L_xy、L_yy的含义类似。
空间尺度理论中高斯是最优化的分析方法。Bay等人指出高斯分析需要对图像进行离散化和裁剪,即使使用高斯滤波对图像进行采样也会出现走样的情况。所以可以使用方框滤波来代替高斯滤波,使用积分图像来加快卷积以提高计算速度。
在原始图像上,使用方框滤波器的效果反映在掩膜版尺寸上。如图所示为9×9方框滤波掩膜版,其中灰色部分掩膜版值为0.对应二阶高斯滤波系数σ=1.2,方框滤波模板同图像卷积运算后的值记分别记为D_xx,D_yy,D_xy。
为平衡准确值与近似值间的误差引入权值,权值随尺度变化,H矩阵判别式可以表示为:
由于高斯滤波与近似高斯滤波的差异性,我们用根据公式计算滤波响应的相对权重系数进一步平衡Hessian矩阵行列式的相对权重,其中|��|��是Frobenius范数,这样就保证了Frobenius范数能够适用于任何尺寸的滤波器模板。在实际应用中,用常量0.9表示其相对权重系数不会对结果产生较大的影响。
为了使图像具有尺度不变性以适应不同的图像中目标尺度的变化,我们需要构建尺度空间进行SURF特征点的提取。图像金字塔是图像多尺度表达的一种方式,为了获取图像在不同尺度下通过Hessian矩阵判别式得到极值点,用类似SIFT的方法构建尺度图像金字塔,将尺度空间分为若干阶(octave),每一阶存储了不同尺寸的方框滤波对输入图像进行滤波后得到的模糊程度不同的图片。但SURF算法中图片大小是一直不变的,只是不同阶中方框滤波模板大小不相同。在每一阶中选择4层的尺度图像,构建参数如图所示:
灰色底的数字表示方框滤波模板的大小。如果图像尺寸远大于模板大小,还可以继续增加阶数。若模板尺寸为N×N,则该模板对应的尺度为σ=1.2×9/N。通过Hessian矩阵求出个尺度极值后,在3×3×3的立体邻域内进行非极大值比较,若该极值点仍为最大值或最小值,则该极值点为候选特征点,然后在尺度空间和图像空间中进行插值运算,得到稳定的特征点位置及所在的尺度值。
SIFT算法选取特征点主方向是采用在特征点领域内统计其梯度直方图,取直方图bin值最大的以及超过最大bin值80%的那些方向作为特征点的主方向。而SURF算法是通过统计特征点领域内的Haar小波特征确定其主方向。
为保证旋转不变性,以特征点为中心,计算特征点邻域(如半径为6s的圆,s为该点所在尺度)内的点在x,y方向的Haar小波响应,Haar小波边长取4s,这样一个扇形得到了一个值。然后60°扇形以一定间隔进行旋转,将60°范围内的响应相加以形成新的矢量,遍历整个圆形区域,选择最长矢量的方向为该特征点的主方向。
SURF算法中,以特征点为中心,将坐标轴旋转到主方向以确保旋转不变性。按照主方向选取边长为20s的正方形区域,然后将该区域划分为4×4的子区域,每个子区域计算5s×5s范围内的小波响应。
相对于主方向的水平、垂直方向的Haar小波响应分别记做d_x 、d_y,同样赋予响应值以权值系数,以增加对集合变换的鲁棒性;之后将每个子区域的响应及其绝对值相加形成,这样在每个子区域形成四维分量的矢量,因此对每一个特征点,则形成64维的描述向量,再进行向量的归一化,从而对光照具有一定的鲁棒性。
在opencv中可以看到如下定义
typedef SURF cv::xfeatures2d::SurfDescriptorExtractor
typedef SURF cv::xfeatures2d::SurfFeatureDetector
也就是说在我们实际使用中是根据不同的功能分别调用SurfDescriptorExtractor和SurfFeatureDetector两个函数的,同样SIFT算法也有类似定义如下:
typedef SIFT cv::xfeatures2d::SiftDescriptorExtractor
typedef SIFT cv::xfeatures2d::SiftFeatureDetector
有需要使用SIFT算法的同学可以参考下。重点说一下SURF算法的两个函数。
SURF算法作为一个大类,其继承关系可参照下图:
其成员函数有很多,如下:
virtual bool getExtended () const =0
virtual double getHessianThreshold () const =0
virtual int getNOctaveLayers () const =0
virtual int getNOctaves () const =0
virtual bool getUpright () const =0
virtual void setExtended (bool extended)=0
virtual void setHessianThreshold (double hessianThreshold)=0
virtual void setNOctaveLayers (int nOctaveLayers)=0
virtual void setNOctaves (int nOctaves)=0
virtual void setUpright (bool upright)=0
函数详细含义可以查询OpenCV文档得知。这里介绍一下特征点的检测。
利用SURF算法进行特征点检测可以使用SurfFeatureDetector及它的子函数detect(位于其父类Feature2D中)来实现检测过程,使用drawKeypoints函数绘制检测到的关键点。
drawKeypoints
void cv::drawKeypoints ( InputArray image,
const std::vector< KeyPoint > & keypoints,
InputOutputArray outImage,
const Scalar & color = Scalar::all(-1),
int flags = DrawMatchesFlags::DEFAULT
)
image:输入图像
**keyPoint:**SURF算法检测到的特征点
outImage:输出图像,其内容取决于第五个参数标识符
color:绘制特征点颜色,有默认值Scalar::all(-1)
flags:绘制特征点的特征标识符,有默认值,有如下方式:
enum {
DEFAULT = 0, //创建输出图像矩阵,使用现存的输出图像绘制匹配对和特征点,对每一个关键点只绘制中间点
DRAW_OVER_OUTIMG = 1,//不创建输出图像矩阵,而是在输出图像上绘制匹配对
NOT_DRAW_SINGLE_POINTS = 2, //单点特征点不被绘制
DRAW_RICH_KEYPOINTS = 4 //对每一个特征点绘制带大小和方向的关键点图形。
}
这里有必要说一下KeyPoint()类,是一个为特征点检测而形成的数据结构,用于表示特征点,其类结构如下:
cv::KeyPoint::KeyPoint ( Point2f _pt,
float _size,
float _angle = -1,
float _response = 0,
int _octave = 0,
int _class_id = -1
)
_pt:特征点坐标
_size特征点直径
_angle:特征点方向,范围为[0,360),负值表示不使用
_response:关键点检测器对于关键点的响应程度,也就是关键点强度
_octave:特征点所在金字塔的层
_class_id:用于聚类的id
函数还有另外一种定义形式如下:
cv::KeyPoint::KeyPoint ( float x,
float y,
float _size,
float _angle = -1,
float _response = 0,
int _octave = 0,
int _class_id = -1
)
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat srcImage = imread("Surf_test_image.jpg");
//判断图像是否读取成功
if (srcImage.empty())
{
cout << "图像加载失败" << endl;
return -1;
}
else
{
cout << "图像加载成功" << endl << endl;
}
namedWindow("原图像",WINDOW_AUTOSIZE);
imshow("原图像",srcImage);
Mat imageMid; //定义滤波后图像
//GaussianBlur(srcImage,imageMid,Size(9, 9),0,0); //kernel尺寸为3x3的高斯滤波
//namedWindow("高斯滤波后图像",WINDOW_AUTOSIZE);
//imshow("高斯滤波后图像",imageMid);
int minHessian = 700; //定义Hessian矩阵阈值特征点检测算子
SurfFeatureDetector detector(minHessian); //定义SURF检测器
vector keypoints; //定义KeyPoint类型的矢量容器vector存储检测到的特征点
detector.detect(srcImage,keypoints); //调用detect检测特征点
//绘制检测到的特征点
Mat dstImage;
//drawKeypoints(imageMid,keypoints,dstImage,Scalar::all(-1),DrawMatchesFlags::DEFAULT); //高斯滤波后关键点检测
drawKeypoints(srcImage, keypoints, dstImage, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
namedWindow("特征点检测",WINDOW_AUTOSIZE);
imshow("特征点检测",dstImage);
waitKey(0);
return 0;
}