
点云、SLAM、Nav2这三个词拆开看分别属于感知、建图与导航三个方向可实际上在扫地机器人这类室内轮式机器人上它们是一条完整数据流的不同阶段传感器先吐出一堆点云SLAM榨干这些点云建出地图并实时定位Nav2再拿这张图去做代价地图、路径规划和避障。最近给一台扫地机器人做了完整的SLAM与Nav2导航方案从RealSense D435的点云获取、SLAM Toolbox调参到Nav2行为树和3D雷达接入整个链路走了一遍踩了不少坑今天把过程整理出来。这篇内容适合正在做ROS2扫地机、服务机器人、AGV的开发者也适合刚把《视觉SLAM十四讲》啃完、想从理论知识落到实际整机的人。我会尽量把从点云到地图的转换逻辑、SLAM调参背后的判断依据、Nav2行为树的组织方式以及常见的故障排查方法讲清楚。全程不会绕弯子基本都是实测下来的结论。1. 全链路拆解一台扫地机器的数据流长什么样1.1 为什么是“点云→地图→导航”这条链路很多人把点云、SLAM、Nav2当成三个独立的方向去搜资料其实它们就是同一串数据流的不同阶段。扫地机器人的核心任务只有两个知道自己在哪知道怎么走。前者靠SLAM完成后者靠Nav2完成而点云是所有空间信息的源头。一台典型的扫地机器人传感器可能是一个2D激光雷达也可能是一颗RealSense D435这样的结构光相机或者是一颗3D激光雷达。无论哪种原始输出本质上都是一堆带坐标的三维点这就是点云。SLAM负责把这些点按照时间顺序拼接起来同时估计机器人自身的位姿最终输出一张可供导航用的2D栅格地图或者3D地图。Nav2拿到这张地图之后在它上面叠加实时传感器的障碍物信息生成代价地图再用规划算法算出一条从当前位置到目标点的安全路径。这里有个容易被忽略的点SLAM输出的地图是静态的。地图建完之后房间里如果多了个快递盒地图上是没有的。Nav2的价值就在于用实时点云或雷达数据去更新代价地图让机器人在动态环境中也能躲开障碍物。所以整个链路不是单向的地图是SLAM给Nav2的“底图”实时点云是Nav2给路径规划的“增量更新”。1.2 硬件信息流与坐标系从硬件到软件的信息流我习惯画成这样一条链传感器原始数据 → 点云/激光扫描 → 里程计与IMU → SLAM节点 → 地图与位姿 → Nav2代价地图 → 规划器 → 速度指令。这里面最核心的是坐标系变换。扫地机器人身上有base_link机器人本体坐标系、laser_link雷达坐标系、odom里程计坐标系、map地图坐标系这几套坐标系。SLAM在建图过程中实时维护map到odom、odom到base_link的变换Nav2的每个模块都要依赖这些TF变换才能工作。我实测下来90%的导航异常最后查出来都是TF发布频率不稳定或者坐标系写错导致的。还有一个实操经验扫地机器人底盘比较矮传感器安装高度对后面点云处理影响很大。如果雷达或者相机装得太低地面点会大量混入导致SLAM扫描匹配出错装得太高又可能看不到桌腿这类低矮障碍。我在某台机器上把D435装到离地约30厘米的位置配合高度切片过滤地板和低矮障碍物都能干净分离这个高度区间在扫地机上算是比较稳妥的。1.3 硬件选型2D雷达、3D雷达、结构光相机怎么搭配硬件选型决定了后面所有代码怎么写这块值得单独说。2D激光雷达比如RPLIDAR A1/A2是扫地机器人的传统方案成本低、算法成熟SLAM Toolbox和Gmapping都能直接吃scan数据。它的缺点是只能看到激光平面上的障碍物桌面上悬空的物体完全感知不到但扫地机器人本身就是要贴地工作所以这个缺点在扫地场景下问题不大。3D雷达比如Livox MID-360的优势是视野广、点云密度高但不直接输出2D scan要么先转成2D扫描喂给传统SLAM要么干脆走3D SLAM。RealSense D435这类结构光相机则完全是另一条路线输出的是深度点云能感知立体障碍物也能输出彩色图像方便做视觉辅助。在实际项目里我见过几种搭配激光雷达IMU、D435单目深度相机、D4352D雷达融合。对于纯扫地场景我个人的排序是2D雷达最省心D435可玩性最高但要处理深度噪声3D雷达适合房间较大的场景。如果你预算有限先用2D雷达把SLAM和Nav2整条链路跑通再考虑加D435补充障碍物感知这样调试成本最低。2. 点云获取与处理从RealSense D435到可用障碍物信息2.1 D435点云获取与ROS2驱动RealSense D435是英特尔的结构光双目深度相机它用红外投影仪投射不可见的结构光纹理配合双红外相机做立体匹配所以室内白墙这种没什么特征的环境也能出深度。在ROS2里驱动它非常简单装好realsense2_camera包后一条命令就能起来ros2 launch realsense2_camera rs_launch.py \ depth_module.depth_profile:640x480x30 \ pointcloud.enable:true这里的关键参数是depth_profile我实测640x48030fps是性价比最高的配置。分辨率再高点云稠密到几百万点SLAM和Nav2根本用不上反而让CPU直接拉满。分辨率再低深度空洞会明显增多近距离桌腿都可能缺一块。点云话题会发布在/camera/depth/color/points坐标系是camera_link。这里要注意一点D435的深度有效范围是0.3米到3米左右离得太近反而会丢深度。扫地机器人底盘低相机如果朝向正前方近距离地面的盲区是客观存在的只能通过角度调整去缓解。我调试时遇到一个典型问题客厅窗户阳光很强的时候D435的深度图上会出现大块黑色空洞。原因是阳光里的红外成分干扰了结构光投影把双目相机的红外图像直接打穿了。这个问题的缓解办法有几个一是把相机曝光时间调低二是加遮光板三是在算法层面对深度空洞做插值。最省事的其实是调整安装角度避免正对窗户。2.2 点云滤波与体素降采样D435每帧会产出一批点云但这里面混着大量噪声和地面点。直接把原始点云丢给SLAM或者成本地图轻则地图模糊重则导航乱转。我的处理链路固定是三步体素降采样、直通滤波、高度切片。体素降采样用的是PCL的VoxelGrid滤波器原理是把空间划分成一个个小立方体每个立方体里只保留一个中心点。对于D435的点云我习惯把leaf_size体素边长设成0.02到0.03米。太大会把细桌腿也磨没了太小则没有降采样效果CPU白烧。!-- pointcloud_to_laserscan 的典型配置片段 -- node pkgpointcloud_to_laserscan execpointcloud_to_laserscan_node param nametarget_frame valuebase_link/ param nametransform_tolerance value0.1/ param namemin_height value0.05/ param namemax_height value0.25/ remap fromcloud_in to/camera/depth/color/points/ remap fromscan to/scan/ /node重点说高度切片。扫地机器人的任务是贴地移动真正需要避障的是从地面到机身高度的障碍物。把min_height设成0.05米max_height设成0.25米就能把桌腿、椅子腿这些“必须躲开的东西”保留下来把地板反射、低矮地毯、还有天花板这些无关点全部过滤掉。这个思路适用于任何把3D点云转2D激光的场景不只是D435。2.3 3D点云怎么变成2D栅格地图扫地机器人整套框架里SLAM和Nav2绝大多数情况下都工作在2D平面上。这就引出一个核心操作如何把3D点云转成2D栅格地图。目前主流做法有两种。第一种是投影法把点云按高度切片后直接向XY平面投影投影密度高的格子标记为占用。第二种是2D扫描模拟法用ray casting的方式模拟一个2D激光雷达把切片内的点云转成距离值。ROS社区常用的pointcloud_to_laserscan就是典型的转换工具它内部做的事情本质上就是“在指定高度范围内做射线模拟”。我自己的经验是转换时不要只看高度还要看点云的密度。D435在3米外深度噪声明显增大转出来的scan会有一圈“毛刺”这些毛刺在SLAM里容易被当成边缘特征导致建图精度下降。所以在转换之前一定要先做体素降采样把体素leaf_size调到0.02米以上再转效果会干净很多。2.4 结构光相机点云融合的补充经验结构光相机不只D435一种还有Orbbec的Astra系列等等。所谓“点云融合”通常是指把深度相机和彩色相机数据融合生成带RGB颜色的点云或者把多个视角的点云配准融合成更完整的三维模型。在扫地机上做RGB-D融合的实际意义更多是给重定位和回环检测提供颜色辅助。比如在一个全是白色墙壁的客厅里激光SLAM的分辨率不够容易发生“走廊尽头认不出来”的问题但如果有彩色图像视觉特征能帮助区分不同位置的相似结构。我曾经在两条外观几乎一样的走廊里做重定位纯激光只能靠里程计推融合D435的图像特征之后能明显降低定位漂移。不过要提醒的是结构光相机在阳光直射下深度质量会严重下降所以融合算法里一定要加置信度判断深度值低于某个阈值就丢弃该点不要强行融合。3. SLAM方案选型与参数调优3.1 SLAM Toolbox、Cartographer、Gmapping怎么选在建图方案上扫地机器人圈子里最常见的三个选择是SLAM Toolbox、Cartographer和Gmapping。我分别跑过一轮对比直接说结论。Gmapping是老爷辈的粒子滤波SLAM依赖里程计质量适合小房间和低算力平台但几乎没有回环检测长时间建大图会飘。Cartographer是Google出的图优化SLAM把激光扫描匹配和子图优化结合起来回环检测强写进论文里的那种强。它能做2D也能做3D代价是配置复杂调参是个技术活。SLAM Toolbox是ROS2官方推荐的方案基于Karto算法在回环检测和地图质量上平衡得很好关键是配置非常简单对新手友好得多。我的选择很直接扫地机器人这种室内结构化环境用SLAM Toolbox最省心。原因有三点一是ROS2原生支持launch文件配起来没那么多幺蛾子二是它的2D建图精度在客厅、卧室这种场景足够用三是有自带的定位图功能能和Nav2无缝衔接。Cartographer更适合大面积厂房、走廊这种环境或者你需要3D地图的场合。方案算法类型回环检测ROI2支持配置复杂度适合场景Gmapping粒子滤波弱不原生低小房间、低算力Cartographer图优化强一般高大场景、2D/3DSLAM Toolbox图优化中强原生低室内扫地、服务机器人3.2 SLAM Toolbox关键参数与调参逻辑SLAM Toolbox的配置文件本质是一堆参数很多人一上来就抄别人的YAML结果跑出来的地图稀烂。这里必须理解每个参数是干什么的。slam_toolbox: ros__parameters: max_laser_range: 12.0 minimum_score: 0.2 laser_frame: laser_link odom_frame: odom map_frame: map scan_match: 1 loop_closure: 1 map_update_interval: 0.5先说max_laser_range。这个值要和你的传感器实际量程匹配。D435转出来的scan有效距离大概3米你如果写成12米SLAM会认为远处也有可靠特征结果把噪声当障碍物。我建议max_laser_range设置为传感器可靠量程的80%左右宁可少看远一点也别吃过多的噪声。minimum_score是扫描匹配的最低得分。建图时如果环境特征太弱比如空旷走廊得分会普遍偏低。设置太严比如0.4以上SLAM会频繁判定匹配失败地图建一半就罢工设置太松比如0.1它会把明显错误的匹配也接受下来地图直接扭曲。我一般从0.2起调如果建图过程经常报告Scan Matching失败就往下降如果地图出现重影错位就往上升。map_update_interval控制地图刷新频率。扫地机器人在建图时如果旋转速度较快建议设0.5秒刷新一次不然规划时看到的地图明显滞后。但也不能太低否则CPU占用飙升。这个值本质上是给地图光滑性和CPU负载之间做权衡。还有一个经常被忽视的选项scan_match设置在MODE_2D还是MODE_3D。D435转成2D scan之后MODE_2D完全够用。但如果你直接喂3D点云给SLAM Toolbox就必须考虑3D模式那个牵扯到更多传感器位姿估计一般扫地场景用不到。3.3 点云配准在SLAM里的实际角色聊到SLAM很多人会看到“点云配准”这个词觉得是个独立知识体系。其实SLAM每时每刻都在做点云配准只不过在SlAM工具箱里它叫scan matching扫描匹配。所谓扫描匹配就是把当前时刻的激光帧和上一帧或者已有地图对齐算出机器人相对上一个位姿的增量。经典做法是ICP迭代最近点和NDT正态分布变换Cartographer用的是Ceres Solver去做非线性优化求解。ICP的思路是找两组点云里距离最近的点对计算旋转和平移让它们尽可能重合。扫地机每走一步雷达看到的墙线就是一组点云SLAM通过配准把连续帧拼成完整地图。我调过最深的一个坑是建图时机器人转得太快导致扫描匹配失败。原因是激光帧率一般10Hz如果角速度太快两帧激光之间的视角变化过大ICP迭代会陷入局部最优。解决办法很简单建图时控制线速度和角速度的上限比如线速度不超过0.3m/s角速度不超过0.5rad/s。这个限制在slam_toolbox的配置里不一定直接体现但可以通过遥控端去控制。3.4 视觉SLAM在扫地机上的坎热词里出现了视觉SLAM、Visual SLAM、图像引导点云这些在扫地机上不是不行但确实不容易。视觉SLAM的核心是用相机图像提取特征点通过特征点三角化得到点云再优化位姿。以ORB-SLAM3、VINS为代表的一类算法在室内环境表现尚可但扫地机这个场景有几个天然的坎。第一扫地机视角太低。相机贴地20-30厘米看到的全是地面纹理和各种腿特征点分布极其不均匀。第二室内白墙和光滑地板缺乏纹理特征提取数量不足视觉里程计很容易跟丢。第三结构光相机在阳光直射下深度失效视觉SLAM跟着完蛋。所以在实际扫地机项目里视觉SLAM更多是充当激光SLAM的补充而不是替代品。我见过一个比较靠谱的融合方案激光SLAM做主定位视觉SLAM做回环检测的图像验证。两者融合之后在长走廊里能有效减少重复场景的误匹配。这个方案对算力要求不低但效果确实比纯激光好。4. Nav2导航代价地图、行为树和路径规划4.1 代价地图的三层结构与配置地图建好之后Nav2要做的事不是直接在上面跑A*而是先构建一张代价地图costmap。代价地图分三层静态层、障碍物层、膨胀层。静态层加载SLAM输出的栅格地图障碍物层实时接收雷达或点云数据膨胀层负责把障碍物“画胖一圈”给机器人留出安全距离。local_costmap: local_costmap: ros__parameters: robot_radius: 0.18 inflation_radius: 0.30 obstacle_layer: observation_sources: scan scan: topic: /scan max_obstacle_height: 0.20 min_obstacle_height: 0.05有几个参数我必须强调。robot_radius是机器人底盘的等效半径扫地机的半宽可能只有0.15米但为了不刮到家具我习惯加上一点余量。inflation_radius决定障碍物的膨胀距离设大了路径会绕远路设小了可能贴着墙角走。0.3米对于半径0.18米的扫地机是个比较平衡的值。障碍物层的min_obstacle_height和max_obstacle_height很多人直接抄默认结果很多时候机器人在桌子底下转圈因为桌面被当成障碍物了。扫地机要考虑的是到底哪些高度有碰撞风险。如果机器人高0.15米障碍物高度切片设成0.05到0.25米就刚好桌面高度0.7米以上的物体在2D代价地图里根本不需要出现的。4.2 Nav2行为树把导航逻辑组织起来Nav2的行为树Behavior Tree是它跟传统导航架构最大的不同。你可以把行为树想象成一个可编程的导航决策流程图每个节点是一个动作或条件节点之间用序列或回退连接。默认的NavigateToPose行为树核心结构是这样一段逻辑先检查目标是否已经到达没有的话就执行“计算路径→跟随路径→如果失败则恢复”的循环。恢复节点RecoveryNode可以说是扫地机导航的保险丝路径规划失败或者跟随路径卡住的时候会自动触发清理代价地图、原地旋转、后退这一系列动作。这里我要特别说说默认行为树的局限。默认恢复动作是ClearEntireCostmap、Spin和BackUp它们解决“代价地图脏了”的问题但解决不了“机器人根本不知道自己在哪”的问题。在扫地机这种经常被搬来搬去的小车上我强烈建议在行为树里加一个重定位分支当路径规划连续失败超过一定次数就触发主动重定位用amcl重新计算位姿而不是傻乎乎地在错误的位置上反复后退。自定义行为树的方式是改BT的XML文件。Nav2的bt_navigator允许你指定自己的XML我可以直接提供一种简化结构主线是计算路径和跟随路径失败时先清理地图再原地旋转若还是失败就触发全局重定位。这样做的好处很实际扫地机被用户拎到另一个房间之后不会再卡在“旧地图找不到路”的状态里。4.3 规划器与控制器Dijkstra还是DWANav2的规划分两层全局规划器负责在地图上算出一条从起点到目标的无碰撞路径局部规划器负责实时避障并生成速度指令。全局规划器我用的是NavFn它支持Dijkstra和A两种算法。扫地机场景建议用Dijkstra因为代价地图里的代价不是二值的A在复杂代价场下可能找到的不是最优解。Dijkstra的计算开销稍大但室内地图规模不大CPU完全扛得住。关键参数tolerance设置成0.3米表示“目标点附近的这段距离内能找到路径就算成功”防止因为目标点被膨胀层覆盖导致路径规划失败。局部控制器我用DWADynamic Window Approach它会在每个控制周期枚举一组线速度和角速度候选预测未来一小段时间的运动轨迹挑一条既避障又接近全局路径的轨迹输出。扫地机这种差速底盘DWA是最稳妥的选择之一。关键参数是max_vel_x和acc_lim_x室内环境下线速度上限0.4m/s加速度上限1.0m/s²就够了。过快会加剧惯性导致转弯时碰撞墙角。controller_plugins: [FollowPath] controller_frequency: 10.0 FollowPath: plugin: nav2_dwb_controller::DWBLocalPlanner max_vel_x: 0.4 min_vel_x: -0.15 acc_lim_x: 1.0 acc_lim_theta: 1.5DWA调参有一个很反直觉的地方max_vel_x设得太大反而会让机器人卡住。扫地机在狭窄过道里速度越快DWA的可行轨迹越少最后规划出来的往往是急刹车。我把限速从0.6降到0.4之后过门和转弯的流畅度明显提升。4.4 3D雷达怎么接入Nav2热词里有一条是“Nav2导航使用3D雷达”这确实是一个高频需求。3D雷达接入Nav2有两条路一条是把3D点云话题直接给障碍物层另一条是转成2D scan再喂进来。直接给点云的方式配置上很简单把障碍物层的observation_sources指向点云topic就行。但实测下来有个大坑3D雷达点云量太大没做体素滤波的话costmap更新频率会掉到几赫兹导航直接变卡顿。所以如果要用点云直通行务必在雷达驱动里先做降采样。另一种更成熟的方式是先把3D雷达点云转成2D scan再接Nav2。这个方式的好处是Nav2的2D代价地图算法稳定调参经验和2D雷达完全通用。转换工具还是pointcloud_to_laserscan重点是高度切片要和雷达安装高度匹配。我用Livox MID-360的时候扫到地面点比较多所以把min_height调得比雷达安装高度略高一点把地面点尽量过滤干净。这样转出来的2D scan质量接近传统2D激光后续SLAM和Nav2都不用改套路。5. 全链路联调与问题排查实录5.1 高频故障速查表走完整条链路我把实际遇到的高频问题整理成了下面这个速查表。每一项都是我在调试中验证过的直接照表排查能省不少时间。故障现象根本原因排查方法解决方案建图时地图扭曲错位里程计漂移或TF错误用plotjuggler看odom变换检查base_link与laser_link坐标校准里程计确认激光安装姿态建图中途SLAM频繁报匹配失败minimum_score过高查看SLAM日志统计匹配得分将minimum_score降低到0.15-0.2建图时地图出现重影点云噪声过大或帧率不足停走看原始scan观察毛刺体素降采样加大滤波强度导航路径贴着墙走膨胀半径过小或障碍物高度切片不对检查代价地图的inflation层可视化增大inflation_radius到0.3米以上机器人导航时反复原地转圈恢复行为触发但代价地图持续被生成观察costmap是否被点云噪声污染增加obstacle层高度切片验证点云滤波amcl定位突然跳变粒子数太少或更新频率过高查看粒子分布可视化增加min_particles到1000降低update_min_a目标点无法到达目标点被膨胀层完全覆盖查看全局代价地图目标区域缩小膨胀半径或调整tolance到0.3以上5.2 实测中的避坑心得第一个心得是先调坐标系再调算法。我在联调的第一周几乎一半时间花在TF上。D435出来的点云在camera_link下但SLAM期望的scan帧是laser_link如果这两个坐标系的变换没有正确发布SLAM建出来的地图就是个旋转过的世界。任何地图问题先检查TF树用ros2 run tf2_tools view_frames看一眼坐标系关系正常后再深入调参。第二个心得是建图过程本身就要控制机器人运动。我见过很多人在建图时遥控机器人满屋子乱窜以为建图越快越好。实际不是这回事。SLAM匹配依赖连续帧之间的重合度转向太快或直行太猛都会导致匹配失败。我用SLAM Toolbox建图时习惯用遥控手柄以0.2m/s左右的线速度、缓慢的角度变化走完整个房间遇到走廊尽头还专门原地旋转几圈把回环特征采集充分。这样建出来的地图后期给Nav2用会顺手很多。第三个心得是AMCL的初始位姿不要省。很多人在启动导航时不设置initial_pose结果AMCL粒子发散机器人以为自己在房间外面规划器当然算不出路径。这里有个操作习惯每次启动导航前在RViz里给AMCL发送一个大概的初始位姿哪怕精度差一点都没关系关键是先把粒子撒到正确区域附近。在扫地机上这个“确定自己大概在哪”的动作比什么都重要。5.3 一套调试效率提升组合拳调试全链路时工具链比想象中重要。我常用的三件套是RViz2、plotjuggler和ros2 topic。RViz2用于可视化地图、代价地图、路径和粒子是整个调试的主战场。几乎所有参数调整我都是看着RViz2里的实时画面来决定方向。plotjuggler则用来查看里程计、IMU、cmd_vel这些数值信号的时序曲线。有一次导航抖动我就是在plotjuggler里发现里程计的线速度曲线有高频毛刺才知道是轮子打滑而不是规划器的问题。ros2 topic主要用于检查话题频率和数据接口比如用ros2 topic hz /scan去确认雷达话题有没有掉频。调试顺序上我的习惯是先验证数据流。依次检查D435点云频率、scan话题频率、TF树是否无缺失、SLAM是否正常建图。链路每一步都有明确的话题和日志输出哪一步断了看哪一步。等数据流全通了再开始调Nav2的行为树和规划参数。这样分段排查能避免把问题混在一起。5.4 从单机到量产还有哪些坑在等你如果只是做一台原型机上面的内容已经够用。但如果你想把方案往量产方向推还有几个坑要提前考虑。第一个是硬件一致性。一个型号的扫地机不同批次传感器的安装角度、离地高度可能有微小差异这个差异在建图上会累积成地图旋转误差。量产方案一定要做产线标定把激光和相机相对机器人本体的外参固化成标定流程。第二个是异常恢复策略。量产设备不能被用户搬起来之后就一直卡在一个死循环里需要有一套异常检测逻辑比如加速度计检测到被抬起来的时间超过一定阈值就自动触发全局重定位。这个逻辑Nav2默认没有必须自己在行为树里加。第三个是算力余量。我在调试时用PC跑的到了嵌入式板卡上D435点云加上Nav2全局规划CPU开销非常可观。嵌入式平台建议把深度相机的分辨率进一步降低点云降采样leaf_size调到0.05米Nav2的costmap更新频率从10Hz降到5Hz。整套方案能跑起来的前提是单核性能足够扛住点云处理不然你就只能老老实实换更轻量的2D雷达方案。最后一个我想说的体会是SLAM和Nav2的链路看起来很长但真正决定成败的往往不是算法本身而是数据的一致性。传感器帧率是否稳定、坐标系是否发布正确、点云滤波参数是否匹配硬件特性这些“不起眼”的细节才是让整个系统从能跑变成跑得好的关键。如果你在调试中也被某些诡异问题卡了很久不妨先放下算法参数回去检查一遍数据链路的质量。