目录
一、概述
阅读完DSO项目一小部分,我觉得是时候写一个总集篇了,原因当然不是时间经费什么的,只是想回顾一下,免得又忘记。
二、makeImages
一大堆变量初始化以后,程序会运行到makeImages(HessianBlocks.cpp)。这时DSO会为图像生成金字塔。
过程:
(1)setGlobalCalibration(DatasetReader.h)确定图像金字塔层数,最大层数设为6;
(2)getImage(DatasetReader.h)读入图像,并且用memcpy()保存为数组;
(3)当前层像素由上一层4个像素取平均值得到,存入当前层三维数组第一维;
(4)当前层梯度由当前层像素计算,存入当前层三维数组第二维和第三维;
结果:
三、setfirst
DSO生成金字塔后,检测到当前帧是初始化的第一帧,则执行setfirst(CoarseInitializer.cpp)。
过程:
3.1 makeK
makeK计算金字塔各层的内参矩阵;
3.2 makeMaps
遍历金字塔,对第0层调用makeMaps提取特征像素:
(a)makeHists(PixelSelector2.cpp)将图像分为小块,块尺寸
32
×
32
32 \times 32
32×32,为小块计算梯度直方图,选择中位数梯度作为块梯度阈值,
3
×
3
3 \times 3
3×3个小块求平均值进行梯度平滑;
(b)select(PixelSelector2.cpp)在16个对应方向上随机选择一个,计算当前像素在对应方向上的投影,投影长度大于阈值,记录像素位置;
(c) 如果提取像素太少,需要减小pot尺寸之后递归重采样;反之增大pot重采样;
结果:
3.3 makePixelStatus
遍历金字塔,对第1层以上调用makePixelStatus提取特征像素:
(a)将当前层图像分为小块,每个小块默认为
5
×
5
5 \times 5
5×5;
(b)遍历块内像素,如果梯度大于阈值,保存像素位置;
(c)如果提取的像素个数太少,需要减小pot尺寸之后递归重采样;反之增大pot重采样;
结果:
3.4 makeNN
把前面得到的点信息全部提取,保存到对应层的点集。
调用makeNN构建树结构,在当前层查询k最近邻,在高层查询1最近邻,保存对应的点和距离。
好了,初始化过程对第一帧的处理结束。
四、trackFrame
调用setfirst提取大梯度点和对应的最近邻后,dso调用trackFrame跟踪第一帧的梯度点。
过程:
4.1 resetPoints
这个函数主要针对setfirst中提取到的坏点,也就是isGood为false的点。事实上,根据程序设定,如果提取的梯度点在图像边缘3个像素内,这些点都被认为是坏点。
for(int y=patternPadding+1;y<hl-patternPadding-2;y++)
for(int x=patternPadding+1;x<wl-patternPadding-2;x++){...}
resetPoints的功能是:如果坏点的十个最近邻里有好点,那么求这些最近邻的逆深度平均值,赋值给坏点,然后就可以把isGood修改为true。
4.2 calcResAndGS
把第一帧作为参考帧,把输入帧作为当前帧,利用LM法,优化两帧之间的光度误差
r
=
L
(
I
2
−
I
2
′
)
=
w
h
(
I
2
[
x
2
]
−
(
a
21
I
1
[
x
1
]
+
b
21
)
)
r=L(I_2-I_2')=w_h(I_2[x_2]-(a_{21}I_1[x_1]+b_{21}))
r=L(I2−I2′)=wh(I2[x2]−(a21I1[x1]+b21))待优化状态包括:逆深度
ρ
\rho
ρ、六自由度姿态
ξ
21
\xi_{21}
ξ21、光度仿射变换参数[a,b] 。
dso没有使用g2o或者ceres,而是在calcResAndGS花费了大量的时间计算舒尔补和能量。
4.3 迭代求解
迭代停止的条件有3个:状态增量足够小、迭代次数大于阈值、失败2次以上。
这里的失败指的是两次accept都为false。按照程序设定
float eTotalNew = (resNew[0]+resNew[1]+regEnergy[1]);
float eTotalOld = (resOld[0]+resOld[1]+regEnergy[0]);
bool accept = eTotalOld > eTotalNew;
accept和所有特征的总平移量有关,和点的总逆深度有关,大概是要相机运动足够大。
然后很绕的是,accept为true之后还不够,位移要足够大,snapped才被设为true,这时snappedAt将被赋值。
if(snapped && snappedAt==0)
snappedAt = frameID;
return snapped && frameID > snappedAt+5;
如果运气够好,之后的连续5帧,accept和snapped被相继设为true,那么程序返回true,初始化结束。
如果运气差,在连续的5帧之间,snapped被设为false,那前面就都变成无用功,只能从头来过。
4.4 propagateDown
当前帧金字塔最高层优化完毕后,下一层的逆深度也需要更新。方法是将父点的逆深度和初始逆深度求加权和来更新逆深度,然后和最近邻逆深度求加权和来平滑,从而更新金字塔逆深度。
4.5 propagateUp
当金字塔所有层都更新完毕后,程序会从第0层开始,为上层每一个父点,计算当前层其所有子点的逆深度平均值,然后父点和其最近邻逆深度求加权和来平滑,更新金字塔逆深度。