化的SLAM實戰(zhàn))
1. 從“看懂”到“改懂”為什么我們需要深入R3LIVE的代碼細節(jié)如果你已經(jīng)跟著教程跑通了R3LIVE的Demo看著點云地圖一點點構建出來感覺挺酷那恭喜你入門了。但接下來你可能會遇到一堆問題為什么我的設備跑起來幀率這么低這個參數(shù)調(diào)了好像沒用我想加個新的傳感器該怎么接想自己改點東西從哪下手這些問題官方文檔和論文往往不會告訴你答案都藏在代碼里?!按a詳解”系列目的就是幫你跨過“能用”到“能改”這道坎。上一篇我們搭建了環(huán)境理清了數(shù)據(jù)流的主干。這一篇我們要真正扎進核心的算法模塊里去看看那些炫酷的實時建圖與定位功能到底是怎么一行行代碼實現(xiàn)的。我會帶你像讀一個復雜工程的源碼一樣不是浮光掠影而是抓住幾個最關鍵、最常被問到的“牛鼻子”圖像數(shù)據(jù)的處理流水線、激光雷達點云的畸變補償與特征提取以及最核心的緊耦合優(yōu)化器。我會用大量的代碼片段和流程圖文字描述來解釋并分享我在調(diào)試和修改這些模塊時踩過的坑和總結的經(jīng)驗。2. 視覺前端從RAW圖到光流追蹤的完整流水線R3LIVE的視覺部分承擔著提供高頻姿態(tài)更新和深度估計的重任。它的輸入是原始圖像輸出則是用于后續(xù)優(yōu)化的視覺特征點。這個過程遠比調(diào)用一個cv::calcOpticalFlowPyrLK函數(shù)復雜。2.1 圖像預處理與金字塔構建原始圖像進來第一步絕不是直接丟給光流。在R3LIVE的image_process函數(shù)中通常位于某個visual_processing.cpp或類似文件中你會發(fā)現(xiàn)它做了以下幾件事去畸變根據(jù)事先標定好的相機內(nèi)參和畸變系數(shù)對圖像進行矯正。這里用的是OpenCV的cv::undistort函數(shù)。一個常見的坑是標定參數(shù)的單位和格式必須與代碼中的cv::Mat矩陣完全匹配否則去畸變后圖像會變得很奇怪。我曾經(jīng)因為標定文件里k1 k2 p1 p2 k3的順序和代碼里讀取的順序不一致導致后續(xù)光流追蹤完全失敗查了半天才定位到這里。// 偽代碼示意 cv::Mat intrinsic_mat (cv::Mat_double(3 3) fx 0 cx 0 fy cy 0 0 1); cv::Mat dist_coeffs (cv::Mat_double(1 5) k1 k2 p1 p2 k3); cv::undistort(raw_image undistorted_image intrinsic_mat dist_coeffs);圖像金字塔生成光流追蹤需要在不同尺度上尋找匹配點以提高對大運動的魯棒性和計算效率。R3LIVE會構建一個高斯金字塔比如4層。底層是原圖或下采樣后的基礎層上層是逐步降采樣的模糊圖像。這里的關鍵參數(shù)是金字塔層數(shù)(pyramid_levels)和每層的縮放系數(shù)(scale_factor通常為0.5)。層數(shù)越多能處理的幀間位移越大但計算量也越大。對于車載或無人機高速場景我通常會把層數(shù)設為4甚至5對于室內(nèi)機器人低速場景3層就夠了可以節(jié)省計算資源。2.2 光流追蹤的核心策略與關鍵代碼預處理后的圖像連同上一幀追蹤到的特征點被送入光流模塊。R3LIVE通常采用LK光流法。但直接調(diào)用OpenCV函數(shù)是不夠的需要一套策略來管理特征點的“生死”。在代碼中你會找到一個FeatureTracker類或類似的結構體。它的trackImage函數(shù)是核心。我拆解一下它的典型步驟前向光流用上一幀的特征點prev_pts在當前幀金字塔上追蹤得到curr_pts。反向光流校驗這是一個非常有效的去外點手段。再用當前幀追蹤到的點curr_pts反向到上一幀金字塔進行追蹤得到back_pts。然后計算prev_pts和back_pts的距離如果大于某個閾值比如0.5個像素則認為這個追蹤不可靠剔除。F矩陣校驗對于前后幀的點對計算基礎矩陣F利用RANSAC剔除不符合極線約束的匹配外點。這一步能過濾掉由于遮擋、重復紋理等造成的錯誤匹配。均勻化與補點經(jīng)過剔除特征點可能變得稀疏或不均勻。代碼里會有一個rejectWithF和uniformDistribution函數(shù)。它會將圖像劃分成網(wǎng)格如10x10的格子確保每個格子里有一定數(shù)量的特征點。如果某個格子點太少就會在這個格子內(nèi)用cv::goodFeaturesToTrack檢測新的角點進行補充。這里有一個重要的實操細節(jié)特征點ID的管理。每個被追蹤的特征點都有一個唯一ID。在補點時新點會被賦予新的ID追蹤成功的點保留原有ID。這個ID會一直傳遞到后端優(yōu)化用于構建重投影誤差。如果ID管理混亂后端的數(shù)據(jù)關聯(lián)就會出錯。在閱讀代碼時務必理清ids這個向量是如何隨著光流追蹤而更新和傳遞的。2.3 視覺深度估計與點云生成追蹤到的2D像素點需要轉(zhuǎn)換成3D空間點才能用于建圖。這就是視覺深度估計模塊的工作。在R3LIVE中深度主要來源于兩種方式激光雷達深度關聯(lián)這是R3LIVE的核心創(chuàng)新之一。對于當前幀的每個視覺特征點代碼會去查找同一時刻或時間戳對齊后的激光雷達點云。利用當前的姿態(tài)估計來自IMU預測或上一輪優(yōu)化將視覺點反向投影到3D空間然后在投影線附近的一定半徑內(nèi)搜索最近鄰的激光點。如果找到則該激光點的深度就作為這個視覺特征點的深度。這個過程在代碼中體現(xiàn)為一個searchPointCloudForDepth的函數(shù)或循環(huán)。注意這里的“半徑”參數(shù)非常關鍵。設大了容易關聯(lián)到錯誤的、非對應的物體上的點引入噪聲設小了很多點會因找不到對應深度而被丟棄導致特征利用率低。我的經(jīng)驗是這個半徑需要根據(jù)激光雷達的精度和點云密度來調(diào)整。對于16線雷達在5-10米內(nèi)0.1-0.2米是個不錯的起點對于固態(tài)激光雷達或高線數(shù)雷達可以更小一些。三角化對于無法從激光雷達獲得深度的點比如遠處或雷達盲區(qū)如果這個特征點被連續(xù)多幀追蹤到則可以通過多視圖幾何三角化的方式估計其深度。代碼中會調(diào)用cv::triangulatePoints函數(shù)。經(jīng)過深度估計后具有有效深度的視覺特征點就被轉(zhuǎn)換為3D點并和激光點云一起送入后續(xù)的融合建圖模塊。一個常見的性能瓶頸就在這里對每一個視覺點進行最近鄰搜索尤其是暴力搜索是非常耗時的。在代碼優(yōu)化時可以考慮對當前幀的激光點云建立KD-Tree能大幅加速搜索過程。3. 激光雷達前端點云去畸變與特征提取激光雷達在旋轉(zhuǎn)掃描過程中載體本身也在運動這會導致點云發(fā)生“運動畸變”。R3LIVE作為緊耦合系統(tǒng)必須首先糾正這個畸變?nèi)缓筇崛∮杏玫奶卣鳌?.1 運動畸變補償原理與插值策略假設一幀激光點云覆蓋了100ms的時間在這100ms里機器人的位置和姿態(tài)發(fā)生了變化。掃描開始時打到的點和掃描結束時打到的點其對應的機器人位姿是不同的?;冄a償?shù)哪康木褪菍⑦@一幀內(nèi)所有的點都統(tǒng)一補償?shù)酵粋€時刻通常是這幀點云的結束時間戳或稱為scan_end_time。在代碼中這個功能通常由一個LidarProcessor類的distortionCorrection函數(shù)完成。它的輸入是原始點云和一系列IMU數(shù)據(jù)提供了高頻的姿態(tài)變化。其核心步驟如下時間對齊為點云中的每個點point_i計算其相對于本幀結束時間scan_end_time的時間差delta_t_i。這個時間差通常由雷達驅(qū)動提供如timestamp字段或者是根據(jù)點的掃描角度和雷達轉(zhuǎn)速推算的。位姿插值我們需要知道在scan_end_time - delta_t_i這個時刻機器人的位姿是什么。由于IMU頻率高如200Hz我們可以利用IMU預積分或直接插值的方式獲取。R3LIVE通常維護一個姿態(tài)緩沖區(qū)state_buffer里面按時間戳存儲了歷史時刻的機器人狀態(tài)位置、姿態(tài)、速度等。通過查找這個緩沖區(qū)對兩個最近時刻的狀態(tài)進行線性插值或球面線性插值對于旋轉(zhuǎn)得到每個點對應時刻的位姿T_i。坐標變換設scan_end_time時刻的位姿為T_end。那么將點point_i從T_i坐標系變換到T_end坐標系的公式為point_i_corrected T_end.inverse() * T_i * point_i。這樣所有點就被校正到了同一時刻的坐標系下。這里有一個極易出錯的細節(jié)姿態(tài)的存儲和插值方式。如果代碼中使用的是四元數(shù)表示旋轉(zhuǎn)插值時必須使用球面線性插值slerp而不是對四元數(shù)的四個分量進行線性插值后者會破壞四元數(shù)的單位約束導致結果錯誤。檢查代碼中是否有Eigen::Quaterniond::slerp或類似函數(shù)的調(diào)用。3.2 特征提取平面點與邊緣點的判別去畸變后的點云接下來要被分類為“平面點”和“邊緣點”。這兩類特征在后端優(yōu)化中分別用于構建點到面的距離誤差和點到線的距離誤差。特征提取的算法通常借鑒LOAM系列。在代碼里你會找到一個extractFeatures函數(shù)。它的大致邏輯是計算曲率對于點云中的每個點i取其前后各5個點在同一掃描線上計算這些鄰近點到點i擬合的直線的距離或到擬合的平面的距離這個距離的某種統(tǒng)計量如方差被定義為曲率。曲率大的點意味著其局部幾何變化劇烈可能是邊緣曲率小的點局部平坦可能是平面。分類篩選對每一條掃描線ring上的點根據(jù)曲率排序。選取曲率最大的幾個點作為候選邊緣點曲率最小的幾個點作為候選平面點。同時要避免特征點聚集所以會施加一個距離閾值確保選出的特征點在空間上是分散的。遮擋判斷這是一個非常重要的邏輯。如下圖所示如果點B被選中但點A和點C與點B的夾角很小且距離激光雷達更近那么點B可能是一個被遮擋的邊緣比如墻的側(cè)面這種點不穩(wěn)定應當剔除。代碼中會計算點B與其前后點的向量夾角和距離比來進行判斷。激光雷達 / \ / \ A C \ / \ / B (可能被遮擋的邊緣點)調(diào)參經(jīng)驗特征提取環(huán)節(jié)有幾個關鍵參數(shù)curvature_threshold曲率閾值直接影響特征點的數(shù)量。閾值設低特征點多優(yōu)化約束強但可能引入噪聲閾值設高特征點少可能約束不足。需要根據(jù)場景調(diào)整。neighbor_count計算曲率時考慮的鄰近點數(shù)。通常5-10是個合理范圍。selected_feature_num_per_scan每條掃描線選取的特征點數(shù)量。這需要平衡計算量和精度。對于16線雷達每條線選2-4個邊緣點和4-6個平面點可能是個起點。提取出的特征點會被存儲起來并和視覺特征點一起等待后端優(yōu)化器的“召喚”。4. 緊耦合后端優(yōu)化誤差構建與求解的魔鬼細節(jié)這是R3LIVE最核心、也是最復雜的部分。它接收來自視覺前端和激光前端預處理好的特征數(shù)據(jù)以及IMU的預積分量構建一個統(tǒng)一的非線性最小二乘問題然后求解出最優(yōu)的機器人狀態(tài)。4.1 狀態(tài)向量與IMU預積分約束優(yōu)化器如Estimator類維護著一個滑窗內(nèi)的狀態(tài)向量。狀態(tài)x通常包括位置p(3維)姿態(tài)四元數(shù)q或旋轉(zhuǎn)矩陣R(4維或3維SO3)速度v(3維)陀螺儀零偏b_g(3維)加速度計零偏b_a(3維)可能還有視覺特征的逆深度λ。IMU預積分約束連接了滑窗中相鄰兩個關鍵幀i和j之間的狀態(tài)。它提供了相對位置、速度和旋轉(zhuǎn)的變化量Δp Δv Δq這些變化量只與i和j時刻之間的IMU測量以及零偏有關與全局狀態(tài)無關。在優(yōu)化中IMU約束構建的誤差項是e_imu [Δp - pred_Δp Δv - pred_Δv 2*[q_i^{-1} ? q_j ? pred_Δq^{-1}]_xyz]其中pred_Δp pred_Δv pred_Δq是由IMU預積分理論計算出的預測值。這個誤差項將i和j時刻的狀態(tài)緊密耦合在一起。代碼閱讀重點找到IMU預積分類如IntegrationBase。關注它如何接收IMU數(shù)據(jù)、進行中值積分或歐拉積分、更新預積分量delta_pdelta_vdelta_q和協(xié)方差矩陣。更重要的是找到它如何提供對零偏b_ab_g的雅可比矩陣因為優(yōu)化過程中需要這些雅可比來更新預積分量當零偏估計發(fā)生變化時。4.2 激光雷達約束點到面與點到線對于提取的一個激光平面點p_l我們在歷史地圖中尋找其對應的平面。具體做法是將p_l根據(jù)當前估計的位姿T變換到世界坐標系然后在世界坐標系下的全局地圖KD-Tree中尋找p_l最近的5個點用這5個點擬合一個平面π (n d)其中n是單位法向量d是原點到平面的距離。那么點到面的距離誤差就是e_lidar_plane n^T * (T * p_l) d這個誤差的物理意義是當前觀測到的點p_l應該落在它對應的歷史地圖平面π上。同理對于一個激光邊緣點我們在歷史地圖中尋找最近的5個點用主成分分析PCA判斷這5個點是否構成一條線第二大特征值遠大于第三大。如果是則用這5個點擬合一條直線。點到線的距離誤差就是點到這條直線距離的平方。在代碼中這部分體現(xiàn)在LidarMapFactor或LidarPlaneNormFactor這樣的自定義因子類中。你需要關注它如何重載Evaluate函數(shù)來計算誤差和雅可比矩陣。雅可比的計算是重中之重它決定了優(yōu)化收斂的速度和正確性。雅可比矩陣描述了誤差e相對于狀態(tài)x如位姿T的變化率即de/dx。通常需要用到李代數(shù)擾動模型來推導。4.3 視覺約束重投影誤差對于一個具有深度λ或3D坐標P_w的視覺特征點其誤差構建更為直觀。假設這個點在第i幀圖像中被觀測到像素坐標為(u_i v_i)。將這個3D點P_w用當前估計的第i幀相機位姿T_cam_i變換到相機坐標系P_c T_cam_i * P_w。將P_c投影到歸一化平面p_n (P_c.x / P_c.z P_c.y / P_c.z)。再經(jīng)過相機內(nèi)參矩陣K投影到像素平面得到預測的像素坐標(u_pred v_pred)。重投影誤差就是觀測值與預測值的差e_vision [u_i - u_pred v_i - v_pred]^T。在代碼中這對應著ReprojectionFactor或ProjectionFactor類。同樣需要關注其Evaluate函數(shù)中誤差和雅可比的計算。視覺誤差的雅可比涉及到對3D點坐標的求導如果狀態(tài)中包含逆深度和對相機位姿的求導。4.4 優(yōu)化求解與邊緣化所有的誤差項IMU、激光、視覺構建好后就形成了一個大規(guī)模的非線性最小二乘問題min Σ ||e_i||^2。R3LIVE通常使用Ceres Solver或g2o來求解。求解前需要設置參數(shù)塊狀態(tài)變量和殘差塊誤差項。一個關鍵技巧是參數(shù)塊的參數(shù)化。對于旋轉(zhuǎn)四元數(shù)必須使用EigenQuaternionParameterization或Ceres的QuaternionParameterization以確保優(yōu)化過程中四元數(shù)保持單位長度。另一個核心概念是邊緣化。由于滑窗大小固定當新的關鍵幀到來時舊的關鍵幀需要被移出窗口。但不能直接扔掉因為舊幀攜帶的信息尤其是IMU預積分約束著滑窗內(nèi)的狀態(tài)。直接扔掉會導致約束丟失系統(tǒng)變得不客觀。邊緣化就是將舊幀的狀態(tài)從優(yōu)化變量中消除但將其攜帶的約束信息轉(zhuǎn)化為一個先驗約束作為一個新的誤差項加到后續(xù)的優(yōu)化問題中。這個過程在代碼中體現(xiàn)為維護一個MarginalizationInfo對象它會構造一個舒爾補將待邊緣化的狀態(tài)消元并更新剩余狀態(tài)的Hessian矩陣和b向量。調(diào)試心得優(yōu)化部分出問題最難查。我的方法是打印誤差值在每次迭代開始或結束時打印出各類誤差e_imue_lidare_vision的范數(shù)。正常情況下它們應該隨著迭代逐漸減小。如果某一項誤差突然變得巨大說明對應的數(shù)據(jù)關聯(lián)或雅可比計算可能出錯了。檢查雅可比Ceres Solver提供了數(shù)值求導選項。對于自定義因子可以先使用數(shù)值求導NumericDiffCostFunction來驗證你解析推導的雅可比是否正確。如果兩者結果一致再用解析求導以提升效率??梢暬虚g結果將優(yōu)化前的激光匹配關系、視覺重投影關系用RVIZ或Pangolin畫出來。肉眼觀察匹配是否正確能快速定位是前端特征關聯(lián)的問題還是后端優(yōu)化本身的問題。5. 地圖管理體素濾波與增量更新優(yōu)化得到的新幀位姿后需要將當前幀處理好的點云包括激光特征點和帶深度的視覺點融合到全局地圖中。R3LIVE通常使用體素網(wǎng)格Voxel Grid來管理地圖以防止地圖無限膨脹并保持實時性。5.1 體素濾波融合代碼中會有一個VoxelMap或PointCloudMap類。其addPoints函數(shù)負責融合新點云。流程如下坐標變換將當前幀點云根據(jù)優(yōu)化后的位姿T變換到世界坐標系。體素化為世界坐標系下的每個點計算其所屬的體素網(wǎng)格的索引。例如體素分辨率設為0.1米那么點(1.05 2.13 0.08)就屬于體素(10 21 0)每個坐標除以分辨率后取整。點云融合對于同一個體素內(nèi)的所有點包括新來的點和地圖中已有的點不是簡單地將新點添加進去而是進行降采樣或取平均。常見策略有體素中心化用該體素內(nèi)所有點的坐標平均值作為這個體素的唯一代表點。隨機采樣在該體素內(nèi)隨機保留一個點。最近點保留保留離體素中心最近的點。第一種方法取平均能有效平滑噪聲是更常用的方法。在代碼中這通常通過一個std::unordered_map來實現(xiàn)鍵是體素索引值是一個結構體存儲該體素內(nèi)點的累加和、數(shù)量以便快速計算平均值。5.2 地圖的保存與加載一個實用的SLAM系統(tǒng)必須支持地圖的保存與重載。R3LIVE的地圖保存通常很簡單將全局地圖點云經(jīng)過體素濾波后的寫入一個.pcd或.ply文件。同時可能需要保存一些關鍵幀的位姿軌跡TUM格式或KITTI格式。更高級的功能是增量保存與加載。這意味著系統(tǒng)在運行時可以定期將新增的地圖點云追加到磁盤文件而不是每次都保存整個地圖。加載時可以快速讀入已有的地圖點云并重建KD-Tree。在相關代碼文件中你會看到saveMap和loadMap函數(shù)里面主要是pcl::io::savePCDFile和pcl::io::loadPCDFile的調(diào)用。一個性能陷阱頻繁地將點云添加到全局地圖并立即重建KD-Tree用于后續(xù)的激光匹配是非常耗時的。常見的優(yōu)化是設置一個緩沖機制累積一定數(shù)量的點比如一幀或若干幀后再一次性添加到地圖并重建KD-Tree。在代碼中可能會有一個map_cloud_buff點云緩沖區(qū)和相關的計數(shù)器。6. 實戰(zhàn)調(diào)試從理論到跑通的最后一步讀懂了代碼最終還是要落到修改和調(diào)試上?;谖叶啻我浦埠驼{(diào)試R3LIVE及其衍生項目的經(jīng)驗分享幾個最具實操性的建議。6.1 如何高效地修改與驗證代碼不要一上來就改核心算法。遵循以下步驟先理解數(shù)據(jù)流在關鍵函數(shù)的入口和出口添加打印語句或ROS_INFO輸出數(shù)據(jù)的尺寸、時間戳、前幾個數(shù)值。確保數(shù)據(jù)如你預期的那樣流動。例如在featureTracker.trackImage()前后打印追蹤到的特征點數(shù)量在lidarProcessor.distortionCorrection()前后打印點云大小。模塊化測試如果可能將視覺前端、激光前端等模塊單獨剝離出來編寫簡單的測試程序輸入模擬數(shù)據(jù)或錄制好的ROS Bag看輸出是否符合預期。這能幫你快速定位問題是出在模塊內(nèi)部還是模塊間的接口上。善用調(diào)試工具GDB對于運行時崩潰段錯誤GDB是定位神器。編譯時加上-g選項運行程序崩潰后用bt命令查看調(diào)用棧。Valgrind檢查內(nèi)存泄漏。SLAM系統(tǒng)長時間運行微小的內(nèi)存泄漏也會導致崩潰。用valgrind --leak-checkfull來運行你的程序。ROS工具rqt_graph查看節(jié)點連接rqt_console查看日志rosbag play -r 0.5慢速播放數(shù)據(jù)包以便觀察。6.2 參數(shù)調(diào)優(yōu)一個系統(tǒng)性的思路參數(shù)眾多不要盲目亂調(diào)。建立一個系統(tǒng)性的調(diào)優(yōu)順序運動畸變補償相關首先確保這部分正確。如果畸變補償沒做好特征提取和后續(xù)匹配全是錯的。檢查IMU和激光雷達的時間戳同步是否精確到毫秒級??梢試L試讓系統(tǒng)靜止不動然后查看去畸變后的點云是否還是一個“干凈”的靜止場景點云如果仍有拖影說明補償有問題。特征提取參數(shù)在靜止和低速場景下調(diào)整曲率閾值、每線提取特征數(shù)等確保提取的特征點既不過于稀疏導致約束不足也不過于密集引入噪聲且增加計算量??梢酝ㄟ^RVIZ將提取的邊緣點紅色和平面點綠色可視化出來直觀判斷。匹配搜索參數(shù)主要是激光匹配時的搜索半徑kdtree_search_radius和視覺深度關聯(lián)時的搜索半徑。從小值開始慢慢調(diào)大觀察地圖質(zhì)量是否出現(xiàn)很多漂浮的離群點和跟蹤穩(wěn)定性。優(yōu)化器參數(shù)Ceres Solver的迭代次數(shù)、線性求解器類型SPARSE_NORMAL_CHOLESKY常用、信任域策略的初始半徑等。對于大多數(shù)問題保持默認值通??梢怨ぷ鳌H绻麅?yōu)化不收斂首先應懷疑是前端數(shù)據(jù)關聯(lián)或雅可比計算錯誤而不是盲目調(diào)整優(yōu)化器參數(shù)。6.3 遇到“跑著跑著就飛了”怎么辦這是SLAM調(diào)試中最常見也最頭疼的問題。提供一個排查清單檢查時間戳這是首要嫌疑犯。確保相機、激光雷達、IMU三者的時間戳在同一個時間源最好是硬件同步下并且在代碼中正確對齊。打印關鍵回調(diào)函數(shù)收到消息的時間戳看差值是否穩(wěn)定。檢查傳感器外參T_lidar_to_imu和T_cam_to_imu這兩個變換矩陣標定不準是致命的??梢試L試讓設備做純旋轉(zhuǎn)運動如果軌跡在旋轉(zhuǎn)中心嚴重漂移很可能是外參有問題。檢查特征關聯(lián)增加可視化。將當前幀的激光特征點變換到世界坐標系和歷史地圖中為其找到的對應平面/線同時顯示出來。肉眼觀察匹配是否正確。大量錯誤的匹配會導致優(yōu)化器“被帶偏”。檢查IMU預積分在靜止初始化階段IMU預積分應該能較好地估計出重力和零偏。如果初始化后重力方向估計錯誤導致地圖傾斜或者零偏估計值異常大可能是IMU數(shù)據(jù)有問題或預積分代碼有bug。逐步簡化問題如果問題復雜嘗試先關閉視覺只運行激光SLAM部分類似LIO。如果激光部分穩(wěn)定再加入視覺?;蛘咴诤唵?、紋理豐富的環(huán)境中先測試排除環(huán)境因素。閱讀R3LIVE這類大型開源項目的代碼就像在解一個精密的機械手表。每個齒輪模塊都必須嚴絲合縫地配合。本篇詳解的幾個核心模塊是保證這塊“手表”走時準確的關鍵齒輪。希望這些帶著代碼細節(jié)和實踐經(jīng)驗的拆解能讓你在動手修改和調(diào)試時不再茫然而是能有條不紊地定位問題甚至實現(xiàn)自己的創(chuàng)新想法。代碼的世界里沒有黑魔法每一行背后都是確定的數(shù)學和邏輯理解它們你就能真正掌控它。