赞
踩
参考源码: https://github.com/andyzeng/tsdf-fusion-python
从图中可以看出,tsdf算法的重建效果还是不错的.该算法是很多牛掰3D重建算法的基础,例如:KinectFusion、InfiniTAM等.
根据环境待重建点云分布情况,确定包围所有点云的边界大小,然后再将边界内的空间根据设定的尺寸大小进行体素化,这样就将空间划分成一个个下图所示的小立方体(立方体的尺寸由用户自行定义,越小建模精度越高,越耗时),图像来源https://blog.csdn.net/qq_30339595/article/details/86103576
定义2.1节建立的体素栅格所在的坐标系为世界坐标系,体素坐标为 ( p 0 w , p 1 w , . . . , p m w ) (\mathbf{p}_{0}^{w}, \mathbf{p}_{1}^{w}, ...,\mathbf{p}_{m}^{w}) (p0w,p1w,...,pmw),相机的位姿为 ( T c 0 w , T c 1 w . . . , T c n w ) (\mathbf{T}_{c_{0}}^{w}, \mathbf{T}_{c_{1}}^{w} ..., \mathbf{T}_{c_{n}}^{w}) (Tc0w,Tc1w...,Tcnw),相机的内参为 K \mathbf{K} K
I
i
j
=
[
u
i
j
v
i
j
]
=
K
T
c
i
w
p
j
w
(1)
\mathbf{I}_{ij} =
式中
i
∈
(
0
,
1
,
.
.
.
.
,
n
)
i\in(0, 1, ...., n)
i∈(0,1,....,n),
j
∈
(
0
,
1
,
.
.
.
,
m
)
j\in(0, 1, ..., m)
j∈(0,1,...,m).
首先计算每个体素sdf值:
s
d
f
j
=
∥
t
p
j
w
−
t
c
i
w
∥
−
d
e
p
(
I
i
j
)
(2)
sdf_{j} = \left \| \mathbf{t}_{pj}^{w} - \mathbf{t}_{ci}^{w} \right \| - \mathbf{dep}(\mathbf{I}_{ij}) \tag{2}
sdfj=∥∥tpjw−tciw∥∥−dep(Iij)(2)
式中
t
p
j
w
\mathbf{t}_{pj}^{w}
tpjw表示第
j
j
j个体素在世界坐标系下的位置信息,
t
c
i
w
\mathbf{t}_{ci}^{w}
tciw表示第
i
i
i个相机位姿在世界坐标系下的位置信息,
d
e
p
(
I
i
j
)
\mathbf{dep}(\mathbf{I}_{ij})
dep(Iij)表示第
j
j
j个体素在第
i
i
i个相机深度图像坐标系下的深度值.
截断每个体素的sdf值:
i
f
s
d
f
j
>
0
t
s
d
f
j
=
m
i
n
(
1
,
s
d
f
j
/
t
r
u
n
c
)
e
l
s
e
t
s
d
f
j
=
m
a
x
(
−
1
,
s
d
f
j
/
t
r
u
n
c
)
(3)
式中
t
r
u
n
c
trunc
trunc 表示截断距离,人为设定,可理解为相机深度信息的误差值,如果误差越大,建议该值设置大一些,否则会丢掉很多深度相机获取的信息.
计算每个体素的tsdf值:
t
s
d
f
j
=
t
s
d
f
j
−
1
⋅
w
j
−
1
+
t
s
d
f
j
⋅
w
j
w
j
−
1
+
w
j
(4)
tsdf_{j} = \frac{tsdf_{j-1}\cdot w_{j-1} + tsdf_{j}\cdot w_{j}}{w_{j-1}+w_{j}} \tag{4}
tsdfj=wj−1+wjtsdfj−1⋅wj−1+tsdfj⋅wj(4)
式中初始
w
j
w_{j}
wj一般默认设置为1.
计算每个体素的权值:
w
j
=
w
j
−
1
+
w
j
(5)
w_{j} = w_{j-1} + w_{j} \tag{5}
wj=wj−1+wj(5)
通过2.2节更新完每个体素的tsdf值之后,通过marching cubes算法寻找体素中的等值面作为重构曲面,算法示意图如下:
其中每个栅格里面的值为对应体素的tsdf值.
参考源码: https://github.com/andyzeng/tsdf-fusion-python
注: 这里仅分析cpu版本源码,对于深入理解tsdf原理足够.
def __init__(self, vol_bnds, voxel_size, use_gpu=True): # 将点云分布边界转换成numpy数组 vol_bnds = np.asarray(vol_bnds) assert vol_bnds.shape == (3, 2), "[!] `vol_bnds` should be of shape (3, 2)." # 定义体素体参数 self._vol_bnds = vol_bnds # 体素体边界 self._voxel_size = float(voxel_size) # 体素体每个立方体边长 self._trunc_margin = 5 * self._voxel_size # truncation on SDF # 截断距离,设置为体素边长的5倍 self._color_const = 256 * 256 # 辅助参数,用于提取rgb值 # 调整体素体边界确保c连续 self._vol_dim = np.ceil((self._vol_bnds[:,1]-self._vol_bnds[:,0])/self._voxel_size).copy(order='C').astype(int) # 计算体素体每个维度方向上的栅格数量 self._vol_bnds[:,1] = self._vol_bnds[:,0]+self._vol_dim*self._voxel_size # 根据各个维度栅格数量,计算体素体边界 self._vol_origin = self._vol_bnds[:,0].copy(order='C').astype(np.float32) # 使体素体原点为c有序 # 初始化保存体素体信息的容器 self._tsdf_vol_cpu = np.ones(self._vol_dim).astype(np.float32) # 用于保存每个体素栅格的tsdf值 self._weight_vol_cpu = np.zeros(self._vol_dim).astype(np.float32) # 用于保存每个体素栅格的权重值 self._color_vol_cpu = np.zeros(self._vol_dim).astype(np.float32) # 用于保存每个体素栅格的颜色值(将rgb三个值压缩成一个float32值表示) # 获取每个体素栅格的坐标 xv, yv, zv = np.meshgrid( range(self._vol_dim[0]), range(self._vol_dim[1]), range(self._vol_dim[2]), indexing='ij' ) self.vox_coords = np.concatenate([ xv.reshape(1,-1), yv.reshape(1,-1), zv.reshape(1,-1) ], axis=0).astype(int).T
def integrate(self, color_im, depth_im, cam_intr, cam_pose, obs_weight=1.): im_h, im_w = depth_im.shape # 获取图像尺寸 # 将rgb三个值表示的颜色通道信息转换成一个用float32表示的单通道信息 color_im = color_im.astype(np.float32) color_im = np.floor(color_im[...,2]*self._color_const + color_im[...,1]*256 + color_im[...,0]) # 将体素栅格坐标转换成像素坐标,对应2.2节的公式(1) cam_pts = self.vox2world(self._vol_origin, self.vox_coords, self._voxel_size) # 体素坐标系转换到世界坐标系 cam_pts = rigid_transform(cam_pts, np.linalg.inv(cam_pose)) # 世界坐标系转换到相机坐标系 pix_z = cam_pts[:, 2] pix = self.cam2pix(cam_pts, cam_intr) # 相机坐标系转换到像素坐标系 pix_x, pix_y = pix[:, 0], pix[:, 1] # 移除像素边界之外的投影点 valid_pix = np.logical_and(pix_x >= 0, np.logical_and(pix_x < im_w, np.logical_and(pix_y >= 0, np.logical_and(pix_y < im_h, pix_z > 0)))) depth_val = np.zeros(pix_x.shape) depth_val[valid_pix] = depth_im[pix_y[valid_pix], pix_x[valid_pix]] # 更新每个体素栅格的tsdf值及对应的权重 depth_diff = depth_val - pix_z # 计算sdf值,对应公式(2) valid_pts = np.logical_and(depth_val > 0, depth_diff >= -self._trunc_margin) # 确定出有效深度值(即sdf值的值要大于负的截断值) dist = np.minimum(1, depth_diff / self._trunc_margin) # 计算截断值,对应公式(3) valid_vox_x = self.vox_coords[valid_pts, 0] valid_vox_y = self.vox_coords[valid_pts, 1] valid_vox_z = self.vox_coords[valid_pts, 2] w_old = self._weight_vol_cpu[valid_vox_x, valid_vox_y, valid_vox_z] # 提取上个循环对应体素的权重,对应公式(4)中的w_j_1 tsdf_vals = self._tsdf_vol_cpu[valid_vox_x, valid_vox_y, valid_vox_z] # 提取上个循环对应的tsdf值,对应公式(4)中的tsdf_j_1 valid_dist = dist[valid_pts] tsdf_vol_new, w_new = self.integrate_tsdf(tsdf_vals, valid_dist, w_old, obs_weight) # 计算体素新的权重和tsdf值,对应公式(4)(5) self._weight_vol_cpu[valid_vox_x, valid_vox_y, valid_vox_z] = w_new # 将新的权值和tsdf值更新到体素信息容器中 self._tsdf_vol_cpu[valid_vox_x, valid_vox_y, valid_vox_z] = tsdf_vol_new # 更新每个体素栅格的颜色值,其实就是按旧的权重和新的权重加权更新每个体素栅格的rgb值 old_color = self._color_vol_cpu[valid_vox_x, valid_vox_y, valid_vox_z] old_b = np.floor(old_color / self._color_const) old_g = np.floor((old_color-old_b*self._color_const)/256) old_r = old_color - old_b*self._color_const - old_g*256 new_color = color_im[pix_y[valid_pts],pix_x[valid_pts]] new_b = np.floor(new_color / self._color_const) new_g = np.floor((new_color - new_b*self._color_const) /256) new_r = new_color - new_b*self._color_const - new_g*256 new_b = np.minimum(255., np.round((w_old*old_b + obs_weight*new_b) / w_new)) new_g = np.minimum(255., np.round((w_old*old_g + obs_weight*new_g) / w_new)) new_r = np.minimum(255., np.round((w_old*old_r + obs_weight*new_r) / w_new)) self._color_vol_cpu[valid_vox_x, valid_vox_y, valid_vox_z] = new_b*self._color_const + new_g*256 + new_r
def get_mesh(self): # 获取体素栅格的tsdf值及对应的颜色值 tsdf_vol, color_vol = self.get_volume() # 直接使用scikit-image工具包中封装的Marching cubes算法接口提取等值面 verts, faces, norms, vals = measure.marching_cubes_lewiner(tsdf_vol, level=0) verts_ind = np.round(verts).astype(int) verts = verts*self._voxel_size+self._vol_origin # voxel grid coordinates to world coordinates # 为每个体素赋值颜色 rgb_vals = color_vol[verts_ind[:,0], verts_ind[:,1], verts_ind[:,2]] colors_b = np.floor(rgb_vals/self._color_const) colors_g = np.floor((rgb_vals-colors_b*self._color_const)/256) colors_r = rgb_vals-colors_b*self._color_const-colors_g*256 colors = np.floor(np.asarray([colors_r,colors_g,colors_b])).T colors = colors.astype(np.uint8) return verts, faces, norms, colors
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。