我正在深入研究OpenCV对SIFT描述符提取的实现。我遇到了一些令人费解的代码来获得兴趣点附近的半径。下面是带注释的代码,其中变量名更改为更具描述性:
// keep octave below 256 (255 is 1111 1111)
int octave = kpt.octave & 255;
// if octave is >= 128, ...????
octave = octave < 128 ? octave : (-128 | octave);
// 1/2^absval(octave)
float scale = octave >= 0 ? 1.0f/(1 << octave) : (float)(1 << -octave);
// multiply the point's radius by the calculated scale
float scl = kpt.size * 0.5f * scale;
// the constant sclFactor is 3 and has the following comment:
// determines the size of a single descriptor orientation histogram
float histWidth = sclFactor * scl;
// descWidth is the number of histograms on one side of the descriptor
// the long float is sqrt(2)
int radius = (int)(histWidth * 1.4142135623730951f * (descWidth + 1) * 0.5f);
我知道这与转换成兴趣点的比例有关(我读过Lowe的论文),但我无法将这些点与代码联系起来。具体来说,我看不懂前三行和最后一行。
我需要理解这一点,以便为运动创建一个类似的局部点描述符
我看不懂前三行
实际上,这个SIFT实现在KeyPoint
octave
属性中编码几个值。如果您参考第439行,您可以看到:
kpt.octave = octv + (layer << 8) + (cvRound((xi + 0.5)*255) << 16);
这意味着八度程存储在第一个字节块中,第二字节块中的层,等等。
所以kpt.octave & 255
(可以在unpackOctave
方法中找到)只是屏蔽关键点八度来检索有效的八度程值。
也:这个SIFT实现使用负的第一倍频程(int firstOctave = -1
)来处理更高分辨率的图像。由于八度程索引从0开始,因此计算映射:
octave index = 0 => 255
octave index = 1 => 0
octave index = 2 => 1
...
这个映射在第790行计算:
kpt.octave = (kpt.octave & ~255) | ((kpt.octave + firstOctave) & 255);
因此上面的第二行只是映射这些值的一种方式:
octave = 255 => -1
octave = 0 => 0
octave = 1 => 1
..
第三行只是一种计算音阶的方法,考虑到负八度给出的音阶> 1,例如1 << -octave
给出octave = -1
的2,这意味着它的大小翻倍。
[我不明白]最后一行。
基本上,它对应于一个圆的半径,这个圆包裹了一个尺寸为D
的正方形补丁,因此是sqrt(2)
和除以2。D
的计算方法是:
- 关键点刻度,
- a放大倍数= 3,
- 描述符直方图的宽度= 4,四舍五入到下一个整数(因此+1)
实际上你可以在vlfeat的SIFT实现中找到详细的描述:
每个空间bin的支撑具有SBP = 3sigma的扩展像素,其中sigma是关键点的比例。因此所有的箱一起有一个支持SBP x NBP像素宽。自采用像素加权和插值,扩展了对像素的支持又少了半个箱子。因此,支撑窗是方形的SBP x (NBP + 1)像素。最后,既然补丁可以任意旋转,我们需要考虑一个窗口2W += sqrt(2) xSBP x (NBP + 1)像素宽。
最后我强烈建议您参考这个vlfeat SIFT文档