OpenCV开发笔记六十四红胖子8分钟带你深入了解SURF特征点图文并茂浅显易懂程序源码

22次阅读

共计 8042 个字符,预计需要花费 21 分钟才能阅读完成。

若该文为原创文章,未经允许不得转载
原博主博客地址: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 开发专栏(点击传送门)

上一篇:《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<KeyPoint> 类型的 keypoints,检测到的关键点;
  • 参数三:InputArray 类型的 mask,默认为空,指定在何处查找关键点的掩码(可选)。它必须是 8 位整数感兴趣区域中具有非零值的矩阵。;
void xfeatures2d::SURT::compute( InputArray image,
                                 std::vector<KeyPoint>& keypoints,
                                 OutputArray descriptors );
  • 参数一:InputArray 类型的 image,输入 cv::Mat;
  • 参数二:std::Vector<KeyPoint> 类型的 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

正文完
 0