Overview
输入RGB-D流: { I t } t = 1 N { D t } t = 1 N \{I_t\}^{N}_{t=1}\{D_t\}^{N}_{t=1} {It}t=1N{Dt}t=1N,它们带有已知相机内参 K ∈ R 3 × 3 K\in \mathbb{R}^{3\times 3} K∈R3×3。通过联合优化相机姿态 { ξ t } t = 1 N \{\xi_t\}^{N}_{t=1} {ξt}t=1N和神经场景表示 f t h e t a f_{theta} ftheta来实现密集映射和追踪。
隐式表示方式将世界坐标 x \mathrm{x} x映射为颜色 c \mathrm{c} c和TSDF(截断符号距离) s s s的值: f θ ( x ) → ( c , s ) f_{\theta}(\mathrm{x})\rightarrow(\mathrm{c},s) fθ(x)→(c,s)。
在每次映射迭代中对从全局像素集中随机采样的一组像素执行全局捆绑调整,联合优化场景表示 θ \theta θ和所有相机姿态 ξ t \xi_{t} ξt。
联合坐标和参数编码
采用坐标编码进行场景表示,通过是使用稀疏参数编码加速训练:使用One-blob编码 γ ( x ) \gamma(x) γ(x),而非将空间坐标嵌入到多个频带中。作为场景表示,采用基于多分辨率哈希的特征网格 V α = { V α l } l = 1 L \mathcal{V}_{\alpha}=\{\mathcal{V}^{l}_{\alpha}\}^{L}_{l=1} Vα={Vαl}l=1L。每个级别的空间分辨率以渐进的方式设置在最粗略(coarsest)的 R m i n R_{min} Rmin和最精细(finest)的分辨率 R m a x R_{max} Rmax之间。通过三线性插值查询每个采样点 x \rm{x} x处的特征向量 V α ( x ) \mathcal{V}_{\alpha}(\rm{x}) Vα(x)。几何解码器输出预测的SDF值 s s s和特征向量 h \rm{h} h: f τ ( γ ( x ) , V α ( x ) ) → ( h , s ) f_{\tau}(\gamma(\mathrm{x}),\mathcal{V}_{\alpha}(\mathrm{x}))\rightarrow(\mathrm{h},s) fτ(γ(x),Vα(x))→(h,s)。
最终,颜色多层感知机预测RGB值: f ϕ ( γ ( x ) , h ) → c f_{\phi}(\gamma(\mathrm{x}),h)\rightarrow\mathrm{c} fϕ(γ(x),h)→c。
这里的 θ = { α , ϕ , τ } \theta=\{\alpha,\phi,\tau\} θ={α,ϕ,τ}是可学习的参数。基于哈希的多分辨率特征网格表示中注入One blob编码,可以实现在线SLAM所需的快速收敛、高效内存使用和孔洞填充。
深度和颜色渲染
沿采样光线整合预测值来渲染深度和颜色。具体来说,给定相机原点 o \mathrm{o} o和光线方向 r \mathrm{r} r,均匀采样 M M M个点: x i = o + d i r , i ∈ { 1 , . . . , M } \mathrm{x}_i=\mathrm{o}+d_i\mathrm{r},i\in \{1,...,M\} xi=o+dir,i∈{1,...,M},这些点具有深度值 { t 1 , . . . , t M } \{t_1,...,t_M\} {t1,...,tM}以及预测的颜色 { c 1 , . . . , c M } \{\mathrm{c_1,...,\mathrm{c}_M}\} {c1,...,cM}。
渲染公式如下: c ^ = 1 ∑ i = 1 M w i ∑ i = 1 M w i c i , d ^ = 1 ∑ i = 1 M w i ∑ i = 1 M w i d i \hat{\mathrm{c}}=\frac{1}{\sum^{M}_{i=1}w_i}\sum^{M}_{i=1}w_i\mathrm{c}_i,\hat{d}=\frac{1}{\sum^{M}_{i=1}w_i}\sum^{M}_{i=1}w_id_i c^=∑i=1Mwi1∑i=1Mwici,d^=∑i=1Mwi1∑i=1Mwidi。
其中, { w i } \{w_i\} {wi}是沿着光线计算的权重。需要一个转换函数将预测的SDF值 s i s_i si转换为权重 w i w_i wi。与Neus中提出的渲染方程相反,这里采用简易的钟形(bell-shaped)模型并通过两个Sigmoid函数 σ ( ⋅ ) \sigma(·) σ(⋅)直接计算权重 w i w_i wi: w i = σ ( s i t r ) σ ( − s i t r ) w_i=\sigma(\frac{s_i}{tr})\sigma(-\frac{s_i}{tr}) wi=σ(trsi)σ(−trsi)。
这里的 t r tr tr是截断距离。
深度引导采样:使用深度引导采样:除了在最近除和最远处之间均匀采样 M c M_c Mc个点外,对于具有有效深度测量的射线,还需要对 [ d − d s , d + d s ] [d−d_s,d+d_s] [d−ds,d+ds]范围内的 M f M_f Mf个近表面点进行均匀采样,其中 d s d_s ds是一个小偏移。
追踪和捆绑调整
目标函数:追踪和捆绑调整是通过最小化与可学习参数 θ \theta θ和相机参数 ξ t \xi_t ξt相关的目标函数来实现的。颜色和深度渲染的损失是渲染结果和观测值之间的 l 2 \mathcal{l}_2 l2误差: L r g b = 1 N ∑ n = 1 N ( c ^ n − c n ) 2 , L d = 1 ∣ R d ∣ ∑ r ∈ R d ( d ^ r ) − D [ u , v ] 2 \mathcal{L}_{rgb}=\frac{1}{N}\sum^{N}_{n=1}(\hat{c}_n-c_n)^2,\mathcal{L}_d=\frac{1}{|R_d|}\sum_{r\in R_d}(\hat{d}_r)-D[u,v]^2 Lrgb=N1∑n=1N(c^n−cn)2,Ld=∣Rd∣1∑r∈Rd(d^r)−D[u,v]2。
其中, R d R_d Rd是具有有效深度测量的光线几何, u , v u,v u,v是图像平面上的对应像素。
为了实现具有详细几何形状准确、平滑的重建,这里应用近似SDF和特征平滑度损失。
对于截断区域内的样本,比如 ∣ D [ u , v ] − d ≤ t r ∣ |D[u,v]-d\leq tr| ∣D[u,v]−d≤tr∣这些点,使用采样点与其观测到的深度值之间的距离作为真实SDF近似值的监督: L s d f = 1 ∣ R d ∣ ∑ r ∈ R d 1 ∣ S r t r ∣ ∑ p ∈ S r t r ( s p − ( D [ u , v ] − d ) ) 2 \mathcal{L}_{sdf}=\frac{1}{|R_d|}\sum_{r\in R_d}\frac{1}{|S^{tr}_{r}|} \sum_{p\in S^{tr}_{r}}(s_p-(D[u,v]-d))^2 Lsdf=∣Rd∣1∑r∈Rd∣Srtr∣1∑p∈Srtr(sp−(D[u,v]−d))2。
对于远离表面的点,比如 ∣ D [ u , v ] − d > t r ∣ |D[u,v]-d>tr| ∣D[u,v]−d>tr∣,使用一个自由空间损失,迫使SDF预测为截断距离 t r tr tr: L f s = 1 ∣ R d ∣ ∑ r ∈ R d 1 ∣ S r f s ∣ ∑ p ∈ S r f s ( s p − t r ) 2 \mathcal{L}_{fs}=\frac{1}{|R_d|}\sum_{r\in R_d}\frac{1}{|S^{fs}_{r}|} \sum_{p\in S^{fs}_{r}}(s_p-tr)^2 Lfs=∣Rd∣1∑r∈Rd∣Srfs∣1∑p∈Srfs(sp−tr)2。
为了防止在未观察到的自由空间区域中由哈希碰撞引起的噪声重建,这里对插值特征 V α ( x ) \mathcal{V}_{\alpha}(\mathrm{x}) Vα(x)进行了额外的正则化: L s m o o t h = ∑ x ∈ G Δ x 2 + Δ y 2 + Δ z 2 \mathcal{L}_{smooth}=\sum_{x\in \mathcal{G}}\Delta^2_x+\Delta^2_y+\Delta^2_z Lsmooth=∑x∈GΔx2+Δy2+Δz2。这里的 Δ x , y , z = V α ( x + ϵ x , y , z − V α ( x ) ) \Delta_{x,y,z}=\mathcal{V}_{\alpha}(\mathrm{x}+\epsilon_{x,y,z}-\mathcal{V}_{\alpha}(\mathrm{x})) Δx,y,z=Vα(x+ϵx,y,z−Vα(x))表示哈希网格上沿三维相邻采样顶点之间的特征度量差异。由于对整个特征网格执行正则化在计算上对于实时映射是不可行的,因此在每次迭代中只在一个小的随机区域中执行正则化。
相机追踪:追踪每一帧相机到世界坐标系的转换矩阵 T w c = exp ( ξ t ∧ ) ∈ S E ( 3 ) \mathrm{T}_{wc} = \exp(\xi ^\wedge_t) \in \mathbb{SE}(3) Twc=exp(ξt∧)∈SE(3)。当新的一帧输入时,首先初始化当前帧 i i i的姿态,使用恒定速度假设: T t = T t − 1 T t − 2 − 1 T t − 1 \mathrm{T}_t=\mathrm{T}_{t-1}\mathrm{T}^{-1}_{t-2}\mathrm{T}_{t-1} Tt=Tt−1Tt−2−1Tt−1。然后在当前帧中选择 N t N_t Nt个像素,并通过最小化与相机参数 ξ t \xi_t ξt相关的目标函数来优化姿态。
捆绑调整:在Co-SLAM中,不再需要存储完整的关键帧图像或关键帧选择。相反,只存储像素的一个子集(约 5 % 5\% 5%)来表示每个关键帧。这使我们能够更频繁地插入新的关键帧,并维护一个更大的关键帧数据库。为了进行联合优化,我们从全局关键帧列表中随机采样 N g N_g Ng射线的总数,以优化场景表示和相机姿态。关节优化以交替的方式进行。具体来说,我们首先优化 k m k_m km步长的场景表示 θ θ θ,并使用相机参数 { ξ t } \{\xi_t\} {ξt}的累积梯度更新相机姿态。由于每个相机姿态只使用6个参数,这种方法可以提高相机姿态优化的鲁棒性,而梯度累积的额外计算成本可以忽略不计。
复现过程中遇到的困难
这个过程中遇到最麻烦的问题就是pytorch会报错内存问题以及tiny-cuda-nn和pytorch3d的安装,这两个库花了快一天时间,再加上师兄给催的比较紧,所以当时没怎么思考就不断尝试,最后熬夜静下心来好好整理一下,解决。
首先先把requirements.txt中的最后两行删掉,我们要手动安装,采用它们的方式肯定会有问题。
git+https://github.com/facebookresearch/pytorch3d.git
git+https://github.com/NVlabs/tiny-cuda-nn/#subdirectory=bindings/torch
pytorch
项目的开源baseline:[HengyiWang/Co-SLAM: CVPR’23] Co-SLAM: Joint Coordinate and Sparse Parametric Encodings for Neural Real-Time SLAM中用的是以下方法安装的pytorch:
但是我在多个服务器用该方法安装都不成功,有的会报错显存不足,有的直接kill掉安装指令。后来查询之后发现是因为这种方式下载的临时文件很大,超出了系统分配的预置空间,所以不能用这个指令,可以直接在pytorch官网Previous PyTorch Versions | PyTorch上找对应版本的安装指令:
这里有个小细节,发现AutoDL上的服务器用给定的指令:
conda install pytorch==1.10.0 torchvision==0.11.0 torchaudio==0.10.0 cudatoolkit=11.3 -c pytorch -c conda-forge
无法安装,可以把后面的conda-forge修改成功nvidia:
conda install pytorch==1.10.0 torchvision==0.11.0 torchaudio==0.10.0 cudatoolkit=11.3 -c pytorch -c nvidia
这样pytorch就安装成功了。
tiny-cuda-nn
项目的开源baseline:[HengyiWang/Co-SLAM: CVPR’23] Co-SLAM: Joint Coordinate and Sparse Parametric Encodings for Neural Real-Time SLAM中提到:
我们要手动从源代码中安装,先clone对应的repo,可以任意指定文件夹:
# Build tinycudann
git clone --recursive https://github.com/nvlabs/tiny-cuda-nn
相关依赖
这时候不要着急去安装,先去tiny-cuda-nn的官网: https://github.com/nvlabs/tiny-cuda-nn上查看,可以发现此处需要一些相关依赖:
windows的c环境直接安装visual studio 2019就行,linux需要安装gcc且版本需要在8以上。先使用
gcc --version
查看gcc版本,基本大多数服务器都在8以上,所以这里不考虑升级。
cuda需要在10.2以上,也很容易解决。
CMake的3.21版本确实要求很高,这里单独安装一下:
我用3.24版本比较多,这里直接去它的官网:https://cmake.org/download/,下载cmake-3.24.1.tar.gz
,然后解压:
tar -zxvf cmake-3.24.1.tar.gz
进入到解压后的文件夹,然后执行bootstrap
文件进行检查:
cd cmake-3.24.1
./bootstrap
基本不会出错,检查完之后开始编译:
make
这里需要花费几十分钟的时间,编译完成后执行安装:
sudo make install
安装完成后重开一个终端,就能查询到更新后的cmake版本:
cmake --version
安装
这里没有什么坑,直接按照co-slam的要求执行即可:
# Try this version if you cannot use the latest version of tinycudann
#git reset --hard 91ee479d275d322a65726435040fc20b56b9c991
cd tiny-cuda-nn/bindings/torch
python setup.py install
不过安装过程消耗时间也会很久,几十分钟左右。
pytorch3d
linux系统安装pytorch3d确实有些麻烦,这里参考了很多博客:pytorch3d安装|踩坑指南 - 知乎,blog.csdn.net/weixin_60739161/article/details/135680823。可以总结为两个部分,相关依赖和对应库。
相关依赖
官方给的文档如下:
其他条件基本没问题,主要是fvcore和ioPath这俩需要单独安装。可以直接用conda install,但是注意一定要在虚拟环境下,否则安装过程需要删除掉一些conda库,但是base环境下该库是不能动的。
conda install -c fvcore -c iopath -c conda-forge fvcore iopath
安装
然后在官网Files | Anaconda.org上个下载符合自己环境的安装包,比如我的python是3.7,torch是1.10.0,cuda是11.3,那我就下载pytorch3d-0.7.1-py37_cu113_pyt1110.tar.bz2
下载完成后不用解压,直接安装:
conda install pytorch3d-0.7.1-py37_cu113_pyt1110.tar.bz2
这里也需要很长时间,几十分钟左右。
这样两个依赖就可以完成。
运行
修改config文件夹里的yaml文件,把指定路径索引到数据集,然后命令行输入以下指令即可:
python coslam.py xxx/xxxx.yaml