[论文笔记]DROID-SLAM

DROID-SLAM和Raft(ECCV2020 Best Paper)的通讯都是ImageNet的一作,给跪了。

从dense mapping的角度来看,DROID-SLAM采用 ”缝合预测光流 + DBA + Upsample“的情况,极大的提高了一个预训练模型在各个场景的泛化性(相比于估深度的网络)。

从Localization的角度来看,与特征点法VSLAM的区别是:信息来源上完整的使用了1/8降采样后的RGB信息,使用预训练模型预测光流从而丢掉了特征匹配过程;与直接法VSLAM的区别是:预测光流的模块可以支持全局BA,直接法VSLAM时间距离较大的两帧之间没法Global BA;

正经的特征点法的BA流程是根据估计的特征点深度和相机位姿计算相应颜色的warping loss然后反过来优化特征点的深度和相机位姿。DROID-SLAM的流程是不求RGB的损失,而是根据RGB+RAFT求出光流偏差r+像素权重w,然后根据权重求光流损失优化降采样光流图的损失,从而优化降采样深度图和相机位姿。

一、高斯牛顿法

对于最小二乘问题:

m i n ( 1 2 ∣ ∣ f ( x ) ∣ ∣ 2 2 ) m i n ( 1 2 ∣ ∣ f ( x + Δ x ) ∣ ∣ 2 2 ) = m i n ( 1 2 ∣ ∣ f ( x ) + J ( x ) T Δ x ∣ ∣ 2 2 ) = [ f ( x ) + J ( x ) T Δ x ] T ⋅ [ f ( x ) + J ( x ) T Δ x ] = ∣ ∣ f ( x ) ∣ ∣ 2 2 + f ( x ) T J ( x ) T Δ x + Δ x T J ( x ) f ( x ) + Δ x T J ( x ) J ( x ) T Δ x min( \frac{1}{2}||f(x)||_2^2 )\\ min( \frac{1}{2}||f(x+\Delta x)||_2^2 ) \\ = min(\frac{1}{2}||f(x) + J(x)^T \Delta x||_2^2) \\ = [f(x) + J(x)^T \Delta x]^T \cdot [f(x) + J(x)^T \Delta x] \\ = ||f(x)||_2^2 + f(x)^T J(x)^T \Delta x + \Delta x^T J(x)f(x) + \Delta x^TJ(x)J(x)^T\Delta x min(21∣∣f(x)22)min(21∣∣f(x+Δx)22)=min(21∣∣f(x)+J(x)TΔx22)=[f(x)+J(x)TΔx]T[f(x)+J(x)TΔx]=∣∣f(x)22+f(x)TJ(x)TΔx+ΔxTJ(x)f(x)+ΔxTJ(x)J(x)TΔx

Δ x \Delta x Δx求导:

f ( x ) J ( x ) + J ( x ) J ( x ) T Δ x = 0 f(x)J(x) + J(x)J(x)^T \Delta x = 0 f(x)J(x)+J(x)J(x)TΔx=0

记作:

H ( x ) : = J ( x ) J ( x ) T g ( x ) : = − f ( x ) J ( x ) H ( x ) Δ x = g ( x ) H(x) := J(x) J(x)^T\\ g(x) := -f(x)J(x)\\ H(x) \Delta x = g(x) H(x):=J(x)J(x)Tg(x):=f(x)J(x)H(x)Δx=g(x)

即高斯牛顿法把 J J T J J^T JJT当作海森矩阵的近似,我们把上面的方程称作增量方程或者高斯牛顿方程。

高斯牛顿法的步骤:

  1. 给定初始值 x 0 x_0 x0

  2. 对于第 k k k次迭代,求出当前的雅可比矩阵 J ( x k ) J(x_k) J(xk)和误差 f ( x k ) f(x_k) f(xk)

  3. 求解增量方程: H Δ x k = g H \Delta x_k = g HΔxk=g

  4. 如果 Δ x k \Delta x_k Δxk足够小,则停止,否则返回第2步。

二、BA与图优化(后端,非线性优化)

在这里插入图片描述

在这里插入图片描述
参考下:https://max.book118.com/html/2019/0307/5201144303002014.shtm

假设有路标点 p p p, T T T代表从世界坐标系到像素坐标系的投影, h ( T i , p j ) h(T_i,p_j) h(Ti,pj)代表观测模型,即把第 j j j个路标点投影到第 i i i个像素坐标系下, z i j z_{ij} zij代表第 i i i个像素坐标系下拍到的第 j j j个路标点。

那么对于 m m m张图观测 n n n个路标点并进行BA的目标函数可以写成:

1 2 ∑ i = 1 m ∑ j = 1 n ∣ ∣ e i j ( x ) ∣ ∣ 2 2 = 1 2 ∑ i = 1 m ∑ j = 1 n ∣ ∣ z i j − h ( T i , p j ) ∣ ∣ 2 2 \frac{1}{2} \sum_{i=1}^m \sum_{j=1}^n||e_{ij}(x)||_2^2 \\ = \frac{1}{2} \sum_{i=1}^m \sum_{j=1}^n ||z_{ij} - h(T_i,p_j)||_2^2 21i=1mj=1n∣∣eij(x)22=21i=1mj=1n∣∣zijh(Ti,pj)22

对于上面的问题,只有 z i j z_{ij} zij是观测到的数据,我们把其他的全都当作待优化的变量:

x = [ T 1 , . . . , T m , p 1 , . . . , p n ] T = [ x c T , x p T ] x = [T_1,...,T_m,p_1,...,p_n]^T = [x_c^T,x_p^T] x=[T1,...,Tm,p1,...,pn]T=[xcT,xpT] 1 2 ∑ i = 1 m ∑ j = 1 n ∣ ∣ e i j ( x + Δ x ) ∣ ∣ 2 2 = 1 2 ∑ i = 1 m ∑ j = 1 n ∣ ∣ e i j + J T Δ x ∣ ∣ 2 = 1 2 ∑ i = 1 m ∑ j = 1 n ∣ ∣ e i j + F Δ x c + E Δ x p ∣ ∣ 2 \frac{1}{2} \sum_{i=1}^m \sum_{j=1}^n||e_{ij}(x+\Delta x)||_2^2 \\ = \frac{1}{2} \sum_{i=1}^m \sum_{j=1}^n ||e_{ij} + J^T \Delta x||^2 \\ = \frac{1}{2} \sum_{i=1}^m \sum_{j=1}^n || e_{ij} + F \Delta x_c + E \Delta x_p||^2 21i=1mj=1n∣∣eij(x+Δx)22=21i=1mj=1n∣∣eij+JTΔx2=21i=1mj=1n∣∣eij+FΔxc+EΔxp2

e i j e_{ij} eij对于路标点 p j p_j pj的偏导为 E i j E_{ij} Eij,记 e i j e_{ij} eij对相机位姿 T i T_i Ti的偏导为 F i j F_{ij} Fij J T = [ F , E ] J^T=[F,E] JT=[F,E]

求一下增量方程:

H = J J T = [ F T F F T E E T F E T E ] H = J J^T = \begin{bmatrix} F^TF & F^T E \\ E^T F & E^T E \end{bmatrix} \\ H=JJT=[FTFETFFTEETE]

Δ x = H − 1 g \Delta x = H^{-1}g Δx=H1g,如果路标点很多观测位姿也很多,计算 H H H矩阵的逆需要巨大的计算量,21世纪的VSLAM的一个重要进步是意识到了 H H H矩阵的稀疏结构,并发现该结构可以自然,显示的用图优化来表示。

J i j ( x ) = [ 0 , . . . 0 , ∂ e i j ∂ T i , 0 , . . . 0 ∣ 0 , . . . , 0 , ∂ e i j ∂ p j , 0 , . . . , 0 ] J_{ij}(x) = [0,...0,\frac{\partial e_{ij}}{\partial T_{i}},0,...0 |0,...,0,\frac{\partial e_{ij}}{\partial p_{j}},0,...,0] Jij(x)=[0,...0,Tieij,0,...0∣0,...,0,pjeij,0,...,0]

在这里插入图片描述

一般情况下,路标点的数量 n n n远大于相机数量 m m m,因此左上角非常小,整个矩阵看起来很像箭头,又叫箭头形矩阵。对于这种稀疏矩阵求逆的问题,常用 Schur消元,这个过程也被叫做 Marginalization(边缘化)。

2.1 Schur消元

我们把 H H H 矩阵的左上角,右上角,左下角,右下角的四个矩阵依次记作: ( B , E , E T , C ) (B, E, E^T, C) (B,E,ET,C)

因此增量方程写作:

H ( x ) : = J ( x ) J ( x ) T [ v w ] = g ( x ) : = − f ( x ) J ( x ) H(x) := J(x) J(x)^T\\ \begin{bmatrix} v \\ w \end{bmatrix} = g(x) := -f(x)J(x)\\ H(x):=J(x)J(x)T[vw]=g(x):=f(x)J(x) [ B E E T C ] [ Δ x c Δ x p ] = [ v w ] \begin{bmatrix} B & E \\ E^T & C \end{bmatrix} \begin{bmatrix} \Delta x_c \\ \Delta x_p \end{bmatrix} = \begin{bmatrix} v \\ w \end{bmatrix} [BETEC][ΔxcΔxp]=[vw]

B B B是对角块矩阵,每个块的维度是 c c c的维度, C C C也是对角块矩阵,每个块的维度是 3 × 3 3 \times 3 3×3 ( p p p的维度)。对角块矩阵求逆的难度远小于一般的矩阵,只是需要每个对角块矩阵分别求逆即可。通过消元将 H H H矩阵变成对角块矩阵先:

[ I − E C − 1 0 I ] [ B E E T C ] [ Δ x c Δ x p ] = [ I − E C − 1 0 I ] [ v w ] ⇒ [ B − E C − 1 E T 0 E T C ] [ Δ x c Δ x p ] = [ v − E C − 1 w w ] \begin{bmatrix} I & -EC^{-1} \\ 0 & I \end{bmatrix} \begin{bmatrix} B & E \\ E^T & C \end{bmatrix} \begin{bmatrix} \Delta x_c \\ \Delta x_p \end{bmatrix} = \begin{bmatrix} I & -EC^{-1} \\ 0 & I \end{bmatrix} \begin{bmatrix} v \\ w \end{bmatrix} \\ \Rightarrow \\ \begin{bmatrix} B-EC^{-1}E^T & 0 \\ E^T & C \end{bmatrix} \begin{bmatrix} \Delta x_c \\ \Delta x_p \end{bmatrix} = \begin{bmatrix} v -EC^{-1}w \\ w \end{bmatrix} [I0EC1I][BETEC][ΔxcΔxp]=[I0EC1I][vw][BEC1ETET0C][ΔxcΔxp]=[vEC1ww]

解出 Δ x c \Delta x_c Δxc之后代到下面,下面就是新的对角块矩阵,然后解出 Δ x p \Delta x_p Δxp就完事了。

[ B − E C − 1 E T ] Δ x c = v − E C − 1 w [B-EC^{-1}E^T ] \Delta x_c = v - E C^{-1}w [BEC1ET]Δxc=vEC1w

定义: S : = [ B − E C − 1 E T ] S := [B-EC^{-1}E^T ] S:=[BEC1ET]

在这里插入图片描述

三、DROID-SLAM,NIPS2021

3.1 PVFDM(Derivate from DROID-SLAM)

按照 “PVFDM,Probabilistic Volumetric Fusion for Dense Monocular SLAM, WACV2023” 的说法,先用上面的BA + 高斯牛顿法 拿到 1 8 \frac{1}{8} 81 resolution 的深度图(对于Euroc数据集是 69 × 44 512 × 384 \frac{69 \times 44}{512 \times 384} 512×38469×44),然后用一个 learnable upsample operation 来 recover full resolution 的深度图。

(这个上采样方法是抄作业的 “Raft,ECCV2020 Best Paper” )

传统方法是 “选择特征点 + sparse-depth BA”, 但是PVFDM发现 “降采样版本depth + full-depth BA + Raft做UpSample”

3.2 DROID-SLAM正文

传统的那一套基于BA优化的formulation的好处在于可能拓展到多模态传感器。

DROID-SLAM的评价是Learning-Based的SLAM系统鲁棒性较好,但是它的精度还远远不如传统算法。

RAFT是输入两帧RGB迭代的优化光流,DROID-SLAM是输入arbitrary数量的RGB,迭代的优化深度和位姿。(Joint global refinement of all camera poses and depth maps对于消除累计飘移和回环检测至关重要)

Each update of camera poses and depth maps in DROID-SLAM is produced by a differentiable Dense Bundle Adjustment (DBA) layer, which computes a Gauss-Newton update to camera poses and dense per-pixel depth so as to maximize their compatibility with the current estimate of optical
flow.

在这里插入图片描述

DROID-SLAM中可学习的参数有光流预测部分(包括特征提取和ConvGRU)+ 上采样部分,本身只需要光流真值做监督,但是加了DBA嵌入SLAM后又用预测的pose做了额外监督。训练出的来网络仍然只是做光流预测,为后面的DBA优化提供观测值。可以认为Droid-SLAM就是用RAFT光流代替传统直接法SLAM(如DSO)中的传统光流的SLAM。

DROID-SLAM是在四卡3090上TartanAir上训练了一周然后拿得到的 “Extractor+ConvGRU” 在各种数据集上infer,泛化能力强的主要原因还是在DBA这套传统方法上我感觉。

我们的方法在双3090上实时,第一张卡跑Tracking和Local BA,第二张卡跑global BA和Loop Closure。后端由于要存储feature map,所以比较花费显存。

消溶试验证明full BA很有用捏。

  • GRU输入的光流是啥?

    前端work之前都用零,前端work了(凑够八张)之后,用估的光流。

  • 相比于输入RGB,输入为RGBD时候,DBA是怎么样的形式?

    原文是这样说的:“In the case of RGB-D, we still treat depth as a variable, since sensor depth can be noisy and have missing observations, and simply add a term to the optimization objective.”

3.3 DROID-SLAM代码

  • Python和Pytorch基础知识

    • __setitem__(index, value): 索引赋值重写,即调用 Obj[index]=value等价于Obj.__setitem__(index, value)

    • torch.autograd.Function.apply 是 PyTorch 中一个用于定义自定义自动微分函数的方法。在 PyTorch 中,所有神经网络的核心是 torch.autograd.Function。这是一个抽象类,定义了所有向用户定义的函数应如何工作的基本方法。

      torch.autograd.Functionapply 方法是一个实现自动微分的关键部分。它允许你定义自己的函数,并指定如何计算函数的正向和反向传播。

      class CorrSampler(torch.autograd.Function):
      
          @staticmethod
          def forward(ctx, volume, coords, radius):
              ctx.save_for_backward(volume,coords)
              ctx.radius = radius
              corr, = droid_backends.corr_index_forward(volume, coords, radius)
              return corr
      
          @staticmethod
          def backward(ctx, grad_output):
              volume, coords = ctx.saved_tensors
              grad_output = grad_output.contiguous()
              grad_volume, = droid_backends.corr_index_backward(volume, coords, grad_output, ctx.radius)
              return grad_volume, None, None
      
    • python的setuptools,setup函数负责将自己写好的cpp或者cu代码封装好,以便python脚本直接调用。

      (具体教程见:https://blog.csdn.net/ygf666/article/details/127797494)

      如果想查看某个API到底做啥操作,一旦您用 C++ 和 ATen 编写了计算,可以使用 pybind11 以非常简单的方式将 C++ 函数或类衔接到 Python 中。

      from setuptools import setup
      from torch.utils.cpp_extension import BuildExtension, CUDAExtension
      
      import os.path as osp
      ROOT = osp.dirname(osp.abspath(__file__))
      setup(
          name='droid_backends',
          ext_modules=[
              CUDAExtension('droid_backends',
                  include_dirs=[osp.join(ROOT, 'thirdparty/eigen')],
                  sources=[
                      'src/droid.cpp', 
                      'src/droid_kernels.cu',
                      'src/correlation_kernels.cu',
                      'src/altcorr_kernel.cu',
                  ],
                  extra_compile_args={
                      'cxx': ['-O3'],
                      'nvcc': ['-O3',
                          '-gencode=arch=compute_60,code=sm_60',
                          '-gencode=arch=compute_61,code=sm_61',
                          '-gencode=arch=compute_70,code=sm_70',
                          '-gencode=arch=compute_75,code=sm_75',
                          '-gencode=arch=compute_80,code=sm_80',
                          '-gencode=arch=compute_86,code=sm_86',
                      ]
                  }),
          ],
          cmdclass={ 'build_ext' : BuildExtension }
      )
      

      pybind找到cpp函数和python函数的对应关系

      PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
        // bundle adjustment kernels
        m.def("ba", &ba, "bundle adjustment");
        m.def("frame_distance", &frame_distance, "frame_distance");
        m.def("projmap", &projmap, "projmap");
        m.def("depth_filter", &depth_filter, "depth_filter");
        m.def("iproj", &iproj, "back projection");
      
        // correlation volume kernels
        m.def("altcorr_forward", &altcorr_forward, "ALTCORR forward");
        m.def("altcorr_backward", &altcorr_backward, "ALTCORR backward");
        m.def("corr_index_forward", &corr_index_forward, "INDEX forward");
        m.def("corr_index_backward", &corr_index_backward, "INDEX backward");
      }
      
  • ⭐代码结构图:

    DROIDSLAM的DBA全都是CUDA写的,封装到droid_backend.ba函数中。
    图片太大了,没法上传,URL:https://github.com/Promethe-us/URL_Album/blob/main/202309/DROID-SLAM.png

3.4 DROID-SLAM在TartanAir_AbandonFactory和KITTI

在这里插入图片描述

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值