DROID-SLAM 核心概念拆解:光流、关键帧与 BA 前后端架构

发布时间:2026/8/9 10:22:54
DROID-SLAM 核心概念拆解:光流、关键帧与 BA 前后端架构 SLAM 算法相关基础知识概念本笔记围绕DROID-SLAM梳理 SLAM 的三个核心基础概念光流Optical Flow、关键帧Keyframe、BABundle Adjustment与前后端架构。三者构成一条因果链光流DroidNet 预测的稠密像素对应 │ ├──→ 运动量够大→ 选为「关键帧」MotionFilter 阈值过滤 │ └──→ 作为「观测 / 约束」喂给因子图 → BA 优化位姿 深度 ↑ 前端局部 BA实时 后端全局 BA 回环阅读顺序建议先读 一、BA 与前后端 建立整体架构观再读 二、关键帧 和 三、光流 理解 BA 的输入从哪来。一、BA 与前后端这些都是 SLAM 领域的标准术语。下面分层次讲清楚。BA 是什么 —— Bundle Adjustment光束法平差 / 集束优化名字来源“Bundle”(光束)指相机发射出的光线;每条光线经过一个 3D 点,投影到图像上的一个像素。Bundle Adjustment 的本质:调整光束——也就是同时调整相机位姿和 3D 点位置——使得3D 点重投影回图像的位置与实际观测到的像素位置尽可能吻合。数学表达假设有相机位姿T i T_iTi​、3D 点X j X_jXj​,它的真实观测像素是u i j u_{ij}uij​。BA 要最小化重投影误差:min ⁡ T i , X j ∑ i , j ρ ( ∥ π ( T i , X j ) − u i j ∥ 2 ) \min_{T_i, X_j} \sum_{i,j} \rho\Big( \big\| \, \pi(T_i, X_j) - u_{ij} \,\big\|^2 \Big)Ti​,Xj​min​i,j∑​ρ(​π(Ti​,Xj​)−uij​​2)其中π ( T i , X j ) \pi(T_i, X_j)π(Ti​,Xj​)是把 3D 点X j X_jXj​用位姿T i T_iTi​投影到像素坐标。通俗说:“调整相机和地图,让预测的像素位置和真实像素对齐。”为什么 SLAM 都要用 BA光靠帧间追踪,误差会不断累积(走 100 步,每步差一点,最后位置完全漂了)。BA 把很多帧、很多点放在一起联合优化,误差被全局摊薄,这正是 SLAM 保持精度的关键。在 DROID-SLAM 里 BA 的特殊之处传统 BA 用的是手工特征点(SIFT/ORB)的匹配关系。DROID-SLAM 用DroidNet 神经网络直接预测两帧之间的稠密光流(就是成对的像素对应关系),然后用这些光流作为 BA 的观测,在因子图(factor graph)上迭代求解。前端 / 后端 —— SLAM 的标准架构划分这是 SLAM 系统设计中的两大模块分工,几乎所有现代 SLAM 都这么分。类比:自动驾驶汽车可以这样形象地理解:前端 (Frontend)后端 (Backend)角色“驾驶员的眼睛”——快速感知当下“导航员的大脑”——全局统筹修正任务实时、局部地处理每一帧离线、全局地优化整个轨迹速度必须快(跟得上视频帧率)可以慢(攒够数据后批量做)范围只看最近几个关键帧看所有关键帧 回环前端 (DroidFrontend) 详解“局部 BA”—— 在一个滑动窗口(sliding window)里做。所有帧: K1 K2 K3 ... [K20 K21 K22 ... K45] ← 窗口(最近 25 个关键帧) ↑ 前端只优化这一段窗口大小frontend_window25:只关心最近的 25 个关键帧。目的:追得上、追得稳。视频一直在流式输入,前端要实时估计当前位姿,不能等。每来一个新关键帧,窗口往前滑动,丢掉最老的,加进新的,做一次局部 BA。后端 (DroidBackend) 详解“全局 BA”—— 在所有关键帧构成的大图上做,并且重点处理回环(loop closure)。全部关键帧: K1 --- K2 --- ... --- K100 --- ... --- K1(回到起点回环!) ↑ ↑ └────── 发现回环,全局 BA 把误差摊平 ──┘backend_thresh22.0:当一个新关键帧与历史上某个老关键帧的相似度/距离低于阈值时,认为检测到回环(“我又走回原来这个地方了”)。回环的作用极其重要:它给系统一个强约束——“第 1 帧和第 100 帧其实是同一个位置”,于是可以把中间累积的漂移误差整体拉回来。后端不追求实时,它周期性地跑,把前端积累的所有关键帧、所有约束(包括回环)放在一张大因子图上,做一次彻底的全局优化。为什么要分前后端?关键在于实时性和精度的矛盾:全局 BA 精度高,但太慢,做不到每帧都跑 → 不能让前端等它。局部 BA 速度快,但只顾眼前,时间长了会漂移 → 需要后端兜底修正。所以分工是:前端保实时、后端保精度,两者各司其职,数据通过DepthVideo(共享缓冲)传递。对应到 DROID-SLAM 的代码droid_slam/droid.pyclassDroid:def__init__(self):self.videoDepthVideo()# 共享数据池(图像位姿深度置信度)self.filterMotionFilter(...)# 非关键帧的快速初始化(比前端还轻)self.frontendDroidFrontend(...)# 【前端】窗口25 的局部 BAself.backendDroidBackend(...)# 【后端】全局 BA 回环deftrack(self,tstamp,image,depth,intrinsics):# 每帧都调用:喂给 motion filter 触发前端...defterminate(self):# 视频结束:跑最后一次后端全局 BA,返回完整轨迹...调用流程:每一帧 → MotionFilter(快速估位姿) → 判定是否关键帧 ↓ 是关键帧 DroidFrontend(局部 BA,窗口内迭代) ← 实时进行 ↓ 攒够一定数量 DroidBackend(全局 BA 回环检测) ← 周期性进行 ↓ 视频结束 terminate() → 最终全局 BA → 输出轨迹一张图总结原始图像流 │ ▼ ┌─────────────┐ 快速、实时 │ MotionFilter │ ── 非关键帧的位姿粗估 └──────┬──────┘ │ 关键帧(运动阈值) ▼ ┌──────────────┐ BA Bundle Adjustment(光束法平差) │ 前端 Frontend│ ── 局部 BA:滑窗 25 帧,实时追得稳 │ (局部 BA) │ └──────┬──────┘ │ 周期性触发 ▼ ┌──────────────┐ loop closure(回环) │ 后端 Backend │ ── 全局 BA:全部关键帧 回环,消除漂移 │ (全局 BA) │ └──────────────┘总结:BA(Bundle Adjustment,光束法平差) 同时调整相机位姿和 3D 点,让重投影像素与真实观测像素对齐的优化过程,是 SLAM 消除误差的核心手段。前端 跑在最近几个关键帧上的局部、实时BA,保证系统跟得上视频流。后端 跑在所有关键帧上的全局、周期性BA,重点处理回环,消除长时间累积的漂移。两者通过共享的DepthVideo缓冲协作。二、关键帧关键帧 (Keyframe)是 SLAM 里最核心的概念之一,直接决定了系统的精度、速度和内存占用。为什么需要关键帧 —— 核心动机先理解一个矛盾:不是每一帧都值得做 BA。视频流: F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 ... ↑ ↑ 如果每帧都做 BA: ✗ 太慢(算力爆炸) 如果只拿一帧做 BA: ✗ 太少(信息不够) 需要挑有代表性的帧 → 关键帧 ✓ 平衡想象你在拍视频绕一个物体一圈(360°):如果物体没什么变化,连续 100 帧几乎一模一样→ 这些帧信息冗余,没必要全留着做优化。如果相机转了很大角度,看到了新的视角→ 这一帧信息量大,值得标记为关键帧,纳入 BA 优化。关键帧的本质关键帧 信息量足够大、值得花算力做 BA 优化的代表帧非关键帧(普通的帧)只是用来跟踪当前位姿,不参与 BA,也不维护深度。什么算信息量足够大 —— 两种主流判据主流 SLAM 系统判断关键帧的方法有两种:方法 1:运动量判据Translation Rotation最经典、最常用,ORB-SLAM、DROID-SLAM 都用类似思路。核心思想:当相机移动/旋转超过一定量,就选一个新关键帧。F1(关键帧) → F2 → F3 → F4 → F5 ↑ 相机移动了 0.3 米 旋转了 15° 超过阈值 → F5 成为新关键帧方法 2:视差/重叠率判据核心思想:当当前画面与上一个关键帧的差异超过阈值时,选新关键帧。可以用几种方式衡量:平均光流大小:两帧之间所有像素的平均位移视差 (parallax):同一个 3D 点在两帧间的像素位移图像相似度:SSIM、CNN 特征距离等DROID-SLAM 用的是一种综合判据(下面详述)。DROID-SLAM 中关键帧的具体判定DROID-SLAM 的判断逻辑在motion_filter.py里,核心参数是filter_thresh(默认1.0)。判定流程MotionFilter类# 伪代码,源自 droid_slam/motion_filter.pyclassMotionFilter:def__init__(self,net,video,thresh1.0,devicecuda):self.threshthresh# 阈值:1.0self.netnet# DroidNetself.videovideo# 共享的 DepthVideoself.count0# 当前相对上一个关键帧累积了多少帧def__call__(self,tstamp,image,intrinsics):# 1. 第一帧永远是关键帧ifself.video.counter.value0:self.add_keyframe(tstamp,image,intrinsics)return# 2. 计算当前帧与最近关键帧的距离# 用 DroidNet 预测当前帧相对上一个关键帧的光流# 流的大小直接反映了两帧之间的运动量withautocast(enabledTrue):mapself.net.predict_flow(...)# 网络预测的光流# 3. 欧氏距离:衡量运动量dcompute_distance(map)# 计算综合位移# 4. 阈值判断ifdself.thresh:# 默认 1.0# 运动量不足 → 当作普通帧,只快速更新位姿self.video.tracked_poses[tstamp]estimated_poseelse:# 运动量足够 → 选为新关键帧!self.add_keyframe(tstamp,image,image,intrinsics)DROID-SLAM 的综合距离度量DROID-SLAM 用的是光流幅值 平移 旋转的综合度量。具体来说,compute_distance类似:distance ||displacement_vector|| sqrt( (平移量)^2 (旋转量)^2 (光流均值)^2 )更精确地说,DROID-SLAM 通过DroidNet预测两帧之间的光流,然后计算光流的平均幅值,再结合平移、旋转参数算出一个标量d。参数含义参数默认值作用filter_thresh1.0MotionFilter 选关键帧的阈值。值越小,关键帧越密(精度↑、速度↓);值越大,关键帧越稀疏keyframe_thresh4.0前端(滑窗 BA)插入关键帧的阈值(frontend_thresh16.0是前端成对因子添加阈值,不同含义,见下文)warmup8系统启动的前 N 帧强制全部作为关键帧,让系统快速初始化各个阈值的作用层级这几个参数很容易混淆,它们作用在不同的阶段:所有帧 F1, F2, F3, ... │ ├─ warmup8:前 8 帧强制全选为关键帧(初始化) │ ▼ [MotionFilter] ─── filter_thresh1.0 ─── 判断当前帧 vs 最近关键帧 │ 运动量是否够大? ├─ 不够 → 非关键帧(只更新位姿,不参与 BA) │ └─ 够 → 关键帧 │ ▼ [DroidFrontend] ── frontend_window25 ── 只在最近 25 个关键帧上做 BA frontend_thresh16.0 ── 前端在关键帧对之间添加因子(约束) 的距离阈值 │ ▼ [DroidBackend] ── backend_thresh22.0 ── 后端全局 BA 回环检测关键帧与非关键帧的不同待遇这是理解为什么要分关键帧的关键:待遇完全不同。关键帧 (Keyframe)非关键帧存储存入DepthVideo缓冲(保留图像、位姿、深度、置信度)只临时存位姿,用完丢弃深度维护并优化稠密深度图无独立深度BA 优化✅ 参与前端局部 BA 后端全局 BA❌ 不参与 BA因子图作为图节点,被因子约束不进图用于回环✅ 可作为回环匹配候选❌ 不参与计算开销高(存图像 维护深度 多次 BA)低(只做一次快速位姿估计)待遇的差异本质上就是算力分配策略:关键帧 高价值,值得投入;非关键帧 低价值,快速过一遍即可。参数调节的实际影响调节filter_thresh直接影响系统行为:filter_thresh 0.5 (小阈值 → 严格筛选 → 关键帧密集) ├── 优点:精度高(更多帧参与 BA,约束更充分) ├── 缺点:速度慢、内存占用大 └── 适用:高精度重建、慢速运动场景 filter_thresh 1.0 (默认,平衡) filter_thresh 2.0 (大阈值 → 宽松筛选 → 关键帧稀疏) ├── 优点:速度快、省内存 ├── 缺点:可能漏掉关键信息,精度下降 └── 适用:快速运动场景、实时性要求高数量对比直觉个人推断⚠️ 下面数量级是直觉估计个人推断用于建立量感非实测实际取决于场景纹理、运动速度等。假设 300 帧视频,相机匀速绕物体:filter_thresh1.0→ 可能选出 ~30-50 个关键帧(每 ~7-10 帧一个)filter_thresh0.5→ 可能选出 ~80-120 个关键帧(每 ~3-4 帧一个)filter_thresh2.0→ 可能选出 ~15-20 个关键帧而前端窗口固定 25 帧,所以关键帧越密,前端窗口覆盖的实际时间范围越短。小结概念定义关键帧信息量足够大、值得花算力做 BA 优化的代表帧。存入DepthVideo,参与 BA,维护深度。非关键帧信息量小(与上一个关键帧相比运动不足)。只快速估计位姿,不参与 BA。判定方法DROID-SLAM 用MotionFilter计算当前帧与最近关键帧的光流综合距离,与阈值filter_thresh1.0比较。核心公式距离 sqrt(平移² 旋转² 光流均值²),超过1.0→ 关键帧三个阈值filter_thresh1.0(选关键帧) /frontend_window25(前端滑窗) /backend_thresh22.0(后端回环)一句话:关键帧就是 SLAM 系统精挑细选出来、真正值得投入算力做优化的帧。通过MotionFilter用光流运动量阈值过滤,实现精度 vs 速度的平衡。三、光流 (Optical Flow)好问题。光流 (Optical Flow)是计算机视觉里最基础、最重要的概念之一。在 DROID-SLAM 里,它是整个系统的眼睛——BA 优化的所有约束本质上都来自光流。光流的定义光流 连续两帧图像之间,每个像素的运动矢量(往哪个方向、移动了多少)。简单说,光流描述的是:视频里同一个物体上的同一个点,在相邻两帧之间,从图像的哪个位置移动到了哪个位置。直观例子想象你拍一段视频,一个人从画面左边走到右边:第 1 帧: 第 2 帧: ●←─(人) (人)→● 像素在 (100, 200) 像素移动到了 (130, 200)这个人的鼻尖像素,在第 1 帧位于(100, 200),在第 2 帧跑到了(130, 200)。那么这个像素的光流就是:光流 (130-100, 200-200) (30, 0) └────────┬───────┘ 水平30,垂直0光流就是一个二维位移向量( d x , d y ) (dx, dy)(dx,dy),描述每个像素走了多远、往哪走。稠密光流 vs 稀疏光流这是理解 DROID-SLAM 的关键。稀疏光流传统 SLAM 用的只追踪少数特征点(比如 1000 个角点):第 1 帧: 第 2 帧: ┌────────────┐ ┌────────────┐ │ • • │ │ • • │ ← 只追踪这些点 │ • • │ │ • • │ │ • • │ │ • • │ └────────────┘ └────────────┘ (如 ORB-SLAM、LK 光流)优点:快缺点:信息少,只能用特征明显的点,纹理弱的区域(白墙、地板)就抓瞎稠密光流DROID-SLAM 用的追踪每一个像素:第 1 帧: 第 2 帧: ┌────────────┐ ┌────────────┐ │ •••••••••• │ │ •••••••••• │ ← 每个像素都有 │ •••••••••• │ │ •••••••••• │ 一个运动向量 │ •••••••••• │ │ •••••••••• │ └────────────┘ └────────────┘每个像素都有一个( d x , d y ) (dx, dy)(dx,dy)向量整张图的光流,大小是H × W × 2(宽×高×两个分量)DROID-SLAM 用DroidNet神经网络直接预测这个稠密光流这正是 DROID-SLAM 比传统 SLAM 强的地方:它不挑特征点,全图所有像素都参与,所以即使是无纹理区域也能建立约束。光流的数学形式对图像I 1 I_1I1​中的每个像素( x , y ) (x, y)(x,y),光流给出它在I 2 I_2I2​中的对应位置:I 1 ( x , y ) ≈ I 2 ( x d x , y d y ) I_1(x, y) \approx I_2(x dx, y dy)I1​(x,y)≈I2​(xdx,ydy)这就是亮度恒定假设:同一个点在两帧里亮度/颜色不变,只是位置变了。整个光流场可以可视化为一张图——每个箭头表示一个像素的运动方向和大小:光流可视化(箭头表示运动方向): ────→──── ────→──── 相机向左平移 → 所有像素向右流动 ────→────为什么光流对 SLAM / BA 这么重要这是理解前面所有讨论的关键。回顾一下前面的问题链:Q: BA 是什么? A: 调整相机位姿和 3D 点,让重投影误差最小。 Q: 重投影误差怎么算?需要知道哪个像素对应哪个 3D 点。 A: ← 这正是光流提供的!光流 BA 的观测数据传统 BA 的流程:手工特征点 → 特征匹配 → 匹配关系喂给 BA → 优化DROID-SLAM 的流程:两帧图像 → DroidNet 预测稠密光流 → 光流作为匹配关系喂给因子图 → BA 优化光流告诉系统:“第 1 帧的像素( x 1 , y 1 ) (x_1, y_1)(x1​,y1​)和第 2 帧的像素( x 2 , y 2 ) (x_2, y_2)(x2​,y2​)是同一个 3D 点的投影。”有了这个对应关系,BA 才能建立约束方程去优化位姿和深度。具体到 DROID-SLAM 的因子图关键帧 K1 的像素 (x1,y1) ──光流──→ 关键帧 K2 的像素 (x2,y2) │ │ │ 光流说:这俩是同一个 3D 点 X │ │ │ ▼ ▼ X 投影到 K1 (x1,y1) X 投影到 K2 (x2,y2) │ │ └────── 这构成一个因子(约束) ──┘ │ ▼ BA 优化:调整 K1位姿、K2位姿、X 的位置 使两边的投影都对齐光流在本笔记三个概念中的三个关键角色现在你可以把前面的知识点串起来了:角色 1:判断关键帧MotionFilter当前帧 vs 最近关键帧 → DroidNet 预测光流 → 算光流平均幅值 │ 幅值 filter_thresh(1.0)? → 选为关键帧光流大 相机动了 该选关键帧了。如果光流很小(两帧几乎一样),说明没动,不选。角色 2:前端 BA 的约束来源DroidFrontend滑窗内每对关键帧之间 → DroidNet 预测光流 → 作为因子加入因子图 → 局部 BA角色 3:后端回环检测后的约束发现回环(当前帧 vs 老关键帧)→ 预测光流 → 加入因子图 → 全局 BA 拉回漂移DROID-SLAM 用神经网络预测光流的革命性传统方法(Lucas-Kanade、Farnebäck)算光流:基于亮度恒定 局部梯度大位移、遮挡、弱纹理时会失败只能处理小运动DROID-SLAM 用DroidNet预测光流:端到端学习,直接从两帧图像输出稠密光流能处理大运动、遮挡、弱纹理还附带输出置信度(哪些像素的光流可信,哪些不可信——比如动态物体、遮挡区域可信度低)关于动态物体光流的待核实点本笔记原文提到VIPE 要加 Segment-and-Track-Anything 的原因动态物体上的光流是错误的需要用分割掩码屏蔽掉这些区域的稠密对应否则会污染 BA。这部分来源未确认VIPE 具体指哪个系统、其与 Segment-and-Track-Anything 的集成细节标注为待核实建议后续补充原始链接/论文。一张图总结光流在 DROID-SLAM 中的地位关键帧 K1 关键帧 K2 (图像) (图像) │ │ └──────┬─────────────────┘ ▼ ┌────────────┐ │ DroidNet │ ← 神经网络预测两帧之间的稠密光流 └──────┬─────┘ │ ▼ 稠密光流 (H×W×2) ← 每个像素的运动向量 (dx, dy) 置信度图 ← 哪些像素可信 │ ▼ ┌─────────────────┐ │ 因子图 BA 优化 │ ← 光流 像素对应关系 BA 的约束 │ 位姿 深度 │ └─────────────────┘ │ ▼ 优化后的相机轨迹 稠密深度一句话总结:光流 (Optical Flow)就是连续两帧图像之间,每个像素的运动向量( d x , d y ) (dx, dy)(dx,dy)。它回答的是这个像素在下一帧跑到哪儿去了。在 DROID-SLAM 里,神经网络DroidNet直接预测稠密光流(全图所有像素),这些光流提供了哪个像素对应哪个像素的匹配关系——而这正是 BA 建立约束、优化位姿和深度的核心输入数据。光流大说明相机动了(选关键帧),光流还直接喂给因子图做 BA(优化位姿)。四、三者的串联总览把三个概念按数据如何流动串起来,就是 DROID-SLAM 的完整工作链路:┌─────────────┐ │ 视频帧流 │ └──────┬──────┘ ▼ ┌───────────────────────────┐ │ ① DroidNet 预测稠密光流 │ ← 概念三:光流 │ (当前帧 vs 最近关键帧) │ └──────┬────────────────────┘ │ 光流幅值 运动量 ▼ ┌───────────────────────────┐ │ ② MotionFilter 判定关键帧 │ ← 概念二:关键帧 │ 光流综合距离 1.0 ? │ │ ├ 否 → 非关键帧(只更新位姿)│ │ └ 是 → 关键帧,入 DepthVideo│ └──────┬────────────────────┘ │ 关键帧对之间的稠密光流 观测 ▼ ┌───────────────────────────┐ │ ③ 因子图 BA │ ← 概念一:BA 前后端 │ 前端:滑窗 25 帧局部 BA(实时)│ │ 后端:全部关键帧全局 BA 回环│ └──────┬────────────────────┘ ▼ 优化后的相机轨迹 稠密深度一句话:光流提供像素对应 → 运动量决定哪些帧值得优化(关键帧) → BA 用这些对应关系在因子图上联合优化位姿与深度。

相关新闻