OpenCV SIFT源码详解——detectAndCompute函数详解

OpenCV SIFT源码详解——detectAndCompute函数详解

  • 一、函数声明
  • 二、函数实现关键处注释
  • 三、关键点总结

一、函数声明

    void detectAndCompute(InputArray img, InputArray mask,
                    std::vector<KeyPoint>& keypoints,
                    OutputArray descriptors,
                    bool useProvidedKeypoints = false) CV_OVERRIDE;

useProvidedKeypoints参数决定当前是探测关键点还是计算描述符

二、函数实现关键处注释

void SIFT_Impl::detectAndCompute(InputArray _image, InputArray _mask,
                      std::vector<KeyPoint>& keypoints,
                      OutputArray _descriptors,
                      bool useProvidedKeypoints)
{
    CV_TRACE_FUNCTION();
	/*
	*	firstOctave:当图像放大两倍时,金字塔的组号从-1开始,否则从0开始
	*	actualNOctaves:指图像金字塔的组数
	*	actualNLayers:指DoG每组内参与极值点探测的图像数,一般为3层
	*/
    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;
            /*
            *	scale指的是当前关键点所在图层的大小与原始图像大小之比;
            *	比如,oct = -1时, scale = 2;
            *		  oct = 0时, scale = 1;
            */
            float scale;
            /*
            *	OpenCV很巧妙的使用一个变量(cv::KeyPoint::octave)
            *	存储了两个信息:oct和layer。具体可见本系列我的另一篇文章
            */
            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;
	/*
	* 只有useProvidedKeypoints为true时,actualNOctaves 才会大于 0
	* 此时,使用直接使用已经计算好的actualNOctaves 即可;
	* 否则,说明要进行关键点探测步骤,需要构建高斯图像金字塔,
	* 使用公式 计算组数
	*/
    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 )
    {
    	/*
    	*	构建DOG,具体见本系列我的另一篇文章
    	*/
        std::vector<Mat> dogpyr;
        buildDoGPyramid(gpyr, dogpyr);

        //t = (double)getTickCount();
        
        /*
        *	根据DOG探测关键点,以及极值点亚像素定位
        *	具体可见本系列我的另一篇文章
        */
        findScaleSpaceExtrema(gpyr, dogpyr, keypoints);
		/*
		*	关键点排序去重
		*/
        KeyPointsFilter::removeDuplicatedSorted( keypoints );
		
		/*
		*	nfeatures:用户提供的最大关键点数
		*		若用户提供了最大的关键点数,则根据cv::KeyPoint::response的
		*	大小,去除多余关键点
		*/
        if( nfeatures > 0 )
            KeyPointsFilter::retainBest(keypoints, nfeatures);
        //t = (double)getTickCount() - t;
        //printf("keypoint detection time: %g\n", t*1000./tf);
		
		/*
		*	将关键点放缩到原图尺寸下
		*		经过findScaleSpaceExtrema获取的关键点坐标与高斯金字塔第一组的
		*	尺寸相同,因此,当firstOctave < 0时,要将关键点乘以0.5。
		*		否则,金字塔第一层与原图尺寸相同,无需处理。
		*/
        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;
            }

		/*
		*	如果提供了mask,过滤掉mask标记的关键点
		*/
        if( !mask.empty() )
            KeyPointsFilter::runByPixelsMask( keypoints, mask );
    }
    else
    {
        // filter keypoints by mask
        //KeyPointsFilter::runByPixelsMask( keypoints, mask );
    }
	
	/*
	*	计算关键点描述符
	*/
    if( _descriptors.needed() )
    {
        //t = (double)getTickCount();
        /*
        *	描述符大小:128维
        *	实际上 等于 SIFT_DESCR_WIDTH * SIFT_DESCR_WIDTH * SIFT_DESCR_HIST_BINS;
        */  
        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);
    }
}

三、关键点总结

  1. useProvidedKeypoints为true时,执行compute功能,计算描述符;为false时,执行detect功能,探测关键点;
  2. 当探测关键点时,构建的高斯金字塔首层图像会扩大两倍,firstOctave等于-1;而计算描述符时,firstOctave会根据传入的关键点集合计算得到
  3. findScaleSpaceExtrema探测到的关键点坐标,与高斯金字塔第一层的尺度相同。当firstOctave等于-1时,坐标需要乘以0.5,缩放到原图尺寸。

你可能感兴趣的:(OpenCV,SIFT源码(C++),opencv,计算机视觉,图像处理)