为什么 SIFT 算法在 OctaveLayers 为 1 时消耗更多的时间

本文是本人在 StackOverflow 上提的一个问题和回答,参见:
Why SIFT costs more time with fewer octave layers?

SIFT 算法原理有很多不错的资料都解释地很详细了,比如:

前段时间在使用 OpenCV SIFT 时,遇到一个有点反直觉的问题,特此记录。

问题描述

众所周知,SIFT 计算速度慢一直都是制约其应用的重要缺点。为了降低其计算时耗,我首先想到的是调整其算法参数,这是最简单的方法。SIFT 检测器创建函数为:

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

其中 nOctaveLayers 参数指的是图像金字塔中每一个 Octave 包含的用于提取特征点的 Layer 数量。对应于下图中红框内的 Layer 数量(图片来自前面提到的第一篇博客):
nOctaveLayers=3
默认情况下,nOctaveLayers 值为 3,也就是说金字塔每个 Octave 有 3+2=5 个 DoG 层、3+2+1=6 个高斯模糊层。而图像金字塔高度(Octave 数量)是自动计算的,由输入图像分辨率确定。

那么提速的思路就有了:减少 nOctaveLayers 的值,能减少高斯模糊层和 DoG 层的数量,降低特征点数量,并减少描述子计算时间

我写了一个简单的测试程序,以此评估不同 nOctaveLayers 下 SIFT 的时耗:

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特征点数量时间消耗 (ms)
1102663.41
2179545.07
3204345.74
4217347.83
5222451.86

随着 nOctaveLayers 的减少,特征点数量倒是越来越少,但总时间消耗并没有显著下降,甚至当 nOctaveLayers 为 1 时,时耗不降反升,直接超过设为 5 的情况

原因分析

进过几个小时的源码阅读和调试分析,我终于找到了问题所在:高斯模糊

SIFT 算法流程为:

  1. 创建初始图像:将输入图像数据类型转化为 float 型,分辨率扩大一倍,并做一次高斯模糊 (sigma=1.56)
  2. 构建高斯金字塔
  3. 查找关键点:构建 DoG 金字塔,并查找尺度空间极值点
  4. 计算每个关键点的描述子

nOctaveLayers 增大时,步骤 3 和 4 的时耗确实会增加,但由于多线程计算,这个时耗增加并不显著(大约数毫秒)。

然而,步骤 2 却消耗了总时间的一半以上!当 nOctaveLayers 为 3 时,构建高斯金字塔消耗了 25.27 ms (总共 43.49 ms),当 nOctaveLayers 为 1 时,消耗了 51.16 ms (总共 63.10 ms)。这是因为,为了保持不同 Octave 之间的尺度空间连续性,当 Layer 数量降低时,高斯模糊所使用的 sigma 急剧增大,而 很大的 sigma 值会使高斯模糊极其地慢:

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
*/

上面这段代码模拟了 cv::SIFTbuildGaussianPyramid() 的计算,其中 sigma 序列来自于 cv::SIFT 计算过程。这个测试解释了为什么 SIFT 算法在 OctaveLayers 为 1 时消耗更多的时间。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值