前言

  红胖子,来也!
  识别除了传统的模板匹配之外就是体征点了,前面介绍了SIFT特征点,而SUFT是改进后SIFT特征点。

 

Demo

  
  
  
  

 

SURF特征点

概述

  SURF,即加速稳健特征(Speeded Up Robust Features)是一个稳健的图像识别和描述算法,首先于2006年发表在欧洲计算机视觉国际会议(Europeon Conference on Computer Vision,ECCV)。该算法可被用于计算机视觉任务,如物件识别和3D重构。他部分的灵感来自于SIFT算法。SURF标准的版本比SIFT要快数倍,并且其作者声称在不同图像变换方面比SIFT更加稳健。 SURF 基于近似的2D 离散小波变换响应和并且有效地利用了积分图。
  在 SIFT 中, Lowe 在构建尺度空间时使用 DoG 对 LoG 进行近似。 SURF使用盒子滤波器(box_filter)对 LoG 进行近似。在进行卷积计算时可以利用积分图像(积分图像的一大特点是:计算图像中某个窗口内所有像素和时,计算量的大小与窗口大小无关) ,是盒子滤波器的一大优点。而且这种计算可以在不同尺度空间同时进行。同样 SURF 算法计算关键点的尺度和位置是也是依赖与 Hessian 矩阵行列式的。
  SURF算法采用了很多方法来对每一步进行优化从而提高速度。分析显示在结果效果相当的情况下SURF的速度是SIFT的3倍。SURF善于处理具有模糊和旋转的图像,但是不善于处理视角变化和光照变化。(SIFT特征是局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、噪声也保持一定程度的稳定性)

特征检测步骤

步骤一:构造Hessian矩阵,计算特征值α

  surf构造的金字塔图像与sift有很大不同,就是因为这些不同才加快了其检测的速度。Sift采用的是DOG图像,而surf采用的是Hessian矩阵行列式近似值图像。首先是图像中某个像素点的Hessian矩阵:

步骤二:构造高斯金字塔

  相比于sift算法的高斯金字塔构造过程,sift算法速度有所提高。
  在sift算法中,每一组(octave)的图像大小是不一样的,下一组是上一组图像的降采样(1/4大小);在每一组里面的几幅图像中,他们的大小是一样的,不同的是他们采用的尺度σ不同。
而且在模糊的过程中,他们的高斯模板大小总是不变的,只是尺度σ改变。对于surf算法,图像的大小总是不变的,改变的只是高斯模糊模板的尺寸,当然,尺度σ也是在改变的。
  
  上图中a为高斯模板保持不变,图像大小改变的情况,适用于sift算法,图b是高斯模板改变,图像大小保持不变的情况,适用于surf算法。因为surf算法没有了降采样的过程,因此处理速度得到提高。

步骤三:关键点定位

  在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。

步骤四:方向确定

  将经过hessian矩阵处理过的每个像素点与其3维领域的26个点进行大小比较,如果它是这26个点中的最大值或者最小值,则保留下来,当做初步的特征点。
  

步骤五:关键点描述

  在特征点周围取一个正方形框,框的边长为20s(s是所检测到该特征点所在的尺度)。该框带方向,方向当然就是第4步检测出来的主方向了。然后把该框分为16个子区域,每个子区域统计25个像素的水平方向和垂直方向的haar小波特征,这里的水平和垂直方向都是相对主方向而言的。该haar小波特征为水平方向值之和,水平方向绝对值之和,垂直方向之和,垂直方向绝对值之和。该过程的示意图如下所示:
  

SurfFeatureDetector类

  该类是opencv中nonfree模块中的,之前没有勾选,需要需要重新勾选编译才会有的,所以按照3.4的最新版本为3.4.10,笔者重新编译了一个版本,带contrib模块,编译请参考博文《OpenCV开发笔记(三十四):红胖子带你小白式编译Qt+openCV3.4.1+opencv_contrib(全网最简单最强,可读性最高,没有之一)》,配置时,需要额外勾选下图显示的项:
  
  编译好后,头文件和库替换,重新连接到3.4.10版本,要使用surf,需要添加头文件:

#include <opencv2/xfeatures2d/nonfree.hpp>

  
  编译时仍然报错,如下:
  
  但是确实勾选了,配置也打出来了支持nonfree模块,也许是编译抽风了,所以重新编译一下,发现还是一样错误,索性直接定位到错误文件,修改源码,
  如下图:
  
  编译可以成功了。

SurfFeatureDetector类的使用

cv::Ptr<cv::xfeatures2d::SURF> _pSurf = cv::xfeatures2d::SurfFeatureDetector::create();
std::vector<cv::KeyPoint> keyPoints1;
//特征点检测
_pSurf->detect(srcMat, keyPoints1);

SURF宏定义

typedef SURF SurfFeatureDetector;
typedef SURF SurfDescriptorExtractor;

SURF相关函数原型

static Ptr<SURF> create(double hessianThreshold=100,
int nOctaves = 4,
int nOctaveLayers = 3,
bool extended = false,
bool upright = false);
  • 参数一:double类型的hessianThreshold,默认值100,用于SURF的hessian关键点检测器的阈值;
  • 参数二:int类型的nOctaves,默认值4,关键点检测器将使用的金字塔倍频程数;
  • 参数三:int类型的nOctaveLayers,默认值3,每八度音阶的层数。3是D.Lowe纸张中使用的值。这个八度音阶数是根据图像分辨率自动计算出来的;
  • 参数四:bool类型的extended,扩展描述符标志(true-使用扩展的128个元素描述符;false-使用64个元素描述符),默认false;
  • 参数五:bool类型的upright,向上向右或旋转的特征标志(true-不计算特征的方向;faslse-计算方向),默认false;
void xfeatures2d::SURT::detect( InputArray image,
std::vector<KeyPoint>& keypoints,
InputArray mask=noArray() );
  • 参数一:InputArray类型的image,输入cv::Mat;
  • 参数二:std::Vector类型的keypoints,检测到的关键点;
  • 参数三:InputArray类型的mask,默认为空,指定在何处查找关键点的掩码(可选)。它必须是8位整数感兴趣区域中具有非零值的矩阵。;
void xfeatures2d::SURT::compute( InputArray image,
std::vector<KeyPoint>& keypoints,
OutputArray descriptors );
  • 参数一:InputArray类型的image,输入cv::Mat;
  • 参数二:std::Vector类型的keypoints,描述符不能为其已删除计算的。有时可以添加新的关键点,例如:SIFT duplicates keypoint有几个主要的方向(每个方向);
  • 参数三:OutputArray类型的descriptors,计算描述符;
// 该函数结合了detect和compute,参照detect和compute函数参数
void xfeatures2d::SURT::detectAndCompute( InputArray image,
InputArray mask,
std::vector<KeyPoint>& keypoints,
OutputArray descriptors,
bool useProvidedKeypoints=false );

绘制关键点函数原型

void drawKeypoints( InputArray image,
const std::vector<KeyPoint>& keypoints,
InputOutputArray outImage,
const Scalar& color=Scalar::all(-1),
int flags=DrawMatchesFlags::DEFAULT );
  • 参数一:InputArray类型的image,;
  • 参数二:std::Vector<KeyPoint>类型的keypoints,原图的关键点;
  • 参数三:InputOutputArray类型的outImage,其内容取决于定义在输出图像。请参阅参数五的标志flag);
  • 参数四:cv::Scalar类型的color,绘制关键点的颜色,默认为Scalar::all(-1)随机颜色,每个点都是这个颜色,那么随机时,每个点都是随机的;
  • 参数五:int类型的flags,默认为DEFAULT,具体参照DrawMatchesFlags枚举如下:
      
 

相关博客

  本源码中包含了“透视变换”,请参照博文《OpenCV开发笔记(五十一):红胖子8分钟带你深入了解透视变换(图文并茂+浅显易懂+程序源码)》

 

Demo源码

void OpenCVManager::testSurfFeatureDetector()
{
QString fileName1 = "13.jpg";
int width = 400;
int height = 300; cv::Mat srcMat = cv::imread(fileName1.toStdString());
cv::resize(srcMat, srcMat, cv::Size(width, height)); cv::String windowName = _windowTitle.toStdString();
cvui::init(windowName); cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 3),
srcMat.type()); cv::Ptr<cv::xfeatures2d::SURF> _pSurf = cv::xfeatures2d::SurfFeatureDetector::create(); int k1x = 0;
int k1y = 0;
int k2x = 100;
int k2y = 0;
int k3x = 100;
int k3y = 100;
int k4x = 0;
int k4y = 100;
while(true)
{
windowMat = cv::Scalar(0, 0, 0); cv::Mat mat; // 原图先copy到左边
mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, srcMat, 1.0f, 0.0f, mat); {
std::vector<cv::KeyPoint> keyPoints1;
std::vector<cv::KeyPoint> keyPoints2; cvui::printf(windowMat, 0 + width * 1, 10 + height * 0, "k1x");
cvui::trackbar(windowMat, 0 + width * 1, 20 + height * 0, 165, &k1x, 0, 100);
cvui::printf(windowMat, 0 + width * 1, 70 + height * 0, "k1y");
cvui::trackbar(windowMat, 0 + width * 1, 80 + height * 0, 165, &k1y, 0, 100); cvui::printf(windowMat, width / 2 + width * 1, 10 + height * 0, "k2x");
cvui::trackbar(windowMat, width / 2 + width * 1, 20 + height * 0, 165, &k2x, 0, 100);
cvui::printf(windowMat, width / 2 + width * 1, 70 + height * 0, "k2y");
cvui::trackbar(windowMat, width / 2 + width * 1, 80 + height * 0, 165, &k2y, 0, 100); cvui::printf(windowMat, 0 + width * 1, 10 + height * 0 + height / 2, "k3x");
cvui::trackbar(windowMat, 0 + width * 1, 20 + height * 0 + height / 2, 165, &k3x, 0, 100);
cvui::printf(windowMat, 0 + width * 1, 70 + height * 0 + height / 2, "k3y");
cvui::trackbar(windowMat, 0 + width * 1, 80 + height * 0 + height / 2, 165, &k3y, 0, 100); cvui::printf(windowMat, width / 2 + width * 1, 10 + height * 0 + height / 2, "k4x");
cvui::trackbar(windowMat, width / 2 + width * 1, 20 + height * 0 + height / 2, 165, &k4x, 0, 100);
cvui::printf(windowMat, width / 2 + width * 1, 70 + height * 0 + height / 2, "k4y");
cvui::trackbar(windowMat, width / 2 + width * 1, 80 + height * 0 + height / 2, 165, &k4y, 0, 100); std::vector<cv::Point2f> srcPoints;
std::vector<cv::Point2f> dstPoints; srcPoints.push_back(cv::Point2f(0.0f, 0.0f));
srcPoints.push_back(cv::Point2f(srcMat.cols - 1, 0.0f));
srcPoints.push_back(cv::Point2f(srcMat.cols - 1, srcMat.rows - 1));
srcPoints.push_back(cv::Point2f(0.0f, srcMat.rows - 1)); dstPoints.push_back(cv::Point2f(srcMat.cols * k1x / 100.0f, srcMat.rows * k1y / 100.0f));
dstPoints.push_back(cv::Point2f(srcMat.cols * k2x / 100.0f, srcMat.rows * k2y / 100.0f));
dstPoints.push_back(cv::Point2f(srcMat.cols * k3x / 100.0f, srcMat.rows * k3y / 100.0f));
dstPoints.push_back(cv::Point2f(srcMat.cols * k4x / 100.0f, srcMat.rows * k4y / 100.0f)); cv::Mat M = cv::getPerspectiveTransform(srcPoints, dstPoints);
cv::Mat srcMat2;
cv::warpPerspective(srcMat,
srcMat2,
M,
cv::Size(srcMat.cols, srcMat.rows),
cv::INTER_LINEAR,
cv::BORDER_CONSTANT,
cv::Scalar::all(0)); mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 1, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, srcMat2, 1.0f, 0.0f, mat); //特征点检测
_pSurf->detect(srcMat, keyPoints1);
//绘制特征点(关键点)
cv::Mat resultShowMat;
cv::drawKeypoints(srcMat,
keyPoints1,
resultShowMat,
cv::Scalar(0, 0, 255),
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, resultShowMat, 1.0f, 0.0f, mat); //特征点检测
_pSurf->detect(srcMat2, keyPoints2);
//绘制特征点(关键点)
cv::Mat resultShowMat2;
cv::drawKeypoints(srcMat2,
keyPoints2,
resultShowMat2,
cv::Scalar(0, 0, 255),
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
cv::Range(srcMat.cols * 1, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, resultShowMat2, 1.0f, 0.0f, mat);
cv::imshow(windowName, windowMat);
}
// 更新
cvui::update();
// 显示
// esc键退出
if(cv::waitKey(25) == 27)
{
break;
}
}
}
 

工程模板:对应版本号v1.58.0

  对应版本号v1.58.0

 
 

OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)的更多相关文章

  1. OpenCV开发笔记(五十六):红胖子8分钟带你深入了解多种图形拟合逼近轮廓(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  2. OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  3. OpenCV开发笔记(六十九):红胖子8分钟带你使用传统方法识别已知物体(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  4. OpenCV开发笔记(五十五):红胖子8分钟带你深入了解Haar、LBP特征以及级联分类器识别过程(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  5. OpenCV开发笔记(七十一):红胖子8分钟带你深入级联分类器训练

    前言   红胖子,来也!  做图像处理,经常头痛的是明明分离出来了(非颜色的),分为几块区域,那怎么知道这几块区域到底哪一块是我们需要的,那么这部分就涉及到需要识别了.  识别可以自己写模板匹配.特征 ...

  6. OpenCV开发笔记(七十二):红胖子8分钟带你使用opencv+dnn+tensorFlow识别物体

    前言   级联分类器的效果并不是很好,准确度相对深度学习较低,本章使用opencv通过tensorflow深度学习,检测已有模型的分类.   Demo       可以猜测,1其实是人,18序号类是狗 ...

  7. OpenCV开发笔记(七十三):红胖子8分钟带你使用opencv+dnn+yolov3识别物体

      前言   级联分类器的效果并不是很好,准确度相对深度学习较低,上一章节使用了dnn中的tensorflow,本章使用yolov3模型,识别出具体的分类.   Demo   320x320,置信度0 ...

  8. 树莓派开发笔记(十四):入手研华ADVANTECH工控树莓派UNO-220套件(三):使用研发自带系统测试rtc、gpio、232和485套件接口

    前言   上一篇说明了必须要使用研华自带的8G卡的系统,通过沟通拿到了相关的系统,购买的时候会带8GB的卡,请自行备份一份镜像.本篇对uno-220套件的相关研华配套的额外接口做测试,篇幅较长,重点讲 ...

  9. Java开发笔记(十四)几种运算符的优先级顺序

    到目前为止,我们已经学习了Java语言的好几种运算符,包括算术运算符.赋值运算符.逻辑运算符.关系运算符等基础运算符,并且在书写赋值语句时都没添加圆括号,显然是默认了先完成算术.逻辑.关系等运算,最后 ...

随机推荐

  1. Nginx是如何处理一个请求

    首先,nginx在启动时,会解析配置文件,得到需要监听的端口与ip地址,然后在nginx的master进程里面,先初始化好这个监控的socket(创建socket,设置addrreuse等选项,绑定到 ...

  2. vue中生命周期

    1,说器生命周期,总觉得有熟悉,又陌生,直到看到一道面试题,问父子组件的生命周期的执行顺序,我擦,真没太注意啊,不知道. 2,网上搜了一下,说法是有点像洋葱圈的形式,由外到内,在到外,因为就像一个盒子 ...

  3. Map.Entry的作用

    一般情况下,要输出Map中的key 和 value 是先得到key的集合,然后再迭代(循环)由每个key得到每个value 而Entry可以一次性获得这两个值 Set set = map.keySet ...

  4. Ant 的最完整build.xml(转)

    Ant的概念 Make命令是一个项目管理工具,而Ant所实现功能与此类似.像make,gnumake和nmake这些编译工具都有一定的缺陷,但是Ant却克服了这些工具的缺陷.最初Ant开发者在开发跨平 ...

  5. 串口助手下载-带时间戳的串口助手-极简串口助手-V1.1 自动保存配置参数 能显示收发时间方便调试

    1.串口助手下载 2.带时间戳的串口助手,每次收发指令带上了时间戳,方便调试 3.极简串口助手 4.简单易用 高速稳定 5.每次修改的参数都能自动保存,免去了重复配置的工作 下载地址:http://w ...

  6. Spring-boot01

    本文记录Spring-Boot学习途中的点点滴滴. 其实Spring-Boot出来好长时间了,但是一直没去关注过.之前在我的印象里Spring-Boot好就好在减去了很多XML配置,加入了很多自动配置 ...

  7. ssm中的依赖配置

    <dependency> <groupId>org.springframework</groupId> <artifactId>spring-conte ...

  8. Java 第十一届 蓝桥杯 省模拟赛 字母重新排列

    字母重新排列 题目 问题描述 将LANQIAO中的字母重新排列,可以得到不同的单词,如LANQIAO.AAILNOQ等,注意这7个字母都要被用上,单词不一定有具体的英文意义. 请问,总共能排列如多少个 ...

  9. Java实现 LeetCode 741 摘樱桃(DFS || 递推 || 传纸条)

    741. 摘樱桃 一个N x N的网格(grid) 代表了一块樱桃地,每个格子由以下三种数字的一种来表示: 0 表示这个格子是空的,所以你可以穿过它. 1 表示这个格子里装着一个樱桃,你可以摘到樱桃然 ...

  10. Java实现 LeetCode 227 基本计算器 II(二)

    227. 基本计算器 II 实现一个基本的计算器来计算一个简单的字符串表达式的值. 字符串表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格 . 整数除法仅保留整数部分. 示例 1: 输入: ...