摘要
DROID-W(WildGS-SLAM,CVPR 2026,ETH Zurich)在 DROID-SLAM 的密集 BA 框架基础上,引入可学习的逐像素不确定性掩码,并集成 3D Gaussian Splatting(3DGS)建图,专为动态场景设计。其核心突破在于:不确定性不是后处理掩码,而是与位姿、深度共同参与 BA 优化的状态量,仅用 385 个参数即可在线自适应动态场景。本文对 10 个关键工程优化点进行代码级梳理,每个优化点附具体文件路径,并总结对传统 SLAM 的借鉴价值。
一、整体架构
DROID-W 采用三进程并行架构 ,通过共享 GPU 内存(DepthVideo)实现高效的进程间通信,分工如下:
共享DepthVideo
共享DepthVideo
不确定性掩码反馈
全局BA结果
输入图像流
Process-1: Tracker
Process-2: Mapper
Process-3: Backend
Final Global BA
PoseTrajectoryFiller
ATE 评估
| 进程 | 职责 | 特点 |
|---|---|---|
| Tracker | 前端实时跟踪 + 局部 BA | 每帧必执行,保证实时性 |
| Mapper | 3D Gaussian Splatting 建图 | 提供可靠不确定性掩码 |
| Backend | 全局 BA 优化 | 每 20 KF 触发一次,处理大规模关键帧 |
三进程通过 tensor.share_memory_() 共享 GPU 张量,避免进程间数据拷贝。
二、逐步流程解析
2.1 逐帧处理(MotionFilter.track)
每帧必执行,非关键帧跳过重计算操作:
||delta||_mean > 2.5px
每N帧强制
均不满足
新帧到来
DroidNet fnet 提取128维特征图 (1/8分辨率)
构建4层金字塔相关体 (7×7窗口)
1步GRU更新 → 光流delta
双重关键帧判决
运动显著 → 加入KF
非KF,跳过重计算
调用Metric3D估计度量深度先验 (~5 FPS)
调用DINOv2提取384维语义特征 (~22 FPS)
关键帧判决采用双重机制:运动显著判决保证快速运动帧被捕获,强制关键帧机制防止慢速场景下关键帧密度不足。
2.2 前端局部BA(Frontend.__update)
每新增一个关键帧触发,核心流程:
距离过近 AND 非强制 AND 未超连续丢帧上限
否则保留
新KF触发局部BA
清理生命周期>25的老边 → inactive缓存
add_proximity_factors: NMS选边
4次BA迭代 (不确定性加权)
距离二次验证
rm_keyframe: 撤销刚加入的KF
额外2次BA / loop_ba
用当前pose/disp初始化下一帧
2.3 两阶段初始化
| 阶段 | 触发条件 | 操作 |
|---|---|---|
| 第一阶段 | 达到 warmup=12 帧 |
构建邻域边 (r=3),16次 BA,纯几何引导,快速得到初始位姿 |
| 第二阶段 | Mapper 完成初始建图后 | 重新添加邻域边,8次 BA,不确定性掩码就绪,精度更高 |
两阶段设计的意义在于:第一阶段不依赖不确定性(此时 Mapper 还未就绪),第二阶段在有可靠不确定性掩码后做精细化初始化。
2.4 在线全局BA(每20个KF触发)
Backend.dense_ba 采用 update_lowmem 低内存方案,将全部关键帧分批(每批8帧)计算相关体,解决大规模关键帧显存溢出问题,详见优化 8。
2.5 终止阶段
终止时依次执行:Final Global BA(长序列耗时 27--103 s)→ PoseTrajectoryFiller 用 MoBA 插值全部非关键帧 → evo 工具计算 ATE(kf_traj_eval / full_traj_eval)。
三、10个核心优化点
优化1:不确定性驱动的Bundle Adjustment(UDBA)
代码位置 :src/lib/droid_kernels.cu:324
用软权重替代 RANSAC 的内外点硬分类,动态像素自动降低对 BA 的贡献:
cpp
// CUDA kernel 内,每个像素的 BA 权重乘以不确定性倒数
float scale_uncer = max(45.0 * uncertainties[ix][i][j] - 35.0, 0.1f);
float w_uncer = max(min(1.0 / scale_uncer, 1.0f), 0.0f);
wu = wu * w_uncer; // u 方向加权
wv = wv * w_uncer; // v 方向加权
// 双向不确定性检查:同时考虑目标帧不确定性
float uncer_target = bilinear_interp(uncertainties[jx], target_x, target_y);
wu = wu * (1.0 / max(45.0 * uncer_target - 35.0, 0.1f));
传统SLAM借鉴:将 RANSAC 内外点硬分类替换为软权重,在 LM 优化内部自动衰减动态像素贡献,且梯度可反向更新权重本身。
优化2:DINOv2特征 × 仿射变换 = 极轻量可学习不确定性
代码位置 :depth_video.py:154、droid_kernels.cu:1022
不单独训练动态分割网络,用预计算的 DINOv2 特征加一个仅 385 参数的仿射头,实现在线自适应不确定性掩码:
python
# 前向计算 (Python侧)
# dino_feats_resize: [H/8, W/8, 384],affine_weights: [385]
y_cdot = dino_feats_resize[t] @ affine_weights[:-1] + affine_weights[-1]
uncertainty = torch.log(1.1 + torch.exp(y_cdot)) # softplus,log(1.1)约束最小值
affine_weights(385个参数)与 pose(7 DoF)、disp 一起,在每次 BA 迭代中通过 CUDA kernel 反向传播更新(Adam,lr=1e-2)。反向传播链:
∂ L ∂ w = ∂ L ∂ u ⋅ ∂ u ∂ y ⋅ ∂ y ∂ w = J t o t a l ⋅ σ ( y c d o t ) ⋅ f d i n o \frac{\partial L}{\partial \mathbf{w}} = \frac{\partial L}{\partial u} \cdot \frac{\partial u}{\partial y} \cdot \frac{\partial y}{\partial \mathbf{w}} = J_{total} \cdot \sigma(y_{cdot}) \cdot \mathbf{f}_{dino} ∂w∂L=∂u∂L⋅∂y∂u⋅∂w∂y=Jtotal⋅σ(ycdot)⋅fdino
其中 σ ( ⋅ ) \sigma(\cdot) σ(⋅) 为 sigmoid 函数, f d i n o ∈ R 384 \mathbf{f}_{dino} \in \mathbb{R}^{384} fdino∈R384 为 DINOv2 特征向量。
传统SLAM借鉴:利用现成语义特征(DINOv2、CLIP 等)加线性头,参数极少,可在 BA 过程中在线自适应,无需离线训练。
优化3:DINOv2特征一致性作为不确定性的数据项
代码位置 :droid_kernels.cu:1446
让高不确定性像素的特征不一致代价贡献更小:
L d a t a = 1 − cosine_sim ( f i , f j r e p r o j ) u i ⋅ u j r e p r o j L_{data} = \frac{1 - \text{cosine\_sim}(\mathbf{f}_i, \mathbf{f}_j^{reproj})}{u_i \cdot u_j^{reproj}} Ldata=ui⋅ujreproj1−cosine_sim(fi,fjreproj)
其中 u i u_i ui、 u j r e p r o j u_j^{reproj} ujreproj 分别为源帧和目标帧(重投影后)的不确定性值。梯度计算解耦形式:
∂ L ∂ u i = − r ⋅ u j ( u i ⋅ u j ) 2 \frac{\partial L}{\partial u_i} = -\frac{r \cdot u_j}{(u_i \cdot u_j)^2} ∂ui∂L=−(ui⋅uj)2r⋅uj
额外约束:cosine_sim < 0.5 的像素对截为 0,排除纯噪声;仅计算帧距 ≥ 2 \geq 2 ≥2 的边,排除相邻帧重叠导致的冗余计算。
传统SLAM借鉴:将语义特征一致性引入位姿优化残差,代替或补充光度一致性,DINOv2 特征对遮挡、光照变化天然鲁棒。
优化4:先验正则化约束不确定性不发散
代码位置 :droid_kernels.cu:1688
通过 Log-barrier 先验与硬约束防止不确定性数值发散:
cpp
// L_prior = log(u + 1),先验损失约束不确定性范围
// ∂L_prior/∂u = 1 / (u + 1)
J_prior[block_id][k] = 1.0 / (uncertainties[idx][i][j] + 1.0);
// 硬约束,限制不确定性在物理合理范围内
uncer = clamp(uncer, 0.1, 2.0); // uncertainty_retr_kernel
系统总损失:
L t o t a l = γ d a t a ⋅ L d a t a + γ p r i o r ⋅ L p r i o r + γ d e p t h ⋅ L d e p t h L_{total} = \gamma_{data} \cdot L_{data} + \gamma_{prior} \cdot L_{prior} + \gamma_{depth} \cdot L_{depth} Ltotal=γdata⋅Ldata+γprior⋅Lprior+γdepth⋅Ldepth
其中 γ d a t a \gamma_{data} γdata、 γ p r i o r \gamma_{prior} γprior、 γ d e p t h \gamma_{depth} γdepth 为各项损失权重。
传统SLAM借鉴:对可学习参数加 log-barrier 先验,既防止数值不稳定,又给参数一个物理意义上合理的范围。
优化5:Scale-Shift联合BA(度量深度先验融合)
代码位置 :src/geom/ba.py:BA_with_scale_shift
解决单目深度先验的尺度和偏移未知问题,为每个关键帧维护独立的 scale/shift 参数,与深度、位姿联合优化:
d i = s i ⋅ d ^ i m o n o + t i d_i = s_i \cdot \hat{d}_i^{mono} + t_i di=si⋅d^imono+ti
其中 s i s_i si、 t i t_i ti 为第 i i i 个关键帧的尺度和偏移, d ^ i m o n o \hat{d}_i^{mono} d^imono 为 Metric3D 输出的视差。对应的额外 Jacobian:
J s = − d ^ i m o n o ⋅ α , J t = − α , H s t = J s t ⊤ J s t \mathbf{J}{s} = -\hat{d}i^{mono} \cdot \sqrt{\alpha}, \quad \mathbf{J}{t} = -\sqrt{\alpha}, \quad \mathbf{H}{st} = \mathbf{J}{st}^\top \mathbf{J}{st} Js=−d^imono⋅α ,Jt=−α ,Hst=Jst⊤Jst
valid_depth_mask(有效深度像素)权重放大 10×,确保可信深度对校准有更大拉力。
传统SLAM借鉴:将外部深度先验(IMU、激光、单目网络)通过 scale-shift 参数化融入滑窗 BA,自由度分配更合理。
优化6:关键帧撤销机制(含连续撤销上限)
代码位置 :frontend.py:78
BA 后二次确认关键帧有效性,避免前端预判误差,同时设置连续撤销上限防止退化场景下无限丢帧:
python
if (d.item() < keyframe_thresh
and num_dropped < max_consecutive_drops
and not force):
graph.rm_keyframe(t1 - 1) # ← 撤销刚加入的 KF
video.counter -= 1
num_keyframes_dropped += 1
else:
num_keyframes_dropped = 0 # ← 重置连续撤销计数
传统SLAM借鉴:关键帧选择放到 BA 后再确认,利用优化后的更精确估计做二次判断,提升关键帧选择准确率。
优化7:边的生命周期管理(Age + Inactive缓存)
代码位置 :factor_graph.py:85、factor_graph.py:162
避免直接删除老边导致的长程约束丢失,采用"生命周期管理 + 缓存池"策略:
python
# 老边不删除,移入 inactive 缓存
self.ii_inac = torch.cat([self.ii_inac, self.ii[mask]], 0)
self.jj_inac = torch.cat([self.jj_inac, self.jj[mask]], 0)
self.target_inac = torch.cat([self.target_inac, self.target[mask]], 0)
# BA 时按需激活近期 inactive 边
if use_inactive:
m = (self.ii_inac >= t0 - 3) & (self.jj_inac >= t0 - 3)
ii = torch.cat([self.ii_inac[m], self.ii], 0)
jj = torch.cat([self.jj_inac[m], self.jj], 0)
传统SLAM借鉴:co-visibility 图的边不必立即丢弃,可用 age/score 管理池化,按需激活,尤其适合重访场景。
优化8:低内存全局BA(分块相关体)
代码位置 :factor_graph.py:update_lowmem
解决全局 BA 中"所有关键帧相关体无法同时放入显存"的问题,懒计算 + 分批处理:
python
for i in range(0, jj.max() + 1, 8):
v = (self.ii >= i) & (self.ii < i + 8)
# 特征图 fmaps 存于共享 GPU 内存,按索引查询
corr = AltCorrBlock(fmaps)(coords1[:, v], rig * iis, rig * jjs)
# 更新这批边的 net/target/weight
net[v], target[v], weight[v] = ...
将全部关键帧按批次(每批 8 帧)处理,节省 2--4 倍显存。
传统SLAM借鉴:大规模地图优化时,将特征/描述子以懒计算方式组织,按窗口分批做 Schur 补求解,节省显存。
优化9:基于NMS的近邻边选择
代码位置 :factor_graph.py:add_proximity_factors(nms_invalidate_ + precompute_offsets)
替代传统 SLAM "选最近 k 帧构建边"的方式,用 NMS 保留几何视差显著的边:
- 如果两帧距离很小,则在一定邻域内抑制其他近邻边(NMS 抑制半径内的冗余边);
- 保留几何视差显著的边,确保约束的有效性与多样性。
传统SLAM借鉴:构建稀疏但多样的约束图,降低计算量的同时保证优化精度。
优化10:Mapper侧不确定性掩码闭环
代码位置 :mapper.py:637、mapper.py:876
Tracker 优化的不确定性直接传递给 3DGS Mapper,形成"优化-建模-反馈"闭环:
python
def _get_video_uncertainty_mask(self, kf_idx):
uncertainty = self.video.uncertainty[kf_idx] # Tracker 维护的不确定性图
uncer_rescaled = torch.clamp(45 * uncertainty - 35, min=0.1)
mask = torch.clamp(1.0 / uncer_rescaled, 0, 1)
# 高不确定性(动态)→ mask≈0 → 3DGS 损失中权重趋零
return mask
传统SLAM借鉴:前端优化的动态场景置信度可直接指导建图侧(3DGS/NeRF/点云)的损失权重,提升建图一致性。
四、借鉴价值汇总
| 借鉴点 | DROID-W 做法 | 传统方案改进方向 |
|---|---|---|
| 动态点处理 | 软权重 1 / u 1/u 1/u,梯度可反传 | 替代 RANSAC 硬分类,提升鲁棒性 |
| 深度先验融合 | per-KF scale-shift + Schur 补 | 优化 IMU/激光深度约束参数化方式 |
| 语义特征利用 | DINOv2 + 线性层 → 动态掩码 | 无需训练专用分割网络,降低开发成本 |
| 双向一致性检查 | 源帧 × 目标帧不确定性乘积 | 提升动态遮挡场景鲁棒性 |
| 参数极简设计 | 385 参数仿射头,在优化中自适应 | 轻量可学习模块嵌入几何优化 |
| Log-barrier 先验 | L p r i o r = log ( u + 1 ) L_{prior} = \log(u+1) Lprior=log(u+1) | 约束辅助变量范围,避免数值发散 |
| 关键帧二次确认 | BA 后再做距离判断 | 比前端预判更可靠的关键帧管理 |
| 边生命周期管理 | Age + inactive 缓存池 | 滑窗外约束不直接丢弃,提升轨迹一致性 |
| NMS 边选择 | 在距离矩阵上做 NMS | 构建稀疏但多样的约束图,降低计算量 |
| 分块低内存 BA | AltCorrBlock + 分批处理 | 大规模地图 BA 的显存优化范式 |
小结
DROID-W 最值得借鉴的设计核心在于:把不确定性当作可优化的状态量,而非后处理滤波器 。传统 SLAM 里,动态物体的处理往往是在外部做分割或 RANSAC 剔除,再喂给优化器------这条链路的问题在于分割误差会直接污染 BA。DROID-W 的方案是把动态像素的"可信度"直接参数化成 u i u_i ui,让它在 BA 的内层迭代里和位姿、深度一起被梯度更新。385 个参数的仿射头保证了这个机制的轻量性,DINOv2 特征的语义鲁棒性保证了初始估计的质量。
局限性上,Metric3D 的 5 FPS 瓶颈是当前实时性的主要制约,Final Global BA 在长序列下耗时可达 100s 以上,限制了离线后处理以外的应用场景。如果目标是嵌入实时系统,深度先验推理的轻量化替换(MiDaS-small、DepthFM 等)和增量式全局 BA 是两个值得探索的方向。