Opencv学习笔记 特征检测和描述模块(2) SIFT类(提取关键点和计算描述符)

一、概述

        2004 年,不列颠哥伦比亚大学的D.Lowe在他的论文Distinctive Image Features from Scale-Invariant Keypoints 中提出了一种新算法 Scale Invariant Feature Transform (SIFT) ,该算法提取关键点并计算其描述符。

        SIFT算法主要涉及四个步骤。

1、尺度空间极值检测

        从上图可以看出,我们不能使用同一个窗口来检测不同尺度的关键点。小的角点也没有问题。但是要检测更大的角点,我们需要更大的窗口。为此,使用了尺度空间过滤。其中,高斯拉普拉斯算子对具有各种σ价值观。LoG 充当斑点检测器,可检测由于变化引起的各种大小的斑点σ. 简而言之,σ充当缩放参数。例如,在上图中,具有低的高斯核σ为小角提供高价值,而高斯核具有高σ非常适合较大的角落。所以,我们可以在尺度和空间上找到局部最大值,这给了我们一个列表( x , y, σ)值,这意味着在 (x,y) 处有一个潜在的关键点σ规模。

        但是这个 LoG 有点昂贵,所以 SIFT 算法使用了高斯差分,它是 LoG 的近似值。高斯差值作为具有两个不同图像的图像的高斯模糊差值获得σ, 随它去σσ _. 这个过程是针对高斯金字塔中图像的不同倍频程完成的。如下图所示:

         一旦找到这个DoG,就会在图像上搜索尺度和空间上的局部极值。例如,将图像中的一个像素与其 8 个相邻像素以及下一个比例的 9 个像素和前一个比例的 9 个像素进行比较。如果是局部极值,就是潜在的关键点。这基本上意味着关键点在该比例下得到最好的表示。如下图所示:

        关于不同的参数,OpenCV官方给出了一些经验数据,主要为: octaves = 4, scale levels = 5, initial σ=1.6, k=\sqrt2.

2、关键点定位

        一旦找到潜在的关键点位置,就必须对其进行细化以获得更准确的结果。他们使用尺度空间的泰勒级数扩展来获得更准确的极值位置,如果该极值处的强度小于阈值(根据论文为 0.03),则将其拒绝。这个阈值在 OpenCV中称为contrastThreshold

        DoG 对边缘的响应更高,因此也需要去除边缘。为此,使用了类似于 Harris 角点检测器的概念。他们使用 2x2 Hessian 矩阵 (H) 来计算主曲率。我们从 Harris 角点检测器中得知,对于边缘,一个特征值大于另一个特征值。所以在这里他们使用了一个简单的函数,

扫描二维码关注公众号,回复: 14285868 查看本文章

        如果该比率大于阈值(在 OpenCV 中称为edgeThreshold),则丢弃该关键点。它在纸上以 10 的形式给出。

        所以它消除了任何低对比度的关键点和边缘关键点,剩下的是强烈的兴趣点。

3、方向分配

        现在为每个关键点分配一个方向,以实现图像旋转的不变性。根据尺度在关键点位置周围取一个邻域,并在该区域计算梯度大小和方向。创建了一个具有 36 个 bin 覆盖 360 度的方向直方图(它由梯度幅度和高斯加权圆形窗口加权σ等于关键点比例的 1.5 倍)。取直方图中的最高峰,任何高于 80% 的峰也被认为是计算方向。它创建具有相同位置和比例但方向不同的关键点。它有助于匹配的稳定性。

4、关键点描述符

        现在创建了关键点描述符。取关键点周围的 16x16 邻域。它分为 16 个 4x4 大小的子块。对于每个子块,创建 8 个 bin 方向直方图。因此共有 128 个 bin 值可用。它被表示为一个向量以形成关键点描述符。除此之外,还采取了一些措施来实现对光照变化、旋转等的鲁棒性。

5、关键点匹配

        通过识别它们最近的邻居来匹配两个图像之间的关键点。但在某些情况下,第二个最接近的匹配可能非常接近第一个。这可能是由于噪音或其他一些原因而发生的。在这种情况下,采用最近距离与次近距离的比率。如果大于 0.8,则拒绝。根据论文,它消除了大约 90% 的错误匹配,而仅丢弃了 5% 的正确匹配。

二、类参考

1、函数原型

static Ptr<SIFT> cv::SIFT::create	(	int 	nfeatures = 0,
int 	nOctaveLayers = 3,
double 	contrastThreshold = 0.04,
double 	edgeThreshold = 10,
double 	sigma = 1.6 
)	

2、参数详解 

nfeatures 要保留的最佳特征的数量。 特征按其分数排序(在 SIFT 算法中作为局部对比度测量)​
nOctaveLayers 每个ctave中的层数。 3 是 D. Lowe 论文中使用的值。 ctave的数量是根据图像分辨率自动计算的。
contrastThreshold 用于滤除半均匀(低对比度)区域中的弱特征的对比度阈值。 阈值越大,检测器产生的特征就越少。

三、OpenCV源码

1、源码路径

opencv\modules\features2d\src\sift.dispatch.cpp

2、源码代码

Ptr<SIFT> SIFT::create( int _nfeatures, int _nOctaveLayers,
                     double _contrastThreshold, double _edgeThreshold, double _sigma )
{
    CV_TRACE_FUNCTION();

    return makePtr<SIFT_Impl>(_nfeatures, _nOctaveLayers, _contrastThreshold, _edgeThreshold, _sigma, CV_32F);
}

Ptr<SIFT> SIFT::create( int _nfeatures, int _nOctaveLayers,
                     double _contrastThreshold, double _edgeThreshold, double _sigma, int _descriptorType )
{
    CV_TRACE_FUNCTION();

    // SIFT descriptor supports 32bit floating point and 8bit unsigned int.
    CV_Assert(_descriptorType == CV_32F || _descriptorType == CV_8U);
    return makePtr<SIFT_Impl>(_nfeatures, _nOctaveLayers, _contrastThreshold, _edgeThreshold, _sigma, _descriptorType);
}
void SIFT_Impl::detectAndCompute(InputArray _image, InputArray _mask,
                      std::vector<KeyPoint>& keypoints,
                      OutputArray _descriptors,
                      bool useProvidedKeypoints)
{
    CV_TRACE_FUNCTION();

    int firstOctave = -1, actualNOctaves = 0, actualNLayers = 0;
    Mat image = _image.getMat(), mask = _mask.getMat();

    if( image.empty() || image.depth() != CV_8U )
        CV_Error( Error::StsBadArg, "image is empty or has incorrect depth (!=CV_8U)" );

    if( !mask.empty() && mask.type() != CV_8UC1 )
        CV_Error( Error::StsBadArg, "mask has incorrect type (!=CV_8UC1)" );

    if( useProvidedKeypoints )
    {
        firstOctave = 0;
        int maxOctave = INT_MIN;
        for( size_t i = 0; i < keypoints.size(); i++ )
        {
            int octave, layer;
            float scale;
            unpackOctave(keypoints[i], octave, layer, scale);
            firstOctave = std::min(firstOctave, octave);
            maxOctave = std::max(maxOctave, octave);
            actualNLayers = std::max(actualNLayers, layer-2);
        }

        firstOctave = std::min(firstOctave, 0);
        CV_Assert( firstOctave >= -1 && actualNLayers <= nOctaveLayers );
        actualNOctaves = maxOctave - firstOctave + 1;
    }

    Mat base = createInitialImage(image, firstOctave < 0, (float)sigma);
    std::vector<Mat> gpyr;
    int nOctaves = actualNOctaves > 0 ? actualNOctaves : cvRound(std::log( (double)std::min( base.cols, base.rows ) ) / std::log(2.) - 2) - firstOctave;

    //double t, tf = getTickFrequency();
    //t = (double)getTickCount();
    buildGaussianPyramid(base, gpyr, nOctaves);

    //t = (double)getTickCount() - t;
    //printf("pyramid construction time: %g\n", t*1000./tf);

    if( !useProvidedKeypoints )
    {
        std::vector<Mat> dogpyr;
        buildDoGPyramid(gpyr, dogpyr);
        //t = (double)getTickCount();
        findScaleSpaceExtrema(gpyr, dogpyr, keypoints);
        KeyPointsFilter::removeDuplicatedSorted( keypoints );

        if( nfeatures > 0 )
            KeyPointsFilter::retainBest(keypoints, nfeatures);
        //t = (double)getTickCount() - t;
        //printf("keypoint detection time: %g\n", t*1000./tf);

        if( firstOctave < 0 )
            for( size_t i = 0; i < keypoints.size(); i++ )
            {
                KeyPoint& kpt = keypoints[i];
                float scale = 1.f/(float)(1 << -firstOctave);
                kpt.octave = (kpt.octave & ~255) | ((kpt.octave + firstOctave) & 255);
                kpt.pt *= scale;
                kpt.size *= scale;
            }

        if( !mask.empty() )
            KeyPointsFilter::runByPixelsMask( keypoints, mask );
    }
    else
    {
        // filter keypoints by mask
        //KeyPointsFilter::runByPixelsMask( keypoints, mask );
    }

    if( _descriptors.needed() )
    {
        //t = (double)getTickCount();
        int dsize = descriptorSize();
        _descriptors.create((int)keypoints.size(), dsize, descriptor_type);

        Mat descriptors = _descriptors.getMat();
        calcDescriptors(gpyr, keypoints, descriptors, nOctaveLayers, firstOctave);
        //t = (double)getTickCount() - t;
        //printf("descriptor extraction time: %g\n", t*1000./tf);
    }
}

四、效果图像示例

猜你喜欢

转载自blog.csdn.net/bashendixie5/article/details/125330693