简介:本资源是一个基于ROS 2与Navigation 2框架构建的自动巡检机器人仿真项目,面向机器人开发初学者及ROS进阶学习者,聚焦环境感知、路径规划、语音交互与图像采集等核心功能实现,适用于工业巡检、设施运维等实际场景的技术验证与教学实践。压缩包共60个文件,含20个Python节点脚本(实现导航控制、语音播报与图像保存)、12个XACRO模型文件(定义机器人URDF结构)、4个YAML配置文件(承载Navigation 2参数与地图坐标)、2个RVIZ可视化配置及1个Gazebo仿真World环境,整体仅68KB,轻量易部署。已有391人学习下载,资源结构清晰分层:涵盖fishbot_navigation2导航栈、fishbot_description机器人模型、autopartol_robot主控逻辑及autopatol_interfaces自定义服务接口,提供从建模、仿真到任务闭环的完整代码链路,可直接运行复现目标点循环巡检、语音提示与本地图像快照功能。 做巡检机器人这几年,我最大的感受是:真正把一台车从"能在仿真里跑起来"推到"能在现场稳定干活",中间隔着的东西远比想象中多。如果你正打算用 ROS 2 和 Navigation 2 攒一台自动巡检机器人,或者已经在调试路上被各种 QOS、TF、代价地图参数折磨,这篇文章应该能帮你省下不少时间。我尽量用一种边做边讲的口吻,把方案怎么定、参数怎么调、坑怎么踩,都摊开来说清楚。
先交代一下背景。我做的项目是基于 Ubuntu 22.04 + ROS 2 Humble + Nav2 的差速轮式巡检小车,搭载 2D 激光雷达、IMU、深度相机和一块一块工控板,主要场景是室内园区、仓库和机房通道的定时巡检。核心任务不是"能走",而是"按固定航线稳定走、误差小、能避障、丢定位了能自己找回"。这篇文章就是围绕这套东西展开的,内容包括整体架构、建图与定位、路径规划与代价地图参数、巡检任务调度、真实调试中的高频问题,以及我从零到跑通真机的完整操作记录。
如果你是 ROS 2 / Nav2 的新手,建议先把官方教程里的 turtlebot3 仿真跑一遍,再回来看这篇,会顺很多。如果你已经入门但卡在"仿真没问题、真机全是问题",那重点看第四、五部分,那都是实打实的现场经验。
1. 整体方案设计与技术选型
1.1 为什么选 ROS 2 + Nav2 而不是ROS 1
做巡检机器人,第一步就是选技术栈。ROS 1 的 move_base 方案在前几年非常成熟,资料多、教程多、很多人一上来就照着旧项目抄。但我还是选了 ROS 2 + Nav2,原因有三个。
第一是通讯机制。ROS 2 基于 DDS,节点之间是去中心化的,不像 ROS 1 需要一个 master 节点。巡检机器人要长时间无人运行,最怕的就是主节点挂了、整个系统瘫痪。ROS 2 的节点各自独立,就算某个传感器节点崩了,导航和任务调度还能继续跑,可以设计降级策略。这一点在长时间自动化场景里非常重要。
第二是生命周期管理。Nav2 里的各个 server(planner、controller、behavior)都实现了生命周期节点,意味着你可以有序地配置、激活、暂停、清理它们。比如机器人进入低电量状态,你可以先暂停导航行为服务器,再慢慢执行返航任务,而不是粗暴地 kill 掉整个进程。
第三是代码维护和生态趋势。ROS 1 已经停止维护,新出的传感器驱动、算法库、硬件接口都在往 ROS 2 迁移。现在做新项目,再用 ROS 1 等于启动即落后。Nav2 的行为树架构也比 move_base 的有限状态机灵活太多,后面做巡检任务编排会非常明显。
1.2 系统架构分层
整个巡检机器人系统我是按四层来设计的,这也是我推荐任何同类项目采用的方式。
- 感知层:激光雷达(2D LiDAR)负责建图和 2D 定位,IMU 提供里程计补充,深度相机负责近距离障碍物检测,必要时还能做目标识别(表计读数、灯光状态、人员闯入等)。
- 决策层:Nav2 是核心,负责全局路径规划、局部控制、避障、恢复行为。上层再跑一个巡检任务调度节点,负责任务队列、航点管理、异常处理。这是整个系统的大脑。
- 执行层:底盘驱动节点接收 /cmd_vel,通过串口或 CAN 下发到底盘 MCU,MCU 做电机闭环控制。这里要特别注意底盘 MCU 必须有自己的急停、堵转保护逻辑,不能完全依赖上层。
- 监控层:一个单独的状态监控节点,定期检查 topic 频率、节点存活、电池电压,并把这些状态上报给调度节点,必要时触发安全停靠或返航。
这种分层的好处是每层都可以独立测试。比如我只想测底盘,就发个 /cmd_vel 看它是不是按预期转;只想测导航,就手动发布一个 goal 让 Nav2 自己去规划。坏了哪一层,不需要整机拆开排查,效率高很多。
1.3 巡检任务的核心需求拆解
自动巡检机器人与普通的"点到点导航"最大的区别在任务模型上。普通导航是用户给一个目标点,机器人从当前位置走过去;巡检则是一套"多点、循环、定时、有条件跳转"的逻辑。
我把巡检需求拆成几个模块:
- 航点管理:用一组带有位姿(x, y, theta)的航点描述一条巡检路线。航点需要支持增删改查,并且要能对应到地图上的物理位置。
- 任务调度:机器人在线巡检可简单理解为一个状态机:空闲(IDLE)→ 前往目标点(NAVIGATING) → 抵达检测点(INSPECTING)→ 继续下一个 / 返回充电桩。状态机用 ROS 2 action client 跟 Nav2 的 action server 交互。
- 异常处理:包括导航超时、定位丢失、任务失败重试、低电量返航等。这层如果做不好,机器人就会在现场"卡死"或"乱跑"。
如果你只是做一个 demo,那用 Nav2 自带的 waypoint_follower 就能满足基本需求。但如果你要做真正的产品级巡检,我强烈建议自己写一个简单的任务调度节点,因为 waypoint_follower 太简陋了,不支持条件跳转、不支持任务反馈、不好对接业务逻辑。后面我会给一份代码思路。
2. 核心细节解析与实操要点
2.1 建图怎么做,SLAM 工具选型
导航的前提是一张准确的静态地图。在 ROS 2 里,主流选择是 slam_toolbox 和 Cartographer。
我实测下来的结论是:室内结构化场景优先用 slam_toolbox,大场景或环境特征复杂的再用 Cartographer。slam_toolbox 是基于 2D 激光的图优化 SLAM,轻量、参数少、上手快,而且支持栅格地图的保存,配合 Nav2 是黄金搭档。Cartographer 精度高,但依赖多、配置复杂,在 ROS 2 里编译和调参成本都比较高。
建图时几个要点:
先检查 TF 是否正确。启动 SLAM 之前,必须保证odom -> base_footprint -> base_link -> laser这条 TF 树链路完整。如果 TF 有问题,即使你看到雷达数据正常,地图也会扭曲。
控制建图速度。手动遥控机器人时速度要慢,建议线速度不超过 0.3 m/s,角速度不超过 0.5 rad/s。速度快了容易造成扫描匹配漂移,地图出现重影。我见过很多人为了赶时间快速推车,结果地图质量一塌糊涂,后面定位就特别痛苦。
闭环检测很重要。建图时尽量让机器人走回已经经过的区域,特别是起点和终点最好重合,这样 slam_toolbox 才能做闭环优化。如果建图过程中发现地图错位,不要抱着"后面再修图"的侥幸心理,回头重跑比修图容易得多。
建图完成后保存地图,同时保存map.yaml。要仔细核对 yaml 里的resolution(每像素代表的米数)、origin(地图原点在世界坐标的位置),这些参数错了,加载地图后定位基准全是歪的。
2.2 定位方案,能用里程计就先用里程计
Nav2 默认的定位是靠amcl节点在已知地图中做粒子滤波。AMCL 需要输入激光数据、TF、地图,输出map到odom的变换。
AMCL 也不是万能的。它依赖两个前提:初始位姿要大致给对,里程计不能漂移得太离谱。我第一次在真机上跑,忘了给初始位姿,结果粒子全部收敛到错误位置,机器人走两步就开始横冲直撞。后来我在任务调度里加了一步:启动时自动设置初始位姿,如果导航定位方差过大就异常退出重试。
关于里程计,我建议至少做到两点:
- 悬空/打滑检测。如果机器人编码器有反馈但里程计没有变化,或者 /cmd_vel 指挥它运动但 odom 没有响应,说明轮子打滑或者底盘卡死,要立刻进入异常处理。
- 轮距与轮径校准。用长距离直线走测试,走 10 米看里程计报了多少,把误差控制在 1% 以内。这个校准如果偷懒,AMCL 的粒子始终会在真实位置附近抖,定位精度上不去。
如果环境里轮子打滑严重(比如地面有水、有油),我建议增加 IMU 融合,用robot_localization的 EKF 节点把轮式里程计和 IMU 数据融合,输出更稳定的 odom。这会极大提升 AMCL 的稳定性。
2.3 代价地图:导航的“交通安全规则”
Nav2 的路径规划本质上是在代价地图上做的。代价地图分成全局代价地图(global_costmap)和局部代价地图(local_costmap),两者可以有不同的传感器来源、不同的分辨率、不同的膨胀半径。它们的 YAML 配置文件是导航调参中最重要的对象,我直接把常用配置贴出来:
# global_costmap.yaml global_costmap: global_frame: map robot_base_frame: base_footprint update_frequency: 1.0 publish_frequency: 1.0 transform_tolerance: 0.5 static_map: true rolling_window: false resolution: 0.05 plugins: - name: static_layer type: nav2_costmap_2d::StaticLayer - name: inflation_layer type: nav2_costmap_2d::InflationLayer inflation_radius: 0.4 cost_scaling_factor: 3.0# local_costmap.yaml local_costmap: global_frame: odom robot_base_frame: base_footprint update_frequency: 5.0 publish_frequency: 2.0 transform_tolerance: 0.5 rolling_window: true width: 3.0 height: 3.0 resolution: 0.05 plugins: - name: obstacle_layer type: nav2_costmap_2d::ObstacleLayer obstacle_range: 2.5 raytrace_range: 3.0 - name: inflation_layer type: nav2_costmap_2d::InflationLayer inflation_radius: 0.3 cost_scaling_factor: 2.0关键参数的经验值:
- inflation_radius:决定路径离障碍物的安全距离。调太大,窄通道会认为不可通行,机器人绕远路;调太小,机器人贴着墙走,容易碰撞。室内小车,我一般全局设 0.4m,局部设 0.3m。
- cost_scaling_factor:控制代价衰减速度。值越小,远离障碍物的代价衰减越慢,路径越“贴边”;值越大,代价下降越快,机器人越倾向于走在通道中央。我习惯全局设 3.0、局部设 2.0,如果你的通道比较窄,可以适当加大这个值。
- obstacle_range / raytrace_range:决定代价地图清障碍的方式和范围。obstacle_range 2.5 意味着 2.5 米内的激光点才会被标记为障碍,超过这个距离的忽略;raytrace_range 3.0 意味着 3 米内的射线都会被用于“清除”自由空间。
调试时一定要打开 RViz 看代价地图的可视化层。我发现很多新手埋头改参数,却不知道当前地图里到底把哪些区域标成了致命障碍、哪些区域是膨胀区域。打开 RViz 之后,很多问题一目了然。例如:
- 地图上有大片 inflate 高亮的“通道堵死”,说明膨胀半径太大。
- 地图上出现“孤岛”障碍,可能是传感器数据帧没配好或者时间戳问题。
- 局部代价地图一直抖动,可能是激光数据频率太低,或是 TF 延迟过大。
2.4 路径规划与局部规划器的选择
Nav2 的全局规划器默认是 NavFn,一个基于 A* 的 2D 全局规划算法。它对大多数室内巡检场景已经足够。如果你的场景比较复杂(比如有斜坡、宽大空间、需要更平滑的全局路径),可以考虑 Smac Planner,但配置难度也更高。
局部规划器是真正决定机器人"走起来舒不舒服"的关键。Nav2 默认提供 DWB(Dynamic Window Approach 的改进版),DWA 是它的基础版本。两者的共同逻辑是:在每个控制周期,采样多组速度和角速度组合,用代价函数评估每组速度在未来一小段轨迹上的安全性、朝向正确性、离全局路径的距离,选出最优的一组执行。
DWB 比 DWA 好的地方在于支持多个批评器(critics)的独立配置,你可以分别调节"避免障碍、接近全局路径、转向目标、速度维持"的权重。比如你的巡检路线有很多长直道,就应该适当调高"维持目标速度"的权重,让机器人在中间不会频繁降速,巡检效率更高;如果你的路线全是狭窄弯道,就应该把"转向目标"和"保持安全距离"权重大一些。
我在调试现场经常做这样一组实验:让机器人以 0.5 m/s 的目标速度走过一段 10 米的直道,观察它速度曲线的平顺度。如果速度忽快忽慢,多半是 DWB 的权重失衡,不是机器人硬件问题,别一上来就怪底盘。
局部代价地图还有一个非常关键的参数:publish_frequency 不能太低。很多人的局部代价地图更新频率只有 0.5Hz,那意味着机器人每走 2 秒才更新一次障碍信息,遇到突然出现的行人,根本来不及避让。我在室内场地要求局部代价地图至少 5Hz 更新,障碍层至少 10Hz,宁可增加一点 CPU 占用,也要保证响应速度。
3. 实操过程与核心环节实现
3.1 从仿真到真机:先把 turtlebot3 跑通
如果是新项目,我建议先在仿真里把 Nav2 的整个流程跑通,再上真机。仿真可以帮你快速验证"地图加载、AMCL 定位、全局规划、局部控制、任务调度"这条完整链路,把逻辑上的问题先消灭掉。
启动 Nav2 的标准流程是:
# 1. 启动 Gazebo 仿真环境 # 以 turtlebot3 为例 export TURTLEBOT3_MODEL=burger ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py # 2. 启动 SLAM 建图 ros2 launch turtlebot3_cartographer cartographer.launch.py # 3. 保存地图 ros2 run nav2_map_server map_saver_cli -f ~/map # 4. 启动 Nav2 ros2 launch nav2_bringup bringup_launch.py map:=/home/user/map.yaml # 5. 在 RViz 里 2D Pose Estimate 设置初始位姿,Nav2 Goal 发布目标点仿真跑通后,把你自己的机器人模型、传感器配置替换进去。这一步主要验证模型的 URDF/Xacro、传感器数据、底盘驱动是否和 Nav2 的接口兼容。
3.2 真机部署时的关键检查和启动顺序
真机和仿真最大的差别在于:真机的数据会延迟、抖动、丢失,底盘的执行也有误差。因此启动顺序和检查逻辑要比仿真严谨得多。
我总结了一套"从底往上"的启动顺序:
- 先启动底盘驱动节点,确认 /odom 和 /cmd_vel 能正常工作。执行
ros2 topic echo /odom观察里程计是否连续更新,再手动发几个速度指令看底盘响应。 - 启动激光雷达驱动,确认 /scan 数据在 RViz 里显示正常,不存在遮挡盲区错误。雷达如果安装在底盘边缘,还要确认它的安装角度、坐标偏移在 URDF 里都写对了。
- 启动 IMU 和 robot_localization 的 EKF,合并里程计。观察 /odom 输出的协方差是否合理、数据是否平滑,有没有跳变。
- 加载地图,启动 amcl。先给初始位姿,再发送一个 1 米左右的小距离目标,看机器人能不能准确移动到误差范围内。如果小距离都不行,不要继续做长距离巡检,先回来排查。
- 确认没问题后,再启动任务调度节点,进入自动巡检模式。
这套顺序我每次换新底盘、新环境都会走一遍,虽然繁琐,但能省后面排查的力气。
3.3 巡检任务调度节点的实现思路
这里我给出一个精简的巡检调度节点思路,用 Python 实现。逻辑很简单:读取一组航点,循环遍历,逐个调用 Nav2 的 NavigateToPose action。
import rclpy from rclpy.node import Node from nav2_msgs.action import NavigateToPose from geometry_msgs.msg import PoseStamped from rclpy.action import ActionClient from action_msgs.msg import GoalStatus class PatrolScheduler(Node): def __init__(self): super().__init__('patrol_scheduler') self.client = ActionClient(self, NavigateToPose, 'navigate_to_pose') # 航点列表,每个元素是 (x, y, theta) self.waypoints = [ (2.0, 1.0, 0.0), (3.0, 2.0, 1.57), (1.5, 3.5, 3.14), (0.5, 1.5, -1.57), ] self.current_wp = 0 def send_goal(self): if not self.client.wait_for_server(timeout_sec=5.0): self.get_logger().error('Nav2 action server not available') return False goal_msg = NavigateToPose.Goal() goal_msg.pose = PoseStamped() goal_msg.pose.header.frame_id = 'map' goal_msg.pose.header.stamp = self.get_clock().now().to_msg() x, y, theta = self.waypoints[self.current_wp] goal_msg.pose.pose.position.x = x goal_msg.pose.pose.position.y = y # 用四元数表示朝向 from tf_transformations import quaternion_from_euler q = quaternion_from_euler(0, 0, theta) goal_msg.pose.pose.orientation.x = q[0] goal_msg.pose.pose.orientation.y = q[1] goal_msg.pose.pose.orientation.z = q[2] goal_msg.pose.pose.orientation.w = q[3] self._send_goal_future = self.client.send_goal_async(goal_msg) self._send_goal_future.add_done_callback(self.goal_response_callback) def goal_response_callback(self, future): goal_handle = future.result() if not goal_handle.accepted: self.get_logger().warn('Goal rejected') return self.get_logger().info('Goal accepted, navigating to waypoint %d' % self.current_wp) result_future = goal_handle.get_result_async() result_future.add_done_callback(self.get_result_callback) def get_result_callback(self, future): result = future.result() status = result.status if status == GoalStatus.STATUS_SUCCEEDED: self.get_logger().info('Reached waypoint %d' % self.current_wp) self.current_wp = (self.current_wp + 1) % len(self.waypoints) self.send_goal() elif status == GoalStatus.STATUS_ABORTED: self.get_logger().error('Navigation aborted at waypoint %d, retrying' % self.current_wp) # 简单重试一次,还不行就进入异常 self.send_goal() else: self.get_logger().error('Unexpected status %d at waypoint %d' % (status, self.current_wp)) # 异常处理:停止任务 self.client.wait_for_server() self.client.cancel_goal(future)上面这段只是核心骨架,真正的产品代码还需要考虑:机器人到达航点后要停留多久、要不要执行拍照/检测动作、失败重试次数上限、低电量返航触发条件、手动暂停恢复等。我建议在调度节点里维护一个完整的状态编译表:
| 状态 | 触发条件 | 处理动作 |
|---|---|---|
| IDLE | 无任务 | 等待指令 |
| NAVIGATING | 有航点任务 | 发送 Nav2 goal |
| INSPECTING | 到达航点 | 执行拍照/检测,等待完成 |
| RETRY | 导航失败 | 重试,最多3次 |
| ABORT | 重试仍失败 | 上报异常,停止任务 |
| CHARGING | 低电量 | 优先导航到充电桩 |
3.4 用行为树(Behavior Tree)实现更复杂的巡检逻辑
Nav2 的一大特色就是内置了行为树(Behavior Tree)作为导航策略。如果你的巡检流程不只是"走点"而是有判断分支(比如"如果电量低,去充电;如果没有检测到任务,回到等待点"),直接在 BT 里写比写在 C++ 状态机里更直观。
Nav2 的默认 BT XML 长这样:
<root main_tree_to_execute="MainTree"> <BehaviorTree ID="MainTree"> <Sequence name="root"> <PipelineSequence name="NavigateWithReplanning"> <RateController hz="1.0"> <RecoveryNode number_of_retries="6" name="NavigateRecovery"> <Sequence> <Planner> <GoalUpdated/> </Planner> <ReactiveFallback> <GoalReached/> <Controller/> </ReactiveFallback> </Sequence> <ClearCostmapService costmap_name="global_costmap"/> </RecoveryNode> </RateController> </PipelineSequence> </Sequence> </BehaviorTree> </root>这个树的意思是:先规划,到达目标前持续控制,如果控制失败就清除全局代价地图,重规划,最多重试 6 次。你可以在里面加自己的节点,比如"如果电量低于 20%,切换到充电桩航点"。
刚开始不熟悉 BT 的话会很懵,但真心建议学一下。它比传统状态机好在:逻辑可视化、可热重载、可复用,而且 Nav2 官方就是用它来组织导航恢复逻辑的,你会很大程度避免自己重复造轮子。
4. 常见问题与排查技巧实录
这一节我整理了我在实际调试中遇到过的、频率最高的 6 类问题,每条都给了判断方法和解决思路,做成一个速查表。
| 现象 | 可能原因 | 判断方法 | 解决办法 |
|---|---|---|---|
| AMCL 定位漂移,机器人实际位置和 RViz 显示不一致 | 初始位姿设置错误;里程计不准;激光数据时间戳乱 | 观察粒子聚集位置是否和真实位置一致;检查 /odom 数据连续性 | 重新给初始位姿;校准轮距轮径;检查 laser scan 时间戳 |
| 全局路径严重绕路,明明直道很近却绕大圈 | 膨胀半径过大;全局代价地图分辨率太低;静态地图有误 | 打开 RViz 看全局代价地图的膨胀区域 | 调小 inflation_radius;调高 resolution;重新建图 |
| 机器人走到一半突然停下,恢复行为频繁触发 | 局部代价地图频繁更新导致代价突变;DWB 权重不合理;局部规划器选不出轨迹 | 看机器人状态是否在 RECOVERING;看局部 costmap 是否抖动 | 调大 transform_tolerance;调 DWB 批评器权重;增加静态层 |
| 窄通道/窄门过不去 | 机器人 footprint 设置偏大;膨胀半径太大 | 用 RViz 测距看通道宽度 | 缩小 footprint;调小膨胀半径;调高 cost_scaling_factor |
| 到达目标点后位置误差特别大,有时偏 20cm+ | 控制器目标容差过严;里程计漂移;AMCL 粒子数不足 | 查看 goal_pose 和实际位置差;看 AMCL 粒子分布方差 | 调整 xy_goal_tolerance/yaw_goal_tolerance;调大 max_particles |
| 传感器数据正常但代价地图不更新 | 代价地图插件没配置好;障碍层没订阅对应 topic;QOS 不匹配 | 查看 costmap 日志;检查 /scan 是否被订阅;检查 topic 类型 | 修正 plugins 配置;检查 sensor topic 名称和 QoS |
下面挑几个重点展开说。
4.1 定位“漂”了怎么找回来
定位漂移是巡检现场最要命的问题。机器人可能只是一个 50cm 的位置偏差,但代价地图里的障碍位置全错了,接下来就会撞墙或者绕路。处理经验:
先看 odom 是不是准的。把机器人手动开到大概位置,再查看 RViz 里的 base_link 是否和地图中的实际位置对上。如果 odom 已经明显不准,AMCL 再强也会跟着偏。
再看 AMCL 参数。如果 max_particles 太少了(比如 500),定位精度会不足,尤其是在光线暗、墙体特征少的环境。可以提升到 2000 甚至 5000,消耗的 CPU 不算大,但定位稳定性提升明显。
如果环境确实复杂,可以用一两个额外的地标(比如反光条、二维码),靠视觉辅助定位。这种做法一劳永逸,但对项目复杂度有影响,一般我在产品化要求高的时候才会加。
4.2 雷达安装位置和 TF 不对导致定位乱
一个我见过多次的新手坑:激光雷达安装位置和 URDF 里的坐标不一致。你要知道,AMCL 是根据"激光数据在 base_footprint 坐标系中的位置"来匹配地图的,如果雷达坐标写错了(比如装在前面 20cm,URDF 里却写居中),那么在地图上看起来就像"雷达扫描到墙的外面",粒子会疯狂跳动,定位永远不对。
调试时先用 RViz 对比:机器人在物理世界中正对着一面墙,RViz 里激光点云应该和墙重合。如果整体偏移,去查 URDF 里 lidar 的 xyz 坐标。如果角度偏移,去查雷达的安装 yaw 角。
4.3 代价地图常驻障碍消不掉
还有一次,机器人走到一片区域后发现前面明明没有障碍,但全局路径一直绕路。查到最后是代价地图的 obstacle_layer 持续把某个"动态物体"当成静态障碍,而这个动态物体其实是以前某次建图时留下的点云噪声。
解决方法是在 obstacle_layer 里调低 obstacle_range,或者打开 raytrace_range 让它主动清除长时间没有扫描到的点。这类问题不太容易一眼发现,但打开 RViz 看代价地图的图层,基本能定位到是把什么区域标记成了障碍。
5. 安全性设计与性能观察
5.1 安全冗余设计
巡检机器人是要脱离人长时间运行的,安全设计绝对不能只靠 Nav2 的避障。我总结了三个维度的安全机制:
- 电机驱动层:底盘 MCU 必须设置电流限制、堵转保护、编码器断线检测,一旦异常立即停车。不能等上层来兜底。
- 导航策略层:Nav2 里设置局部代价地图的禁行区域(比如楼梯口、玻璃墙),同时开启行为树恢复机制。遇到无法恢复的错误,必须自动停车等待人工介入,而不是一直重试。
- 上层监控层:调度节点每 500ms 检查一次 /scan 的发布时间戳,如果超过 1 秒没有更新,说明雷达可能掉线,立即停止导航、进入安全模式。
5.2 性能观察与调优
真机跑导航时,性能瓶颈主要在 CPU 和内存。Nav2 的本地代价地图更新频率高,加上 AMCL 的粒子滤波和 SLAM(如果还在跑),工控板很容易过热降频。
我在部署时养成了一个习惯:每次调试都要开ros2 topic hz和htop观察数据频率和 CPU 负载。一个稳定的系统必须保证 /scan 有稳定的频率、局部代价地图更新不掉帧、全局 Planner 和 Controller 在正常节奏运行,CPU 占用不要长期超过 80%。
如果 CPU 吃紧,优先降低全局代价地图的更新频率(它比局部代价地图重要程度低),再降低 AMCL 的粒子数量,实在不行再考虑传感器降频。这个优先级顺序是我实际调过的经验,因为全局代价地图更新只影响重规划效率,对实时安全影响小,而局部代价地图和 AMCL 是保命和保精度的,不能随意砍。
5.3 巡检效率验证
巡检机器人不是"能走"就完了,效率指标也很重要。我会用一组指标来验收:
| 指标 | 验收标准 |
|---|---|
| 单圈巡检时间 | 在规定时间内完成所有航点 |
| 到点误差 | 每个航点到位误差小于 10cm |
| 定位恢复时间 | 人为打乱 pos 后,AMCL 能在 10 秒内重新收敛 |
| 避障响应时间 | 人为放置障碍物,机器人能在 50cm 内停下或绕开 |
| 连续运行时间 | 跑满 4 小时以上,无崩溃、无任务中断 |
调试时我会专门录制 ros2 bag,把 /odom、/amcl_pose、/global_costmap/costmap、/local_plan 几个关键 topic 记录下来。遇到问题后回放分析,能非常直观地看到机器人当时在想什么。这个手段在远程排查复杂 bug 时特别有价值,强烈建议常态化使用。
6. 实际项目中的一点补充经验
有几个在现场学到的细节,写在这里留给后来人。
第一件事,巡检航线的航点不要只靠手在 RViz 里点。手点很容易让机器人贴着墙跑或者路径不够顺。我一般是在建好图后,用地图坐标直接计算中间点,让航线尽量走在通道中心,转弯半径留足。如果路线复杂,可以在调试时先手动遥控走一遍,记录轨迹作为参考航点,再在轨迹上提取点位。这样出来的巡检路径,比凭空设点顺滑得多。
第二件事,数据时间戳问题在 ROS 2 里十分隐蔽。ROS 2 的 TF2 有很强的"等待变换"机制,但如果你在多个传感器之间做数据融合(比如点云转激光、雷达和 IMU 融合),时间戳不对齐会导致数据漂移、断续。所有传感器的时间戳最好都统一用节点收到消息时的时间,不要用传感器驱动里导出的硬件时间戳,除非你明确知道自己在做什么。
第三件事,现场电磁干扰很容易导致雷达数据异常。一开始我在仓库里测试,雷达偶尔会跳出一圈离谱的数据(比如 10 米外突然出现墙),导航瞬间以为撞墙了,疯狂避障。排查到最后是附近有大功率电机产生的干扰。处理方案是:在雷达驱动里做数据滤波,增加一个简单的最近点/最远点限制,同时把异常数据在代价地图层面过滤掉。这个小经验可能不常见,但真遇到了非常折磨人。
第四件事,关于文档和配置管理。巡检机器人现场调试过程中,参数会改得非常频繁,而且很多时候改完是为了应急,事后又不记得。我后来坚持把所有 Nav2 参数、底盘参数、调度参数全部纳入 git 管理,每次改动写清楚 commit message。有一次机器人表现不稳定,回退参数一查,发现是三天前某次"临时调大速度"没改回来,这就非常直观地定位了问题。
写在最后
自动巡检机器人这个项目,表面上是一堆导航算法和传感器的事,实际上比的是"把系统稳定地跑起来"的耐心。ROS 2 和 Navigation 2 这套生态已经足够成熟,能帮你把底层的规划、控制、定位问题解决掉大半,但真正决定项目成败的,往往是你的任务调度逻辑、安全冗余设计和现场的排查能力。
我希望这篇文章里关于系统架构、参数配置、调试工具箱和问题排查的思路,能让你少走一些弯路。如果你正在做类似的项目,恰好卡在某些细节上,不妨从这几个方面自查一遍,也许答案就在其中。
本文还有配套的精品资源,点击获取