问题描述
我在 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算法的流程是:
- 创建初始图像:将源图像的数据类型转换为
float
,将分辨率加倍,然后执行GaussianBlur
(sigma=1.56) - 构建高斯金字塔
- 找到关键点:构建DoG金字塔并找到尺度空间极值
- 计算描述符
八度音阶数根据图像分辨率计算(见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 花费更多的时间。