
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(平移2 旋轉2 光流均值2),超過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 用這些對應關系在因子圖上聯合優化位姿與深度。