为啥 SIFT 使用更少的倍频程层会花费更多的时间?

Posted

技术标签:

【中文标题】为啥 SIFT 使用更少的倍频程层会花费更多的时间?【英文标题】:Why SIFT costs more time with fewer octave layers?为什么 SIFT 使用更少的倍频程层会花费更多的时间? 【发布时间】:2021-08-02 15:10:19 【问题描述】:

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

nOctaveLayers KeyPoints Time Cost (ms)
1 1026 63.41
2 1795 45.07
3 2043 45.74
4 2173 47.83
5 2224 51.86

据我了解,使用更少的 octave 层应该有更少的计算,但是为什么只有 1 个 octave 层 SIFT 会花费更多的时间

我还分别测试了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的值,重新编译&运行&记录结果。

【问题讨论】:

你能展示你如何测试的代码吗?如果所有测试都在一个代码中,您可以按相反的顺序尝试(从 5 个八度音阶开始)吗?也许初始化时间目前仅在测试 1 中?此外,使用 N 次运行时的平均运行时间总是好的,因为毫秒测量质量完全取决于系统。 测试过程对我来说看起来不错。此外,时间成本在 nOctaves=2 到 5 之间增加,所以我也认为 nOctaves=1 应该有更少的时间成本。也许您将不得不检查 nOctaves=1 在此实现中是否具有特殊含义。您是否知道使用更多八度音程是否只会在更少八度音程的情况下增加额外的关键点,或者是否可以将某些关键点替换为更高的八度音程?如果是后者,可能更高倍频程的关键点更便宜(描述符窗口更小),并且 nOctaves=1 没有机会识别关键点的“真实”大小/倍频程。 我大致检查了这个code,但没有发现选择nOctaves=1有什么特别之处。恕我直言,它应该像 Micka 所说的那样减少时间成本。也许你可以在 github 上提出一个问题并在那里提问。这些人更接近代码。 【参考方案1】:

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

SIFT算法的流水线是:

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

八度音阶数根据图像分辨率计算(见here)。并且nOctaveLayers 控制每个八度中的层数(nOctaveLayers + 3 表示 GaussianPyramid)。

确实,当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 会花费更多时间。

【讨论】:

以上是关于为啥 SIFT 使用更少的倍频程层会花费更多的时间?的主要内容,如果未能解决你的问题,请参考以下文章

如果我在单行数据块笔记本中执行命令会花费更少的时间吗? [关闭]

m+p Analyzer│2D 图表—数据操作

为啥泡菜比 np.save 花费这么多时间?

为啥在这段代码中向量比指针使用更少的内存?

为啥实习全局字符串值会导致每个多处理进程使用更少的内存?

显示更多/更少的效果