为什么 SIFT 花费更多的时间和更少的倍频程层?

问题描述

我在 OpenCV 4.5.2 中使用 SIFT 特征检测器。通过调整 nOctaveLayers 中的 cv::SIFT::create() 参数,我从 detectAndCompute() 获得这些结果:

nOctaveLayers 关键点 时间成本(毫秒)
1 1026 63.41
2 1795 45.07
3 2043 45.74
4 2173 47.83
5 2224 51.86

据我所知,使用较少的八度音阶层应该有更少的计算,但是为什么 SIFT 仅使用 1 个八度音阶层会花费更多的时间

我还分别测试了 detect()compute(),当 nOctaveLayers 为 1 时,它们都花费更多时间,这让我很困惑。

测试图像为 here(来自 TUM 开放数据集)。在此先感谢您的帮助。


[为@Micka 编辑] 我的测试代码

const int test_num = 100;
const int layers = 5;
cout << "layers: " << layers << endl;

auto sift = SIFT::create(0,layers);
vector<KeyPoint> kps;
Mat descs;

auto t1 = chrono::high_resolution_clock::Now();
for (int i = 0; i < test_num; ++i)
    sift->detectAndCompute(img_src,noArray(),kps,descs);
auto t2 = chrono::high_resolution_clock::Now();
cout << "num of kps: " << kps.size() << endl;
cout << "avg time cost: " << chrono::duration<double>(t2 - t1).count() * 1e3 / test_num << endl;

对于每个 nOctaveLayers 配置,我更改代码中的 layers 值,重新编译并运行并记录结果。

解决方法

经过几个小时的分析,我终于找到了原因:GaussianBlur

SIFT算法的流程是:

  1. 创建初始图像:将源图像的数据类型转换为float,将分辨率加倍,然后执行GaussianBlur (sigma=1.56)
  2. 构建高斯金字塔
  3. 找到关键点:构建DoG金字塔并找到尺度空间极值
  4. 计算描述符

八度音阶数根据图像分辨率计算(见here)。而 nOctaveLayers 控制每个八度音阶中的层数(nOctaveLayers + 3 代表高斯金字塔)。

确实,当nOctaveLayers增加时,层数和关键点数都会增加。结果,步骤 3 和 4 的时间成本增加。但是,在并行计算中,这个时间增量并不是很显着(几毫秒)。

相比之下,步骤 2 花费了总时间的一半以上nOctaveLayers 为 3 时花费 25.27 毫秒(43.49 毫秒),nOctaveLayers 为 1 时花费 51.16 毫秒(63.10 毫秒)。那么,为什么会发生这种情况?

因为当层数较少时,GaussianBlur() 的 sigma 增加得更快,这对 GaussianBlur() 消耗的时间至关重要。请参阅下面的测试:

vector<double> sig1 = { 1.6,2.77128,5.54256,11.0851 };
vector<double> sig3 = { 1.6,1.22627,1.54501,1.94659,2.45255,3.09002 };
vector<double> sig5 = { 1.6,0.9044,1.03888,1.19336,1.37081,1.57465,1.8088,2.07777 };

auto blurTest = [](const vector<double>& sigs,const string& label) {
    const int test_num = 100;
    auto t1 = chrono::high_resolution_clock::now();
    for (int i = 0; i < test_num; ++i) {
        vector<Mat> pyr;
        pyr.resize(sigs.size());
        pyr[0] = Mat::zeros(960,1280,CV_32FC1);
        for (size_t i = 1; i < sigs.size(); ++i)
            GaussianBlur(pyr[i - 1],pyr[i],Size(),sigs[i],sigs[i]);
    }
    auto t2 = chrono::high_resolution_clock::now();
    auto time = chrono::duration<double>(t2 - t1).count() * 1e3 / test_num;
    cout << label << ": " << time << " ms\n";
};

blurTest(sig1,"1");
blurTest(sig3,"3");
blurTest(sig5,"5");

/* output:
1: 45.3958 ms
3: 28.5943 ms
5: 31.4827 ms
*/

上面的代码模拟了 nOctaveLayers 为 1、3 和 5 时的 buildGaussianPyramid()。sigma 值来自 cv::SIFT 计算。这就解释了为什么当 nOctaveLayers 为 1 时 SIFT 花费更多的时间。