OpenCV SIFT源码讲解——代码逻辑宏观窥探
一、暴露在外的接口:SIFT
一般来说,我们在OpenCV中使用SIFT算法的方式为:
//该表达式涉及到多态,详解在第2节
cv::Ptr<SiftFeatureDetector> sift = SIFT::create();
那么,SiftFeatureDetector
是什么呢?在 features2d.hpp中清晰的写出来:
typedef SIFT SiftFeatureDetector;
同样在该文件中,可以看到:
class CV_EXPORTS_W SIFT : public Feature2D
{
...
}
CV_EXPORTS_W
的本质如下,表明SIFT
是一个暴露给用户的接口。
define CV_EXPORTS_W CV_EXPORTS
//下面一行宏定义只能在源码内看到,不对外暴露
//若在程序中使用编译好的OpenCV,只能追踪到上一行宏定义
define CV_EXPORTS __declspec(dllexport)
二、隐藏在SIFT背后的本质:SIFT_Impl
在编译好的OpenCV内,对于SIFT,只提供了features2d.hpp文件,我们无法看到任何有关与他的具体实现。因此,想要探究其本质,现在进入其源码内:sift.dispatch.cpp。
在该cpp文件中,定义了一个SIFT
的派生类SIFT_Impl
。该派生类不对外暴露。
class SIFT_Impl : public SIFT{
};
同时,在该CPP文件中,也定义了SIFT接口的create函数:
Ptr<SIFT> SIFT::create( int _nfeatures, int _nOctaveLayers,
double _contrastThreshold, double _edgeThreshold, double _sigma, int _descriptorType )
{
...
/*
* 返回指针
*/
return makePtr<SIFT_Impl>(_nfeatures, _nOctaveLayers, _contrastThreshold, _edgeThreshold, _sigma, _descriptorType);
}
因此,到目前为止,已经知道了SIFT背后的逻辑:
//定义SIFT类型的指针
//在create函数内定义 派生类 SIFT_Impl对象
//SIFT_Impl指针向上转型,赋值给sift。(多态)
cv::Ptr<SiftFeatureDetector> sift = SIFT::create();
之后,sift
通过箭头描述符调用的任何函数,都将通过虚指针指向派生类的具体实现。也即SIFT_Impl
才是sift
的本质。
三、使用sift算法全流程
//1.读取图像
cv::Mat matL = cv::imread(imgL);
cv::Mat matR = cv::imread(imgR);
//2.定义关键点和描述符
std::vector<cv::KeyPoint> kpL,kpR;
cv::Mat descriptorL,descriptorR;
//3.创建SIFT对象
cv::Ptr<SiftFeatureDetector> siftL = SIFT::create();
cv::Ptr<SiftFeatureDetector> siftR = SIFT::create();
//4.探测关键点
siftL->detect(matL, kpL);
siftR->detect(matR, kpR);
//5.计算描述符
siftL->compute(matL, kpL, descriptorL);
siftR->compute(matR, kpR, descriptorR);
//6.描述符匹配
...
对于第3步,创建SIFT对象,上两节已经讲过,那么现在只剩下detect和compute两个步骤了。这两个步骤是个重难点,我将分多篇文章细讲,争取能给大家讲明白。(描述符匹配属于特征匹配通用的部分,不属于SIFT专有,在这里就不讲了)
回到features2d.hpp,可以看SIFT
接口内并没有声明detect
和compute
两个函数,这是因为这两个函数是继承其基类Feature2D
的(可以在该hpp文件内找到)。
进入features2d.cpp内,可以找到detect
和compute
的定义:
void Feature2D::detect( InputArray image,
std::vector<KeyPoint>& keypoints,
InputArray mask )
{
...
detectAndCompute(image, mask, keypoints, noArray(), false);
}
void Feature2D::compute( InputArray image,
std::vector<KeyPoint>& keypoints,
OutputArray descriptors )
{
...
detectAndCompute(image, noArray(), keypoints, descriptors, true);
}
可以看到,这两个函数最终都将调用detectAndCompute
,该函数是被SIFT_Impl
重写(detect和compute还有一个重载函数,但是最后都会调用detectAndCompute)。
我将单独写一篇文章介绍detectAndCompute
函数