简介这份资源是一套基于ROS框架的室内自主导航机器人系统完整工程包面向机器人方向的学生、研究人员与ROS入门开发者帮助解决SLAM定位建图、路径规划与多模式导航的落地实现问题。系统涵盖自动定位建图、手动建图、定点导航、固定线路巡航以及算法切换与参数分析等模块并基于Turtlebot2平台搭建适合课程设计、毕业课题与算法验证等场景。压缩包共637个文件约5.44MB以h与cpp源码为主体配合xml、launch启动配置、yaml与cfg参数文件、py脚本、xacro与urdf模型、world仿真场景及rviz可视化配置另含srv、msg通信定义与pgm、png地图资源结构完整、层次分明。目前已有282人学习下载。读者可据此获得一套可直接编译运行的导航系统参考实现理解SLAM建图与路径规划各模块的代码组织方式并借助参数配置与算法切换机制针对不同室内环境调整导航策略快速完成从仿真到实机的迁移验证。1. 从一台 Turtlebot2 说起室内自主导航到底在解决什么很多人第一次接触 ROS 自主导航是拿一台 Turtlebot2 在实验室走廊里跑 gmapping看着 RViz 里慢慢长出一张地图然后点一个 2D Nav Goal小车自己绕过去。这个场景背后其实串起了四件事传感器数据进来、SLAM 把地图建出来、定位把机器人放进地图里、路径规划把目标点变成一条可执行的速度指令。标题里说的「自动定位建图、手动建图、定点导航、固定线路巡航、算法切换、参数分析」本质就是把这四件事拆成可切换的模块让同一套硬件在不同任务下换不同的跑法。Turtlebot2 是个老平台Kobuki 底盘加 Kinect 或 RPLIDAR算力靠一台跑 Ubuntu 的笔记本。它不新但胜在资料全、社区大ROS 1 的导航栈在它身上跑得最透。如果你手上是别的差速底盘只要发布/odom、/scan、/tf这套东西照样能迁移。这篇笔记就按「先跑通最小闭环再拆模块最后调参数」的顺序讲新手能照着复现熟手能直接跳到参数和避坑那几章。2. 环境搭建与 Turtlebot2 最小闭环从装 ROS 到让小车动起来2.1 版本选择与安装路径ROS 1 和 Turtlebot2 的搭配绕不开 Ubuntu 版本。Turtlebot2 官方包在 KineticUbuntu 16.04和 MelodicUbuntu 18.04上最完整NoeticUbuntu 20.04也能跑但部分 Kobuki 依赖需要手动补。如果你是新装机我一般推荐 Melodic 或 Noetic前者资料最稳后者 Python3 生态更顺。Ubuntu 22.04 及以上对应 ROS 2Turtlebot2 的 ROS 2 支持不完整硬上会花大量时间在驱动适配上不建议作为第一套环境。安装本身国内网络环境下用一键安装脚本能省很多事社区里常说的「鱼香 ROS 一键安装」就是这类工具它把源配置、依赖安装、环境变量写入串成一条命令。但要注意一键脚本装的是通用 ROSTurtlebot2 的 Kobuki 和导航包还得单独装。# 以 Ubuntu 18.04 Melodic 为例先装 ROS 基础版 sudo sh -c echo deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main /etc/apt/sources.list.d/ros-latest.list sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 sudo apt update sudo apt install ros-melodic-desktop-full # 初始化 rosdep这一步在国内经常卡需要换源 sudo rosdep init rosdep update # 装 Turtlebot2 相关包 sudo apt install ros-melodic-turtlebot ros-melodic-turtlebot-apps \ ros-melodic-turtlebot-interactions ros-melodic-kobuki-* \ ros-melodic-turtlebot-navigation ros-melodic-gmapping \ ros-melodic-amcl ros-melodic-move-base ros-melodic-map-server这段命令的逻辑是先配 ROS 官方源装桌面完整版再用 rosdep 解决包依赖最后把 Turtlebot2 的驱动、导航、建图包一次性装齐。rosdep init和rosdep update是最容易翻车的地方国内直连 raw.githubusercontent.com 经常超时常见做法是换国内镜像源或手动改/etc/ros/rosdep/sources.list.d/20-default.list里的地址。装完后每开一个新终端都要source /opt/ros/melodic/setup.bash嫌麻烦就写进~/.bashrc。2.2 让底盘和雷达先说话环境装好别急着建图先确认硬件在 ROS 里活着。Turtlebot2 的 Kobuki 通过 USB 连接雷达如果是 RPLIDAR 也走 USB。先看设备有没有被识别ls /dev/ttyUSB* # 正常会看到 ttyUSB0Kobuki和 ttyUSB1雷达顺序可能变 # 给串口权限避免每次 sudo sudo usermod -aG dialout $USER # 改完要重新登录才生效然后分两个终端启动底盘和雷达# 终端 1启动 Kobuki 底盘 roslaunch turtlebot_bringup minimal.launch # 终端 2启动雷达以 RPLIDAR A2 为例 roslaunch rplidar_ros rplidar.launch启动后检查话题rostopic list | grep -E scan|odom rostopic hz /scan rostopic echo /odom -n1/scan有稳定频率RPLIDAR A2 一般 8~10Hz、/odom有位置和速度说明底层通了。如果/scan没有数据先查雷达串口是不是被占用、波特率对不对如果/odom不动检查 Kobuki 的 USB 线和供电。这一步是整个导航系统的地基地基不稳后面建图全是玄学。2.3 用键盘遥控验证运动学在建图之前用遥控确认底盘能按预期方向走能省掉后面大量「地图歪了是不是雷达装歪了」的排查。roslaunch turtlebot_teleop keyboard_teleop.launch按 i 前进、j 左转、l 右转、k 停。重点看两件事前进时 RViz 里机器人朝向和实际是否一致原地转 360 度后/odom的角度是否回到接近 0。如果转一圈角度差很多说明轮距参数或编码器标定有问题这个误差会直接让建图闭环失败。Turtlebot2 的默认轮距在kobuki_node的参数里实测偏差大时可以改wheel_separation做补偿。3. SLAM 建图两条路自动建图与手动建图的取舍和实操3.1 gmapping 自动建图的启动与参数自动建图指的是机器人自己边走边建常见做法是 gmapping 配合一个探索策略或者干脆手动遥控走一圈但用 gmapping 实时出图。标题里的「自动定位建图」在 Turtlebot2 上最稳的组合就是 gmapping它基于粒子滤波2D 激光下成熟度高对算力要求低。# 终端 1底盘 roslaunch turtlebot_bringup minimal.launch # 终端 2雷达 roslaunch rplidar_ros rplidar.launch # 终端 3gmapping 建图 roslaunch turtlebot_navigation gmapping_demo.launch # 终端 4RViz 可视化 roslaunch turtlebot_rviz_launchers view_navigation.launch启动后 RViz 里 Fixed Frame 设为map添加 LaserScan 和 Map 显示。遥控小车慢慢走地图会实时生长。gmapping 的关键参数在gmapping_demo.launch引用的配置文件里几个必须理解的参数默认值作用调整建议particles30粒子数地图糊就加到 50~80算力够再往上delta0.05地图分辨率(m)室内 0.05 够用想更细改 0.025 但内存翻倍linearUpdate1.0走多远更新一次走太快地图会断可降到 0.5angularUpdate0.5转多少弧度更新转角大时降到 0.3maxUrange5.0激光有效距离按雷达实际量程设设太大引入噪声xmin/ymin/xmax/ymax±10地图边界按场地大小设太小地图会被截断particles和delta是最影响体验的两个。粒子太少机器人快速转弯时地图会出现重影分辨率太高老笔记本跑几分钟就卡。我一般先用默认跑一圈看地图质量再决定要不要加粒子。3.2 手动建图的适用场景与操作差异手动建图不是另一套算法而是指人全程遥控、不依赖自动探索gmapping 或 cartographer 只负责把激光和里程计拼成地图。它和自动建图的区别在「谁决定往哪走」自动建图靠探索算法如 frontier exploration找未知区域手动建图靠人判断。什么时候用手动场地小、结构简单、你熟悉环境时手动走一圈十分钟搞定比调探索算法快得多。自动探索在开阔大场地有优势但 Turtlebot2 算力有限探索策略容易在原地打转或卡在门口。标题里把两者并列实际工程里我建议第一次建图手动走把地图质量做扎实需要反复建图的场景再上自动探索。手动建图的操作要点走慢转弯慢尽量让激光扫到房间每个角落避免原地快速旋转那会让 gmapping 的粒子权重剧烈波动走「回环」路线从起点出发绕一圈回到起点让算法有机会闭合回环修正累积误差。建完图保存# 建图完成后保存地图会生成 .pgm 和 .yaml 两个文件 rosrun map_server map_saver -f ~/maps/my_room.yaml里记录了分辨率、原点、阈值.pgm是灰度图。这两个文件后面定位和导航都要用别弄丢。保存前最好在 RViz 里确认地图没有明显重影和断裂有的话重新走一遍比后期修图省事。3.3 建图质量的三个判断标准地图建完怎么判断能不能用我一般看三点。第一墙壁是不是直线如果墙是波浪形说明里程计或雷达安装有偏差。第二回环处有没有明显错位走回起点时地图上的墙应该重合错位大说明回环没闭合。第三空旷区域有没有「鬼影」即不该有障碍的地方出现黑点通常是雷达噪声或动态物体人走动留下的。这三点里回环错位最致命它会让后面的 AMCL 定位在错位区域反复跳变。遇到回环错位先检查轮距标定再考虑降低linearUpdate让更新更密。如果场地大gmapping 本身回环能力有限可以换 cartographer但它配置更复杂Turtlebot2 上要额外装cartographer_ros并调 lua 配置新手先用 gmapping 把流程跑通。4. 定位与路径规划AMCL、move_base 和算法切换怎么落地4.1 AMCL 定位把机器人放进已有地图建好的地图是静态的机器人每次启动都要知道自己在地图的哪个位置这就是 AMCL自适应蒙特卡洛定位干的活。它用粒子滤波把激光和地图匹配输出map到odom的变换。# 终端 1底盘 roslaunch turtlebot_bringup minimal.launch # 终端 2雷达 roslaunch rplidar_ros rplidar.launch # 终端 3加载地图 AMCL move_base roslaunch turtlebot_navigation amcl_demo.launch map_file:/home/user/maps/my_room.yaml # 终端 4RViz roslaunch turtlebot_rviz_launchers view_navigation.launch启动后 RViz 里机器人可能不在正确位置用「2D Pose Estimate」在地图上点一下初始位置和朝向AMCL 的粒子会收敛。AMCL 的关键参数参数默认值作用调整建议min_particles500最少粒子定位慢就加到 1000max_particles2000最多粒子算力够可到 5000laser_max_range5.0激光最大距离按雷达设设错定位会飘update_min_d0.2走多远更新默认够用update_min_a0.5转多少更新默认够用odom_alpha1~40.2里程计噪声模型定位跳变时调这几个odom_alpha系列是血泪参数。Turtlebot2 的里程计在打滑地面比如地毯上误差大如果定位频繁跳变把这几个值调大让 AMCL 更信任激光而不是里程计。反过来如果激光环境特征少长走廊调小让里程计多起作用。4.2 move_base 的全局与局部规划器定点导航靠 move_base它分全局规划和局部规划两层。全局规划器负责从当前位置到目标点算一条路径局部规划器负责沿着路径走并实时避障。Turtlebot2 默认用navfn做全局、base_local_planner或dwa_local_planner做局部。# 在 amcl_demo 启动后用 RViz 的 2D Nav Goal 点目标点 # 或者用命令行发目标 rostopic pub /move_base_simple/goal geometry_msgs/PoseStamped \ {header: {frame_id: map}, pose: {position: {x: 2.0, y: 1.0, z: 0}, orientation: {w: 1.0}}}move_base 的核心参数分三块代价地图、全局规划、局部规划。代价地图的inflation_radius决定障碍物膨胀多大设太小机器人贴着墙走容易刮蹭设太大窄门过不去。Turtlebot2 半径约 0.18minflation_radius一般设 0.3~0.5。局部规划器dwa_local_planner的参数里max_vel_x、min_vel_x、max_rot_vel决定速度上限acc_lim_x、acc_lim_theta决定加速度。Turtlebot2 底盘不快max_vel_x设 0.3~0.5 m/s 比较稳设到 0.8 以上转弯容易甩。xy_goal_tolerance和yaw_goal_tolerance决定到点多近算到达设太小机器人会在目标点附近反复微调设 0.1m 和 0.1rad 比较合适。4.3 算法切换全局规划器的替换与对比标题里的「算法切换」在 move_base 里就是换base_global_planner参数。默认 navfn 用的是 Dijkstra 变种换成global_planner可以选 A*换成carrot_planner或navfn各有适用场景。!-- 在 move_base 的 launch 里改全局规划器 -- param namebase_global_planner valueglobal_planner/GlobalPlanner/ param nameGlobalPlanner/use_dijkstra valuefalse/ !-- false 即用 A* -- param nameGlobalPlanner/use_grid_path valuetrue/A* 和 Dijkstra 的区别Dijkstra 向所有方向均匀扩展保证最短但慢A* 带启发式朝目标方向优先扩展快但路径不一定最短。室内小场景两者差别不大大场景 A* 明显快。use_grid_path为 true 时路径贴栅格为 false 时路径更平滑但可能贴障碍。局部规划器也可以换teb_local_planner在窄通道和动态障碍下比 DWA 好但它参数多、调参成本高。我的建议是先用 DWA 把定点导航跑通遇到窄通道过不去再考虑 TEB。换规划器不是越新越好是看场景开阔场地 DWA 够用复杂动态环境才值得上 TEB。5. 固定线路巡航与参数分析把导航做成可重复的任务5.1 固定线路巡航的实现方式固定线路巡航指的是机器人沿预设路径反复走比如巡检、送物。实现上有两种常见做法。第一种是记录一串航点用 move_base 依次发目标点到点后发下一个。第二种是录制一条路径用follow_waypoints或自己写节点沿路径跟踪。航点方式简单可靠适合路径由直线段组成的场景#!/usr/bin/env python import rospy import actionlib from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal # 定义巡航航点坐标基于 map 坐标系 waypoints [ (1.0, 0.0, 0.0), # x, y, yaw (2.0, 1.0, 1.57), (1.0, 2.0, 3.14), (0.0, 1.0, -1.57), ] def send_goal(x, y, yaw): client actionlib.SimpleActionClient(move_base, MoveBaseAction) client.wait_for_server() goal MoveBaseGoal() goal.target_pose.header.frame_id map goal.target_pose.header.stamp rospy.Time.now() goal.target_pose.pose.position.x x goal.target_pose.pose.position.y y # yaw 转四元数这里简化处理实际用 tf.transformations goal.target_pose.pose.orientation.z yaw goal.target_pose.pose.orientation.w 1.0 client.send_goal(goal) client.wait_for_result() return client.get_state() if __name__ __main__: rospy.init_node(patrol) while not rospy.is_shutdown(): for wp in waypoints: send_goal(*wp)这段代码的逻辑是循环遍历航点每个航点用 move_base 的 action 接口发送等到达再发下一个。wait_for_result会阻塞直到成功或失败实际用的时候要加超时和失败重试否则一个点卡住整个巡航就停了。航点坐标要在建好的地图上量RViz 里用 Publish Point 工具点一下能看到坐标。5.2 参数分析的三个层次标题里的「参数分析」不是把所有参数列一遍而是知道哪些参数在什么场景下要动。我把它分三层感知层、规划层、控制层。感知层是雷达和里程计参数在驱动包里一般不动除非换硬件。规划层是 move_base 的代价地图和规划器参数这是调参主战场。控制层是底盘的速度和加速度限制在base_local_planner或dwa的参数里。调参的顺序应该是先调代价地图让机器人不撞不卡再调局部规划器让运动平滑最后调全局规划器优化路径。反过来先调全局规划器局部走不顺白搭。每次只改一个参数改完跑同一段路对比别一次改五个然后不知道哪个起了作用。5.3 用 rosbag 做参数回归调参最怕「改完感觉好了换个场景又不行」。我一般用 rosbag 录一段典型场景的激光和里程计数据调参时回放保证每次对比的输入一致。# 录一段包含激光、里程计、tf 的数据 rosbag record -O nav_test.bag /scan /odom /tf /tf_static # 回放时用 --clock 让节点用 bag 里的时间 rosbag play nav_test.bag --clock回放时 move_base 和 AMCL 照常跑你改参数重启节点输入数据不变输出差异就是参数造成的。这个方法能把「玄学调参」变成可对比的实验。注意回放时要把真实硬件节点关掉否则话题会冲突。rosbag 文件别录太长几十秒的典型路段就够太长回放慢、占内存。6. 避坑与排查Turtlebot2 导航最常见的五个翻车现场6.1 建图时地图重影、墙壁变厚现象RViz 里同一面墙出现两条线或者墙明显变厚。原因通常是里程计标定不准机器人转一圈实际角度和/odom报告的角度对不上gmapping 把激光拼歪了。解决先做里程计标定让机器人原地转 10 圈对比/odom累计角度和实际角度差得多就改wheel_separation。另外检查雷达安装是否水平、是否松动雷达倾斜也会让扫描面偏移。6.2 AMCL 定位粒子发散、机器人位置乱跳现象RViz 里粒子云散成一片机器人位置在地图上乱跳。原因可能是初始位置没给对或者laser_max_range设得比雷达实际量程大导致 AMCL 拿无效数据匹配。解决先用 2D Pose Estimate 给一个靠谱的初始位置检查 AMCL 配置里的laser_max_range和laser_min_range是否和雷达一致如果环境特征少长走廊适当调大odom_alpha让里程计多参与。6.3 move_base 规划失败、机器人原地不动现象发了目标点RViz 里路径不出现或者出现一下又消失机器人不动。原因常见的是代价地图里目标点被障碍物膨胀覆盖或者全局规划器找不到路径。解决看 RViz 里 costmap 的膨胀层如果目标点在膨胀区内换个点检查inflation_radius是不是设太大把通道堵死了看 move_base 的日志有没有Failed to find a plan有的话检查地图和机器人半径参数。6.4 机器人贴着墙走、窄门过不去现象路径规划出来贴着墙或者到窄门就停。原因是inflation_radius和机器人半径不匹配。Turtlebot2 半径约 0.18minflation_radius设 0.5 时实际可通行宽度要大于 1m 才过得去。解决量一下最窄通道宽度反推inflation_radius上限如果通道实在窄考虑换 TEB 局部规划器它对窄通道处理更好或者手动在地图上把窄门处的障碍物擦掉一点不推荐但应急可用。6.5 巡航到某个点反复微调、不结束现象机器人到了目标点附近来回小幅移动move_base 不返回成功。原因是xy_goal_tolerance和yaw_goal_tolerance设太小机器人精度达不到。解决把xy_goal_tolerance放到 0.1~0.15myaw_goal_tolerance放到 0.1~0.2rad检查底盘最小速度如果min_vel_x太大机器人没法慢速微调把它设到 0 或负值允许后退微调。7. 进阶技巧用拓扑航点加动态重规划做鲁棒巡航固定航点巡航有个硬伤环境一变有人走动、椅子挪位move_base 可能卡住整个巡航停摆。我的做法是在航点之间加一层「拓扑重规划」不要求严格沿直线走而是把每个航点当成一个区域到达区域内就算成功区域之间用 move_base 自由规划。具体实现是给每个航点加一个容差半径用move_base的xy_goal_tolerance配合自定义节点判断是否进入区域。更进一步可以在巡航节点里监听 move_base 的状态如果某个航点连续失败两次跳过它继续下一个并记录日志等巡航一圈回来再重试。这样单点故障不会拖垮整条线路。# 带容差和失败跳过的巡航逻辑片段 def patrol_with_tolerance(waypoints, tolerance0.3, max_retry2): idx 0 retry {} while not rospy.is_shutdown(): wp waypoints[idx] state send_goal(*wp) if state 3: # SUCCEEDED retry[idx] 0 idx (idx 1) % len(waypoints) else: retry[idx] retry.get(idx, 0) 1 if retry[idx] max_retry: rospy.logwarn(waypoint %d failed, skip, idx) idx (idx 1) % len(waypoints) else: rospy.sleep(1.0) # 等一秒重试这段逻辑的关键是retry字典记录每个航点的失败次数超过阈值就跳过避免死磕。tolerance在实际中通过 move_base 的xy_goal_tolerance参数生效这里只是示意。真实部署时还要加一个「全局超时」比如一圈超过 10 分钟就重置状态防止某个点反复重试把时间耗光。验证这套逻辑我一般先在 Gazebo 里跑仿真用turtlebot_gazebo起一个带障碍物的世界把航点设在障碍物两侧人为在 RViz 里加障碍物模拟动态变化看巡航能不能跳过失败点继续走。仿真过了再上真机真机上先用低速跑确认逻辑对了再提速。最后说个习惯每次改完参数或逻辑我都会把当时的 launch 文件、参数 yaml 和 rosbag 一起归档命名带日期和场景。导航调参是个反复的过程没有后悔药只有存档。下次遇到类似问题翻出当时的配置对比比从头调快得多。希望帮到你。本文还有配套的精品资源点击获取