OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/106816775
各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究
红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…(点击传送门)
上一篇:《OpenCV开发笔记(六十三):红胖子8分钟带你深入了解SIFT特征点(图文并茂+浅显易懂+程序源码)》
下一篇:持续补充中…
前言
红胖子,来也!
识别除了传统的模板匹配之外就是体征点了,前面介绍了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分钟带你深入了解SIFT特征点(图文并茂+浅显易懂+程序源码)》
下一篇:持续补充中…
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/106816775
OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)的更多相关文章
- OpenCV开发笔记(五十六):红胖子8分钟带你深入了解多种图形拟合逼近轮廓(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- OpenCV开发笔记(六十九):红胖子8分钟带你使用传统方法识别已知物体(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- OpenCV开发笔记(五十五):红胖子8分钟带你深入了解Haar、LBP特征以及级联分类器识别过程(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- OpenCV开发笔记(七十一):红胖子8分钟带你深入级联分类器训练
前言 红胖子,来也! 做图像处理,经常头痛的是明明分离出来了(非颜色的),分为几块区域,那怎么知道这几块区域到底哪一块是我们需要的,那么这部分就涉及到需要识别了. 识别可以自己写模板匹配.特征 ...
- OpenCV开发笔记(七十二):红胖子8分钟带你使用opencv+dnn+tensorFlow识别物体
前言 级联分类器的效果并不是很好,准确度相对深度学习较低,本章使用opencv通过tensorflow深度学习,检测已有模型的分类. Demo 可以猜测,1其实是人,18序号类是狗 ...
- OpenCV开发笔记(七十三):红胖子8分钟带你使用opencv+dnn+yolov3识别物体
前言 级联分类器的效果并不是很好,准确度相对深度学习较低,上一章节使用了dnn中的tensorflow,本章使用yolov3模型,识别出具体的分类. Demo 320x320,置信度0 ...
- 树莓派开发笔记(十四):入手研华ADVANTECH工控树莓派UNO-220套件(三):使用研发自带系统测试rtc、gpio、232和485套件接口
前言 上一篇说明了必须要使用研华自带的8G卡的系统,通过沟通拿到了相关的系统,购买的时候会带8GB的卡,请自行备份一份镜像.本篇对uno-220套件的相关研华配套的额外接口做测试,篇幅较长,重点讲 ...
- Java开发笔记(十四)几种运算符的优先级顺序
到目前为止,我们已经学习了Java语言的好几种运算符,包括算术运算符.赋值运算符.逻辑运算符.关系运算符等基础运算符,并且在书写赋值语句时都没添加圆括号,显然是默认了先完成算术.逻辑.关系等运算,最后 ...
随机推荐
- Nginx是如何处理一个请求
首先,nginx在启动时,会解析配置文件,得到需要监听的端口与ip地址,然后在nginx的master进程里面,先初始化好这个监控的socket(创建socket,设置addrreuse等选项,绑定到 ...
- vue中生命周期
1,说器生命周期,总觉得有熟悉,又陌生,直到看到一道面试题,问父子组件的生命周期的执行顺序,我擦,真没太注意啊,不知道. 2,网上搜了一下,说法是有点像洋葱圈的形式,由外到内,在到外,因为就像一个盒子 ...
- Map.Entry的作用
一般情况下,要输出Map中的key 和 value 是先得到key的集合,然后再迭代(循环)由每个key得到每个value 而Entry可以一次性获得这两个值 Set set = map.keySet ...
- Ant 的最完整build.xml(转)
Ant的概念 Make命令是一个项目管理工具,而Ant所实现功能与此类似.像make,gnumake和nmake这些编译工具都有一定的缺陷,但是Ant却克服了这些工具的缺陷.最初Ant开发者在开发跨平 ...
- 串口助手下载-带时间戳的串口助手-极简串口助手-V1.1 自动保存配置参数 能显示收发时间方便调试
1.串口助手下载 2.带时间戳的串口助手,每次收发指令带上了时间戳,方便调试 3.极简串口助手 4.简单易用 高速稳定 5.每次修改的参数都能自动保存,免去了重复配置的工作 下载地址:http://w ...
- Spring-boot01
本文记录Spring-Boot学习途中的点点滴滴. 其实Spring-Boot出来好长时间了,但是一直没去关注过.之前在我的印象里Spring-Boot好就好在减去了很多XML配置,加入了很多自动配置 ...
- ssm中的依赖配置
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-conte ...
- Java 第十一届 蓝桥杯 省模拟赛 字母重新排列
字母重新排列 题目 问题描述 将LANQIAO中的字母重新排列,可以得到不同的单词,如LANQIAO.AAILNOQ等,注意这7个字母都要被用上,单词不一定有具体的英文意义. 请问,总共能排列如多少个 ...
- Java实现 LeetCode 741 摘樱桃(DFS || 递推 || 传纸条)
741. 摘樱桃 一个N x N的网格(grid) 代表了一块樱桃地,每个格子由以下三种数字的一种来表示: 0 表示这个格子是空的,所以你可以穿过它. 1 表示这个格子里装着一个樱桃,你可以摘到樱桃然 ...
- Java实现 LeetCode 227 基本计算器 II(二)
227. 基本计算器 II 实现一个基本的计算器来计算一个简单的字符串表达式的值. 字符串表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格 . 整数除法仅保留整数部分. 示例 1: 输入: ...