檢測實戰(zhàn):從位姿漂移到實時閉環(huán)完整方案)
做2D激光SLAM的多數(shù)人第一個大坎不是前端匹配而是地圖的長期一致性。機(jī)器人走個幾十米還能湊合一旦跑個幾百米再繞幾圈地圖就開始出現(xiàn)重影、錯位甚至本來筆直的走廊都合不攏。這個問題的本質(zhì)是位姿漂移而回環(huán)檢測Loop Closure就是專門用來收拾漂移的。所謂回環(huán)就是機(jī)器人回到曾經(jīng)到過的地方——把當(dāng)前激光幀和歷史關(guān)鍵幀做一個全局性的“認(rèn)親”認(rèn)上了就往后端丟一條閉環(huán)約束把累積誤差一次性拉回來。我前陣子在一個2D激光SLAM項目中完整實現(xiàn)了實時回環(huán)檢測從方案選型到工程落地踩了不少坑這篇就從頭到尾梳理一遍適合正在做SLAM、或者想搞明白回環(huán)檢測為什么能“治病”的同學(xué)。1. 回環(huán)檢測到底在解決什么問題1.1 漂移是怎么一點(diǎn)點(diǎn)攢起來的先說清楚漂移的來源。前端匹配本質(zhì)上是一個“相對定位”過程每一幀激光都跟前一幀或者局部地圖做配準(zhǔn)得到兩幀之間的相對位姿增量。這個增量的精度受傳感器噪聲、環(huán)境幾何、動態(tài)障礙物影響不可能完全準(zhǔn)確。關(guān)鍵問題在于增量誤差會一路累加下去。打個比方你在一個黑暗的走廊里蒙眼走路每一步都以為自己走了一米但實際每一步只有0.99米。走十步你還沒感覺走一百步你實際位置已經(jīng)比“自以為”的位置落后了一米。SLAM里的位姿漂移就是這個邏輯只不過誤差來源更復(fù)雜有激光噪聲、匹配算法的近似、還有環(huán)境退化帶來的方向滑動。特別要說一個很常見的場景——長走廊退化。在一條筆直的走廊里激光掃描到的幾何特征大部分都在墻壁平面內(nèi)沿著走廊方向幾乎沒有可辨識的特征。這時候掃描匹配對“沿著走廊方向的平移”和“繞z軸的微小旋轉(zhuǎn)”非常不敏感前端匹配結(jié)果會在這些自由度上“滑”來滑去。一次匹配滑個一厘米不顯眼連續(xù)滑幾十次軌跡就偏了。這也是為什么很多建圖項目的漂移重災(zāi)區(qū)都在走廊和空曠大廳。還有一類誤差來自動態(tài)物體。行人走過、門開合、叉車移動這些都會在掃描里產(chǎn)生不穩(wěn)定的點(diǎn)。如果不做過濾前端匹配很容易被這些點(diǎn)帶偏。1.2 回環(huán)檢測在整個SLAM系統(tǒng)中的位置要理解回環(huán)檢測得先建立整個2D LiDAR SLAM系統(tǒng)的全局觀。典型的系統(tǒng)分兩層前端和后端。前端負(fù)責(zé)“即時、局部”的定位把每個新掃描匹配到當(dāng)前局部地圖上輸出高頻位姿頻率通常在10Hz到幾十赫茲。前端做得好局部軌跡看著很平滑但它是個“近視眼”看不到全局。后端負(fù)責(zé)“全局、慢速”的優(yōu)化。最常見的形式是位姿圖Pose Graph節(jié)點(diǎn)是歷史關(guān)鍵幀位姿邊是節(jié)點(diǎn)之間的相對約束。普通情況下邊只有相鄰關(guān)鍵幀之間的前端匹配結(jié)果也就是順序約束。順序約束的問題是所有誤差都沿著軌跡方向傳遞前面偏了后面跟著偏沒有任何機(jī)制能把累積誤差“拽回來”。回環(huán)檢測的作用就是給這張位姿圖添加“捷徑”約束。當(dāng)機(jī)器人重新走到曾經(jīng)訪問過的地方回環(huán)檢測在歷史關(guān)鍵幀里找到匹配的候選形成一條閉環(huán)邊。這條邊的兩端位置在真實世界里應(yīng)該是同一個點(diǎn)但在地圖里因為漂移差了很大一截。后端優(yōu)化拿到這條約束后會把整張位姿圖重新拉平讓閉環(huán)的兩端對齊同時把中間軌跡的誤差分配到每一步里。所以回環(huán)檢測不是可有可無的增強(qiáng)功能對于需要在真實環(huán)境里長時間運(yùn)行的機(jī)器人它是剛需。沒有回環(huán)地圖規(guī)模一大必然崩有了回環(huán)地圖才能做到“繞一圈回到原點(diǎn)還能嚴(yán)絲合縫”。2. 回環(huán)檢測的三種主流實現(xiàn)路徑2.1 掃描匹配直接比對ICP家族的思路最直覺的做法是把當(dāng)前幀激光和歷史每一幀關(guān)鍵幀都做一次掃描匹配匹配分?jǐn)?shù)高就認(rèn)為是一個回環(huán)。匹配算法一般沿用ICP家族常見的有點(diǎn)到點(diǎn)ICP、點(diǎn)到線ICPPL-ICP、NDT。點(diǎn)到點(diǎn)ICP思路很簡單尋找兩幀點(diǎn)云中的最近點(diǎn)對最小化它們的歐氏距離之和迭代求解位姿。點(diǎn)到線ICP改進(jìn)了一步把點(diǎn)投影到目標(biāo)幀的線段上在走廊等結(jié)構(gòu)化環(huán)境里收斂精度更好。NDT則先把目標(biāo)幀建成分塊正態(tài)分布優(yōu)化當(dāng)前幀點(diǎn)在分布上的概率密度計算效率和對初始值的容忍度相對好一些。但直接用ICP做回環(huán)檢測有個大坑——對初始值敏感?;丨h(huán)檢測時當(dāng)前幀和候選歷史幀可能相隔很遠(yuǎn)我們不知道它們之間的粗略位姿關(guān)系只能從零開始猜。如果初始猜測差得遠(yuǎn)ICP很容易陷入局部極小值匹配出的位姿是錯的但收斂分?jǐn)?shù)可能還不錯。這種“匹配成功但實際是假配對”的情況是誤回環(huán)的主要來源之一。所以實際工程里ICP很少單獨(dú)作為回環(huán)檢測的完整方案更多是作為“粗檢索之后的精細(xì)驗證器”。先用別的辦法篩出一小撮候選再用ICP做高精度配準(zhǔn)看配準(zhǔn)后的重疊度和殘差是否達(dá)標(biāo)。2.2 先算描述子再檢索給激光幀做“指紋”第二種思路借鑒了視覺SLAM里的詞袋思想不直接比較原始點(diǎn)云而是給每幀激光提取一個低維的“描述子”然后做快速檢索。對2D激光來說描述子可以基于線段特征、角點(diǎn)特征也可以做一些全局統(tǒng)計。比較有代表性的思路是全局描述子方案把一幀激光按距離和角度組織成一個二維結(jié)構(gòu)再對這個結(jié)構(gòu)做編碼。這樣每幀激光就變成一個固定長度的向量檢索的時候用哈?;蛘邩錉钏饕芸炀湍苷业揭慌螤钕嗨频暮蜻x。這個方案的檢索速度遠(yuǎn)快于逐幀ICP典型的做法是“描述子粗篩 幾何精驗證”。描述子方案的優(yōu)點(diǎn)是快缺點(diǎn)是有區(qū)分度上限。工業(yè)廠房、地下車庫、辦公區(qū)走廊這些環(huán)境的重復(fù)度很高不同的位置可能長得非常像。如果只依賴描述子很容易把“相似的但不同的地方”當(dāng)成回環(huán)。所以我一般把描述子當(dāng)作“漏斗的上層”篩出Top-N候選之后后面必須接一層嚴(yán)格的幾何一致性驗證。漏檢和誤檢在描述子方案里是一對矛盾描述子越緊湊檢索越快但區(qū)分度越低保留越多的原始幾何信息區(qū)分度高但檢索變慢。這個平衡需要在項目里針對實際場景調(diào)。2.3 柵格似然匹配把魯棒性建立在搜索結(jié)果上第三條路跟前兩條不太一樣它不找“配對”而是直接搜索“當(dāng)前幀在哪個位置最可能就是歷史那個地方”。具體做法是把歷史關(guān)鍵幀附近的激光點(diǎn)累積成一張局部柵格地圖每個柵格記錄占用概率。然后當(dāng)前幀在這個柵格地圖上做“掃描匹配”——把當(dāng)前掃描放到某個候選位姿上計算所有激光點(diǎn)落在占用柵格上的加權(quán)分?jǐn)?shù)。搜索過程遍歷一個三維空間x方向、y方向、旋轉(zhuǎn)角找分?jǐn)?shù)最高的位姿。因為計算的就是“當(dāng)前幀有多大可能是從這個位置看到的”這種基于似然的方法對噪聲和部分遮擋的魯棒性比ICP更好。這個方案的關(guān)鍵在搜索效率。三維搜索空間如果樸素的暴力遍歷計算量是天文數(shù)字。工業(yè)界的主流通用解法是兩層先做多分辨率柵格從粗分辨率開始大范圍搜索鎖定一個大致區(qū)域再用細(xì)分辨率在當(dāng)前區(qū)域里精確搜索。配合分支定界Branch and Bound策略搜索過程中用一個“分?jǐn)?shù)上界”剪掉明顯不行的分支可以把搜索時間壓縮到幾十毫秒級別。柵格似然匹配在精度和魯棒性上表現(xiàn)最好也是我最終在項目里采用的主流方案。它的代價是實現(xiàn)復(fù)雜度高需要維護(hù)歷史子地圖、多分辨率結(jié)構(gòu)以及搜索樹的剪枝邏輯。不過一旦跑通實時性和正確性都很有保障。三種方案的定位差異我整理成一張表方便選型時對照方案核心思路優(yōu)點(diǎn)缺點(diǎn)典型適用場景ICP系列直接匹配點(diǎn)云最近點(diǎn)迭代配準(zhǔn)實現(xiàn)簡單局部精度高對初始值敏感易陷局部極小值作為候選的精驗證器描述子與索引檢索提取幾何描述子建索引檢索快適合大規(guī)模相似環(huán)境區(qū)分度不足候選粗篩的第一層漏斗柵格似然搜索多分辨率似然匹配剪枝搜索魯棒性好全局性最強(qiáng)實現(xiàn)復(fù)雜內(nèi)存開銷大需要高可靠回環(huán)的項目主力方案3. 實時性的核心工程策略3.1 關(guān)鍵幀機(jī)制不是每一幀都值得做匹配回環(huán)檢測最大的實時性矛盾是前端匹配每一幀都要做但回環(huán)檢測如果每一幀都跟全部歷史做匹配計算量完全不可接受。解決思路是引入關(guān)鍵幀機(jī)制。關(guān)鍵幀是經(jīng)過挑選的、有一定“代表性”的歷史幀只有它們才會進(jìn)入回環(huán)檢測的候選池。選擇條件一般包括位移、旋轉(zhuǎn)和時間三個維度平移超過一定距離、旋轉(zhuǎn)超過一定角度、或者距離上一個關(guān)鍵幀超過一定時間就插入一個新關(guān)鍵幀。一個我常用的插入策略示例bool ShouldInsertKeyframe(const Pose2d cur, const Pose2d last) { double dt (cur.translation() - last.translation()).norm(); double dtheta std::abs(normalizeAngle(cur.rotation() - last.rotation())); if (dt 0.35 || dtheta 0.25) return true; // 0.35m 或約14° 即插入 if (timeSinceLastKeyframe 4.0) return true; return false; }閾值怎么定很有講究。閾值太大關(guān)鍵幀太稀疏回環(huán)檢測會漏掉一些“小回環(huán)”閾值太小關(guān)鍵幀密集檢索庫膨脹計算和內(nèi)存都上去了。我一般從0.3米和10°起步根據(jù)環(huán)境尺度調(diào)。在走廊多的場景位移閾值可以放寬到0.5米因為走廊里連續(xù)幀視覺變化小太密集的關(guān)鍵幀其實是在重復(fù)存儲幾乎一樣的信息。另外關(guān)鍵幀不能太靠近當(dāng)前幀。回環(huán)的意義是“到過一個很久以前的地方”如果相鄰關(guān)鍵幀之間搞出“回環(huán)”那只是一條平凡的短距離約束對全局優(yōu)化毫無幫助反而可能因為前后端重復(fù)約束導(dǎo)致矩陣病態(tài)。所以檢測時通常會加一個時間間隔下限比如距離當(dāng)前幀至少20個關(guān)鍵幀以上才允許被判定為回環(huán)。3.2 匹配加速三板斧降采樣、多分辨率、分支定界光有稀疏關(guān)鍵幀還不夠單次匹配本身的耗時也得壓下來。我用得最多的三招是降采樣、多分辨率、分支定界。第一招是點(diǎn)云降采樣。2D激光一幀通常有幾百到上千個點(diǎn)但很多點(diǎn)是冗余的。用一個體素濾波器把空間范圍內(nèi)相近的點(diǎn)合并成一個代表點(diǎn)每幀保留兩三成點(diǎn)就足夠做回環(huán)判定。把減法做在數(shù)據(jù)源頭后面所有計算的負(fù)載都線性下降。注意降采樣粒度要與柵格分辨率匹配比如柵格分辨率5厘米降采樣網(wǎng)格設(shè)2到3厘米效果比較合理太粗會丟細(xì)節(jié)太細(xì)起不到降載作用。第二招是多分辨率柵格。搜索時先構(gòu)建一個粗柵格比如20厘米分辨率把整個歷史子地圖的占用信息壓成一張小尺寸圖當(dāng)前幀算分?jǐn)?shù)時只需要做少量查詢。粗搜索鎖定一個或幾個高分區(qū)后再切到5厘米的細(xì)柵格在局部區(qū)域精確搜索。粗搜索本身耗時小細(xì)搜索只在小范圍內(nèi)做總耗時降到單級搜索的十分之一以下。第三招是分支定界。搜索空間本質(zhì)上是一棵巨大的樹每個節(jié)點(diǎn)代表一個位姿子區(qū)間。想象一下根節(jié)點(diǎn)覆蓋全圖它的子節(jié)點(diǎn)把全圖分成四塊每塊的分?jǐn)?shù)上界可以由粗柵格快速預(yù)估。如果某一塊的上界分?jǐn)?shù)都比當(dāng)前最優(yōu)解還低那這一整棵子樹都可以直接放棄不用進(jìn)去細(xì)搜。這是教科書級別的剪枝思路實現(xiàn)的時候核心要點(diǎn)是“上界必須是對真實分?jǐn)?shù)的上界”——寧高勿低否則會錯誤剪掉真正的最優(yōu)解。我調(diào)這塊時吃過虧上界算得過于樂觀導(dǎo)致回環(huán)明明存在卻被剪掉了排查了很久才發(fā)現(xiàn)是剪枝條件寫反了。3.3 與后端的配合約束怎么加、優(yōu)化怎么觸發(fā)回環(huán)檢測出一個候選之后不能直接閉著眼睛把約束丟給后端。從我踩坑的經(jīng)驗看插入約束前至少要經(jīng)過兩道驗證一是精細(xì)配準(zhǔn)的得分要超過硬閾值二是配準(zhǔn)結(jié)果要在后續(xù)連續(xù)幾幀里保持穩(wěn)定。連續(xù)幾幀都能以相近的位姿關(guān)系再次匹配同一個歷史關(guān)鍵幀這才算一個“可信回環(huán)”。后端收到回環(huán)約束后位姿圖優(yōu)化需要做兩件事一是把新約束加進(jìn)圖里二是觸發(fā)一次優(yōu)化。優(yōu)化時機(jī)分兩種局部優(yōu)化和全局優(yōu)化。局部優(yōu)化只調(diào)整回環(huán)附近的一小段軌跡代價低適合回環(huán)剛發(fā)現(xiàn)時快速吸收一部分誤差全局優(yōu)化則把所有節(jié)點(diǎn)一起調(diào)整代價高但能把誤差在整個軌跡上均勻分?jǐn)?。常用的策略是發(fā)現(xiàn)回環(huán)時先做局部優(yōu)化確認(rèn)沒問題再在后臺以較低頻率比如每幾個關(guān)鍵幀一次觸發(fā)全局優(yōu)化。全局優(yōu)化如果每幀都跑CPU肯定吃不消而且小幅度漂移根本不需要頻繁全局重調(diào)。約束在優(yōu)化里的權(quán)重也很關(guān)鍵。每次約束除了相對位姿還要帶一個信息矩陣代表這條約束的可信程度。前端順序約束的信息矩陣來自匹配的協(xié)方差回環(huán)約束的權(quán)重可以給得高一些因為長程約束更能“壓住”累積漂移。但權(quán)重也不能無腦調(diào)高一旦這條回環(huán)本身是錯的高權(quán)重會直接把整張圖撕裂后續(xù)所有優(yōu)化都會跟著混亂掉。這也是為什么魯棒核函數(shù)在回環(huán)約束里幾乎是標(biāo)配——它能讓誤差特別大的約束在優(yōu)化時自動降權(quán)而不是硬生生地拉壞全局。4. 常見問題與排查技巧實錄4.1 誤回環(huán)環(huán)境太像認(rèn)錯了地方這是回環(huán)檢測里最危險的問題危險在于它不報錯。系統(tǒng)會“信心滿滿”地把兩個本來不相干的位置拼成同一個結(jié)果地圖出現(xiàn)折疊或者結(jié)構(gòu)化錯亂。最容易觸發(fā)誤回環(huán)的環(huán)境是高度重復(fù)的結(jié)構(gòu)比如標(biāo)準(zhǔn)辦公層的同款走廊、地下車庫同尺寸的柱網(wǎng)、倉庫里間距幾乎一致的貨架。這些環(huán)境里不同位置的激光幀本來就高度相似無論用描述子還是ICP分?jǐn)?shù)都可能很接近真回環(huán)。我的排查思路是給回環(huán)檢測加三道閘。第一道是配準(zhǔn)后的幾何驗證計算兩幀在最佳對齊下的點(diǎn)云重疊率和平均殘差重疊率太低直接拒絕。第二道是時序驗證要求同一回環(huán)在后續(xù)若干幀中反復(fù)出現(xiàn)且位姿變化趨勢一致單幀偶然的高分匹配不能算數(shù)。第三道是優(yōu)化后的殘差檢驗把新增回環(huán)約束放進(jìn)局部優(yōu)化如果這個約束的殘差遠(yuǎn)大于其他約束的平均殘差就把它剔除。三道閘下來誤回環(huán)被攔住的比例很高。4.2 漏檢與高延遲明明走過卻認(rèn)不出來漏檢的典型表現(xiàn)是機(jī)器人繞了一圈回到起點(diǎn)附近地圖肉眼可見地能對上但回環(huán)檢測就是沒觸發(fā)或者很晚才觸發(fā)。先查候選池。如果關(guān)鍵幀插入太稀疏或者描述子索引只返回了Top-5、Top-10的候選而真正的回環(huán)幀剛好排在第十一那就會漏。解決辦法是適當(dāng)調(diào)高候選數(shù)量并增加一個基于位置距離預(yù)篩的步驟先用當(dāng)前幀估計位置附近的子圖做一次粗匹配把得分夠高的都留下再去做精細(xì)驗證。這樣即使描述子排名不靠前只要幾何上能對上就不會被漏掉。再查環(huán)境變化因素。動態(tài)障礙物、掃描角度差異、機(jī)器人經(jīng)過同一地點(diǎn)時的朝向不同都會讓兩幀之間差異變大。一個很實用的改進(jìn)是在建立回環(huán)匹配時不要用單幀而是用“子圖”——把當(dāng)前幀最近的若干關(guān)鍵幀累積成一小塊局部地圖再拿去和歷史子圖匹配。子圖對噪聲和單幀差異的容忍度遠(yuǎn)高于單幀匹配這個改動對漏檢率的改善立竿見影。4.3 回環(huán)加了反而地圖崩了比不觸發(fā)回環(huán)更糟的是回環(huán)觸發(fā)之后后端一優(yōu)化原本還能看的地圖反而崩了。這種情況我遇到過多次問題幾乎都出在“回環(huán)約束本身質(zhì)量不行但被當(dāng)作高權(quán)重約束用”。一種原因是檢測階段把分?jǐn)?shù)閾值調(diào)太低放了低質(zhì)量回環(huán)進(jìn)來另一種原因是信息矩陣給得不對回環(huán)約束的協(xié)方差被低估優(yōu)化的結(jié)果被一條錯誤約束強(qiáng)行拉偏。排查時可以先在可視化里把優(yōu)化后的回環(huán)約束殘差標(biāo)出來殘差顏色最刺眼的幾條基本就是“害群之馬”。治本的辦法是前面提到的三道閘治標(biāo)的辦法是在后端里給回環(huán)約束掛魯棒核函數(shù)。核函數(shù)能讓殘差超過一定界限的約束在迭代里被自動降權(quán)即使偶爾混進(jìn)來一條壞約束也不會一次性毀掉全局。我個人建議只要加了回環(huán)就老老實實把魯棒核掛上看似多了一層保護(hù)實際救過我好幾次。4.4 實時性不達(dá)標(biāo)的排查路徑回環(huán)檢測跑得慢問題可能出現(xiàn)在任意一個環(huán)節(jié)所以第一步永遠(yuǎn)是量化和定位耗時而不是猜。做法是在每個模塊入口和出口打時間戳關(guān)鍵幀插入耗時、候選檢索耗時、精細(xì)配準(zhǔn)耗時、約束添加耗時、后端優(yōu)化耗時分別統(tǒng)計均值和最大值。耗時會跳變最大值往往比均值更有參考價值。定位到具體環(huán)節(jié)后整改優(yōu)先級我一般這樣排先查是否在回環(huán)檢測線程里做了全量歷史匹配如果是改成增量式——只有新的關(guān)鍵幀才去匹配歷史舊關(guān)鍵幀之間不需要重復(fù)算再查候選檢索階段是否遍歷了過多歷史描述子考慮給描述子建索引而不是線性掃描最后查精細(xì)配準(zhǔn)的迭代次數(shù)上限是否設(shè)置合理有些ICP在分?jǐn)?shù)已經(jīng)不變化的情況下還傻傻迭代幾十次白白浪費(fèi)算力。還有一個容易被忽略的點(diǎn)是多線程下的資源競爭。前端、回環(huán)檢測、后端優(yōu)化如果共享同一份地圖數(shù)據(jù)鎖競爭就會讓實際耗時遠(yuǎn)高于純計算耗時。我的做法是給地圖分版本回環(huán)檢測線程讀的是上一版本的只讀快照不需要跟前端搶鎖。這樣改造之后幀率抖動問題基本消失。最后分享幾個小經(jīng)驗參數(shù)調(diào)完一輪之后我習(xí)慣在筆記本上固定一組“基準(zhǔn)環(huán)境驗證清單”先在對稱走廊環(huán)境測誤回環(huán)率再在大回環(huán)場地測檢出時間最后在動態(tài)人群環(huán)境測穩(wěn)定性。每次改了任何回環(huán)相關(guān)代碼都重跑這三項避免“修好A場景又搞壞B場景”。調(diào)試回環(huán)檢測最值回票價的一件事是給系統(tǒng)加一個“回環(huán)可視化開關(guān)”把所有候選匹配、接受/拒絕的決策過程和分?jǐn)?shù)實時畫出來。很多問題在數(shù)字表格里看不出來畫成圖一眼就能看出來是候選范圍太小、還是匹配分?jǐn)?shù)閾值過高。我后來把這個開關(guān)默認(rèn)打開因為它幫我把排查誤回環(huán)的時間縮短了一半以上。如果你正在做2D激光SLAM的回環(huán)檢測我的建議很直接先用多分辨率柵格分支定界建一個可靠的主方案再在上面疊描述子索引做粗篩提速最后用幾何驗證和魯棒核函數(shù)守住質(zhì)量底線。這套組合實測下來最穩(wěn)既扛得住復(fù)雜環(huán)境又留得住實時性。等把這套跑通你會發(fā)現(xiàn)地圖長期一致性這個問題是真的能被解決掉的。