Opencv学习之SURF算法
SURF(加速版的具有鲁棒性的特征,SpeededUp Robust Features),SURF是尺度不变特征变换算法(SIFT算法)的加速版。SURF最大的特征在于采用了harr特征以及积分图像的概念。
SURF原理:
(1)构建Hessian矩阵构造高斯金字塔尺度空间
SIFT采用的是DoG图像,而SURF采用的是Hessian矩阵(SURF算法核心)行列式近似值图像。在数学中,Hessian矩阵是一个自变量为向量的实值函数的二阶偏导数组成的方块矩阵,即每一个像素点都可以求出一个2x2的Hessian矩阵,可计算出其行列式detH,可以利用行列式取值正负来判别该点是或不是极值点来将所有点分类。在SURF算法中,选用二阶标准高斯函数作为滤波器,通过特定核间的卷积计算二阶偏导数,从而计算出Hessian矩阵,但是由于特征点需要具备尺度无关性,所以在进行Hessian矩阵构造前,需要对其进行高斯滤波,即与以方差为自变量的高斯函数的二阶导数进行卷积。通过这种方法可以为图像中每个像素计算出其H的行列式的决定值,并用这个值来判别特征点。
上面说这么多,只是得到了一张近似hessian的行列式图,类似SIFT中的DoG图。但是在金字塔图像中分为很多层, 每一层叫做一个octave,每一个octave中又有几张尺度不同的图片。在SIFT算法中,同一个octave层中的图片尺寸(大小)相同,但是尺度不同(模糊程度)不同,而不同的octave层中的图片尺寸也不相同,因为它是由上一层图片将采样得到的。在进行高斯模糊时,SIFT的高斯模板大小是始终不变的,只是在不同的octave之间改变图片的大小。而在SURF中,图片的大小是一直不变的,不同octave层的待检测图片是改变高斯模糊尺寸大小得到的,当然,同一个octave中不同图片用到的高斯模板尺寸也不同。算法允许尺度空间多层图像同时被处理,不需要对图像进行二次抽样,从而提高算法性能。
(2)利用非极大值抑制初步确定特征点
此步骤和SIFT类似,将经过hessian矩阵处理过的每个像素点与其三维邻域的26个点进行大小比较,如果它是这26个点中的最大值或者最小值,则保留下来,当作初步的特征点。检测过程中使用与该尺度层图像解析度相对应大小的滤波器进行检测。
(3)精确定位极值点
这里也和SIFT算法中类似,采用三维线性插值法得到亚像素级的特征点,同时也去掉那些值小于一定阈值的点,增加极值使检测到的特征点数量减少,最终只有几个特征最强点会被检测出来。
(4)选取特征点的主方向
这一步与SIFT也大有不同,SIFT选取特征点主方向是采用在特征点邻域统计其梯度直方图,取直方图bin值最大的以及超过bin值80%的那些方向作为特征点的主方向。
而在SURF中,不统计其梯度直方图,而是统计特征点邻域内的harr小波特征。即在特征点的邻域(比如说,半径为6s的圆内,s为该点所在的尺度)内,统计60度扇形内所有点的水平haar小波特征和垂直haar小波特征总和,haar小波的尺寸变长为4s,这样一个扇形得到了一个值,然后60度扇形以一定间隔进行旋转,最后将最大值那个扇形的方向作为该特征点的主方向。
(5)构造surf特征点描述算子
在SIFT中,是在特征点周围取16x16的邻域,并把该邻域化为4x4个的小区域,每个小区统计8个方向的梯度,最后得到4x4x8=128维的向量,该向量作为该点SIFT描述子。
在SURF中,也是在特征点周围取一个正方形框,框的边长为20s(s是所检测到该特征点所在的尺度)。该框带方向,方向当然就是第(4)步检测出来的主方向了。然后把该框分为16个子区域,每个子区域统计25个像素的水平方向和垂直方向的haar小博特征,这里的水平和垂直方向都是相对主方向而言的。该haar小波特征为水平方向值之和,水平方向绝对值之和,垂直方向之和,垂直方向绝对值之和。这样每个区域就有4个值,所以每个特征点就是16x4=64维向量,相比于SIFT而言,少了一半,这在特征匹配过程中会大大加快匹配速度。
SURF采用Hessian矩阵获取图像局部最值十分稳定,但是在求主方向阶段太过于依赖局部区域像素的梯度方向,有可能使找到的主方向不准确。后面的特征向量提取以及匹配都严重依赖于主方向,即使不大偏差角度也可以造成后面特征匹配的放大误差,从而使匹配不成功。另外图像金字塔的层取得不够紧密也会使得尺度有误差,后面的特征向量提取同样依赖响应的尺度,发明者在这个问题上的折中解决办法是取适量的层然后进行插值。
void drawKeypoints(const Mat&image, const vector& keypoints,Mat& outImage,const Scalar& color=Scalar::all(-1),int flags=DrawMatchesFlags::DEFAULT)
*第一个参数,输入图像
*第二个参数,根据源图像得到的特征点,它是一个输出参数。
*第三个参数,输出图像,其内容决定于第五个参数标识符flags。
*第四个参数,关键点的颜色,默认值Scalar::all(-1),表示颜色是随机生成的。。
*第五个参数,绘制关键点的特征标识符,默认值DrawMatchesFlags::DEFAULT。
KeyPoint类:一个为特征点检测而生的数据结构,用于表示特征点
class KeyPoint
{
Point2f pt;//坐标
float size;//特征点的邻域直径
float angle;//特征点的方向,值为[0,360度),负值表示不使用
float response;
int octave;//特征点所在的图像金字塔的组
int class_id;//用于聚类的id
}
*使用FeatureDetector接口来发现感兴趣点。
*使用SurfFeatureDetector以及其函数detect来实现检测过程。
*使用函数drawFeypoints绘制检测到的关键点。
在特征匹配中,特征描述子通常用于N维向量,在光照不变以及少许透视变形的情况下很理想。另外,优质的描述子可以通过简单的距离测量进行比较,比如欧氏距离。在Opencv中,使用SURF进行特征点描述主要是drawMatches方法和BruteForceMatcher类的运用。
drawMatches用于绘制相匹配的两个图像的关键点
void drawMatches(const Mat& img1,const vector& keypoints1,const Mat& img2,const vector& keypoints2,const vector& matches1to2,Mat& outImg,const Scalar& matchColor=Scalar::all(-1),const Scalar& singlePointColor=Scalar::all(-1),const vector& matchesMask=vector(),intflags=DrawMatchesFlags::DEFAULT
)
void drawMatches(const Mat& img1,const vector& keypoints1,const Mat& img2,const vector& keypoints2,const vector
第一步,利用SURF特征的特征描述办法,其操作封装在类SurfFeatureDetector中,利用类内的detec函数可以检测出SURF特征的关键点,保存在vector容器中。第二部,利用SurfDescriptorExtractor类进行特征向量的相关计算,将之前的vector变量变成向量矩阵形式保存在MAt中。最后,强行匹配两幅图像的特征向量,利用类BruteForceMatcher中的函数match。
*使用DescriptorExtractor接口来寻找关键点对应的特征向量。
*使用SurfDescriptorExtractor以及它的函数compute来完成特定的计算。
*使用BruteForceMatcher来匹配特征向量。
*使用函数drawMatches来绘制检测到的匹配点。
BruteForceMatcher来匹配特征向量:
首先,检测每幅图像中的特征,然后提取它们的描述子。第一幅图像中的每一个特征描述子向量都会与第二幅图中的描述子进行比较,得分最高的一对描述子(也就是两个向量的距离最近)将被视为哪个特征的最佳匹配。该过程对于第一幅图像中的所有特征进行重复。
使用FlannBasedMatcher接口以及函数FLANN(),实现快速高效匹配(快速最近邻逼近搜索函数库,Fast Library for Approximate Nearest Neighbors,FLANN)。
void DescriptorMatcher::match(
const Mat& queryDescriptors,//查询描述符集
const Mat& trainDescriptors,//训练描述符集
vector& matches,//得到的匹配,若查询描述符有在掩膜中被标记出来,则没有匹配添加到描述符中,则匹配量可能会比查询描述符数量少。
const Mat& mask=Mat()//指定输入查询和训练描述符允许匹配的掩膜
)
void DescriptorMatcher::match(
const Mat& queryDescriptors,//查询描述符集
vector& matches,//得到的匹配,若查询描述符有在掩膜中被标记出来,则没有匹配添加到描述符中,则匹配量可能会比查询描述符数量少。
const vector& masks=vector()//一组掩膜,每个masks[i]从第i个图像trainDescCollection[i]指定输入查询和训练描述符允许匹配的掩膜
)
#include
#include
using namespace cv;
using namespace std;
//主函数
int main()
{
//载入源图像,显示并转换灰度图
Mat srcImage=imread("/Users/new/Desktop/5.jpg"),grayImage;
namedWindow("image[origin]",1);
imshow("image[origin]",srcImage);
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
//检测surf关键点、提取训练图像描述符
vector keyPoint;
Mat descriptor;
SurfFeatureDetector featureDetector(80);
featureDetector.detect(grayImage,keyPoint);
SurfDescriptorExtractor featureExtractor;
featureExtractor.compute(grayImage, keyPoint, descriptor);
//创建基于FLANN的描述符匹配对象
FlannBasedMatcher matcher;
vector desc_collection(1,descriptor);
matcher.add(desc_collection);
matcher.train();
//创建视频对象、定义帧率
VideoCapture cap(0);
unsigned int frameCount=0;//帧数
//不断循环,直到q键被按下
while(char(waitKey(1))!=27)
{
//参数设置
int64 time0=getTickCount();
Mat testImage,grayImage_test;
cap>>testImage;//采集视频到testImage中
if(testImage.empty())
continue;
//转换图像到灰度
cvtColor(testImage, grayImage_test, COLOR_BGR2GRAY);
//检测S关键点、提取测试图像描述符
vector keyPoint_test;
Mat descriptor_test;
featureDetector.detect(grayImage_test, keyPoint_test);
featureExtractor.compute(grayImage_test, keyPoint_test, descriptor_test);
//匹配训练和测试描述符
vector<vector >matches;
matcher.knnMatch(descriptor_test, matches, 2);
//根据劳氏算法,得到优秀的匹配点
vector goodMatches;
for(unsigned int i=0;iif(matches[i][0].distance<0.6*matches[i][1].distance)
goodMatches.push_back(matches[i][0]);
}
//绘制匹配点并显示窗口
Mat dstImage;
drawMatches(testImage, keyPoint_test, srcImage, keyPoint, goodMatches, dstImage);
namedWindow("image[match]",1);
imshow("image[match]",dstImage);
//输出帧率信息
cout<<"当前帧率为:"<return 0;
}
##寻找已知物体
在FLANN特征匹配的基础上,还可以进一步利用Homography映射找出已知物体,也就是利用findHomography函数通过匹配的关键点找出相应的变换,再利用perspectiveTransform函数映射映射点群。
函数的作用是找到并返回源图像和目标图像之间的透视变换H
Mat findHomography(inputArray srcpoints,inputArray dstpoints,int method=0,double ransacReprojThreshold=3,outputArray mask=noArray())
*第一个参数,源平面上的对应点,可以是CV_32FC2的矩阵类型或者vector
*第二个参数,目标平面上的对应点,可以是CV_32FC2的矩阵类型或者vector
*第三个参数,用于计算单应矩阵的方法。
(1)0:使用所有点的常规方法(默认值)
(2)CV_RANSAC:基于RANSAC的鲁棒性方法
(3)CV_LMEDS:最小中值鲁棒性方法
*第四个参数,有默认值3,处理点对为内围层时,允许重投影误差的最大值,若secPoints和dstpoints是以像素为单位的,那么此参数的取值范围一般在1到10之间。
*第五个参数,默认值noArray(),通过上面讲到的鲁棒性方法(CV_RANSAC或者CV_LMEDS)设置输出掩码,需要注意的是,输入掩码值会被忽略掉。
函数的作用是进行向量透视矩阵变换
void perspectiveTransform(inputArray src,outputArray dst,inputArray m)
*第一个参数,输入图像,且需要为双通道或者三通道浮点型图像,其中的每个元素是二维或三维可被转换的矢量。
*第二个参数,存放函数调用后的输出结果,需要和源图片有一样的尺寸和类型。
*第三个参数,变换矩阵,为3x3或者4x4浮点型矩阵。
#include
#include
int main()
{
//载入源图像
cv::Mat srcImage1=cv::imread("/Users/new/Desktop/4.jpg");
cv::Mat srcImage2=cv::imread("/Users/new/Desktop/3.jpg");
//使用SURF算子检测关键点
int minHessian=400;//SURF算法中的hessian阈值
cv::SurfFeatureDetector detector(minHessian);
std::vector keyPoints_object,keyPoints_scene;
//调用detect函数检测出SURF特征关键点
detector.detect(srcImage1, keyPoints_object);
detector.detect(srcImage2, keyPoints_scene);
//计算描述符(特征向量)
cv::SurfDescriptorExtractor extractor;
cv::Mat descriptor_object,descriptor_scene;
extractor.compute(srcImage1, keyPoints_object, descriptor_object);
extractor.compute(srcImage2, keyPoints_scene, descriptor_scene);
//使用FLANN匹配算子进行匹配
cv::FlannBasedMatcher matcher;
std::vector matches;
matcher.match(descriptor_object, descriptor_scene, matches);
double max_dist=0;double min_dist=100;//最小距离和最大距离
//计算出关键点之间距离的最大值和最小值
for(int i=0;idouble dist=matches[i].distance;
if(distif(dist>max_dist)max_dist=dist;
}
printf(">Max dist最大距离 : %f \n",max_dist);
printf(">Min dist最小距离 : %f \n",min_dist);
//存下匹配距离小于3*min_dist的点对
std::vector good_matches;
for(int i=0;iif(matches[i].distance<3*min_dist)
{
good_matches.push_back(matches[i]);
}
}
//绘制出匹配到的关键点
cv::Mat img_matches;
cv::drawMatches(srcImage1,keyPoints_object,srcImage2,keyPoints_scene,good_matches,img_matches,cv::Scalar::all(-1),cv::Scalar::all(-1),std::vector<char>(),cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
//定义两个局部变量
std::vector obj;
std::vector scene;
//从匹配成功的匹配对中获取关键点
for(unsigned int i=0;i3,cv::noArray());//计算透视变换
//从待测图片中获取角点
std::vector obj_corners(4);
obj_corners[0]=cvPoint(0,0);obj_corners[1]=cvPoint(srcImage1.cols,0);
obj_corners[2]=cvPoint(srcImage1.cols, srcImage1.rows);
obj_corners[3]=cvPoint(0,srcImage1.rows);
std::vector scene_corners(4);
//进行透视变换
cv::perspectiveTransform(obj_corners, scene_corners, H);
//绘制出角点之间的直线
line(img_matches,scene_corners[0]+cv::Point2f(static_cast<float>(srcImage1.cols),0),scene_corners[1]+cv::Point2f(static_cast<float>(srcImage1.cols),0),cv::Scalar(255,0,123),4);
line(img_matches,scene_corners[1]+cv::Point2f(static_cast<float>(srcImage1.cols),0),scene_corners[2]+cv::Point2f(static_cast<float>(srcImage1.cols),0),cv::Scalar(255,0,123),4);
line(img_matches,scene_corners[2]+cv::Point2f(static_cast<float>(srcImage1.cols),0),scene_corners[3]+cv::Point2f(static_cast<float>(srcImage1.cols),0),cv::Scalar(255,0,123),4);
line(img_matches,scene_corners[3]+cv::Point2f(static_cast<float>(srcImage1.cols),0),scene_corners[0]+cv::Point2f(static_cast<float>(srcImage1.cols),0),cv::Scalar(255,0,123),4);
//显示最终结果
imshow("Good Matches & Object detection",img_matches);
cv::waitKey(0);
return 0;
}
(1)vector模板类是能够存放任意类型的动态数组,能够增加和压缩数据
(2)利用SurfFeatureDetector定义一个SurfFeatureDetector(SURF)特征检测类对象。并使用detect函数检测出SURF特征关键点,保存在vector容器中。
SurfFeatureDetector detector(minHessian);//minHessian为SURF算法中hessian阈值
std::vectorkeyPoint1,keyPoint2;//vector模板类,存放任意类型的动态数组
–detect()
(3)计算描述符:
SurfDescriptorExtractor extractor;
Mat descriptor1,descriptor2;–compute()
(4)使用BruteForce进行匹配:
BruteForceMatcher
VideoCapture cap(0);
cap.set(CV_CAP_PROP_FRAME_WIDTH,360);//设置采集视频的宽度
cap.set(CV_CAP_PROP_FRAME_HEIGHT,900);//设置采集视频的高度