前言
最近,对TSDF相关的论文(Kinect-Fusion, Voxel-Hashing, Bundle-Fusion)进行了大致的理解和阅读,这里总结一下其中用到的主要算法TSDF,(Truncated Signed Distance Functions)
1.TSDF原理及流程
在Kinect-Fusion论文中有详细讲解,TSDF的原理非常简单,将整个建模空间视为一个长方体(volume),并将长方体等值的分为若干体素
v
i
v_i
vi(voxel),一个voxel中包含SDF值
s
d
f
(
i
)
sdf(i)
sdf(i)与权重
w
w
w,其中
s
d
f
(
i
)
sdf(i)
sdf(i)可简单理解为当前体素到最近表面的距离,权重则表示为置信度。迭代
v
i
v_i
vi中的
s
d
f
(
i
)
sdf(i)
sdf(i)与
w
w
w。其伪代码如下图,这里我们详细记录单步的执行操作的:
1.准备阶段:
生成重建环境需要大小的体volume,其中包含
N
∗
M
∗
S
N*M*S
N∗M∗S个体素voxel,设置voxel大小(volume体积限制情况下,voxel越小则重建效果越好)。获得每个voxel在世界坐标系下的位置
p
w
p_{w}
pw。
2.已知信息:
(i)相机内参矩阵(intrinsic matrix)
K
K
K;
(ii)当前帧相对于世界坐标系的位姿变换
T
w
,
i
T_{w,i}
Tw,i;
(iii)前帧相对于世界坐标系位姿变换
T
w
,
i
−
1
T_{w,i-1}
Tw,i−1;
(iv)前帧
t
s
d
f
i
−
1
tsdf_{i-1}
tsdfi−1,
w
i
−
1
w_{i-1}
wi−1
3.输入:
当前帧深度图信息
D
i
D_{i}
Di(深度图)
4.执行:
step1:将volume中所有voxel位置变换到当前
i
i
i相机坐标系下:
p
i
=
T
w
i
−
1
∗
p
w
p_{i} = T_{wi}^{-1}*p_{w}
pi=Twi−1∗pwstep2:将所有voxel投影到当前帧相机二维坐标系下(不在投影范围内的则不纳入本次计算):
p
i
x
e
l
(
u
,
v
)
=
1
Z
(
p
i
)
K
p
i
pixel(u,v) = \frac{1}{Z(p_{i})}Kp_{i}
pixel(u,v)=Z(pi)1Kpistep3:计算
s
d
f
i
sdf_{i}
sdfi:
s
d
f
i
=
∣
∣
p
i
(
z
)
∣
∣
−
D
i
(
p
i
x
e
l
)
sdf_{i} = ||p_{i}(z)|| - D_{i}(pixel)
sdfi=∣∣pi(z)∣∣−Di(pixel)后面的步骤描述与伪码中step8~step14一致。
最后,其实我一开始看这个算法时并没有理解理解迭代更新过程为什么能够实现迭代到最后 t s d f i tsdf_{i} tsdfi能够定量的表示与最近表面的距离,后来代入了几个例子到迭代公式中发现是个初中生都能想通的问题(人晕了)。一般来说在 t s d f i tsdf_{i} tsdfi迭代更新的过程中权值 w i w_{i} wi都取为了 w i − 1 + 1 w_{i-1}+1 wi−1+1,当一个voxel足够贴近一个面时其最终都会得到一个在 ( − 1 , 1 ) (-1, 1) (−1,1)之间的值。
2.关于Hashing Voxel
前面提到的TSDF算法由于需要将整个场景视为一个volume导致了明显的问题即内存消耗过大。在文章Real-time 3D Reconstruction at Scale using Voxel Hashing中使用Hash表的方法对该问题做出了解决,Hashing Voxel也是在现在的基于TSDF的重建中普遍采用的方法。
Hashing Voxel的思路很简单,仅在实体表面周围生成voxel并使用Hash表原理将voxel三维坐标作为键值对这些无序的voxel进行存储。其数据结构如下图:
其中voxel blocks是voxel的集合体,在文章中定义的是包含888个voxel。确定对应表面的voxel block三维坐标,一个表面的voxel block可能已存在此时则更新voxel blocks中各voxel的sdf值与权值w,当其三维坐标在hash table中不存在时表示还未建立则此时项hash table中插入新元素指向该voxel block,并初始化其中的各voxel。