
机器人导航学习历程里全局路径规划解决的是“从A点到B点走哪条路”而真正让机器人动起来、绕开突然冒出来的障碍物、稳稳停到目标点面前的是local planner。这篇文章是系列第四篇我把自己在DWA、TEB两种主流局部规划器上踩过的坑、调过的参数、从仿真搬到实物的完整过程整理出来希望能帮正在搞机器人导航的朋友少走弯路。1. 全局规划和局部规划别再把分工搞混很多刚接触导航的朋友最容易犯的错就是以为有了全局路径规划就完事了机器人会沿着那条规划出来的线一路走到底。实际根本不是这样。全局规划器拿到的是一张静态地图它规划出的路径是“假设周围环境完全不变”的前提下算出来的。可真实世界里过道上突然有人、墙角多了纸箱、桌子的位置被移动过这些都没法提前写进地图里。local planner干的事就是在全局路径的引导下只盯着传感器实时看到的那一小片区域以十几赫兹甚至更高的频率滚动地给机器人下发速度指令。它可能只“看”前方几米的局部代价地图但正是这个“只看眼前”的家伙决定了机器人会不会撞墙、能不能拐过窄门、会不会在目标点前原地打转。你可以把全局规划理解成手机地图给的路线local planner则是你开车时的实时判断。地图告诉你“前方第三个路口右转”但真正让你避开突然窜出来的电瓶车、在路边停车时一把倒进车位的是你眼睛看到的实际情况和手脚的瞬时操作。没有这个“实时判断”机器人就是盲人开车哪怕手里拿着再精确的全局地图也没用。1.1 从一次真实卡顿说起我印象最深的一次是在实验室走廊里给机器人导航。全局路径规划得好好的一条直线从房间A到房间B中间经过一扇门。结果机器人走到门框附近时开始左右来回扭动速度一下掉到几乎为零然后突然急加速往前冲差点撞上墙。查了半天问题就出在local planner上。门框在局部代价地图里被同时算成了左右两个障碍物规划器在每个控制周期里反复权衡“从左绕还是从右绕”打分上微小的波动就会导致选择来回切换表现出来就是机器人在门口“抽搐”。这让我彻底明白导航系统里真正考验功力的往往是local planner的调参而不是全局规划器的配置。1.2 数据流和接口关系先理顺再动手在动手调参之前我强烈建议先把move_base框架下的数据流捋一遍。简单来说是这样的传感器激光雷达为主发布/scan数据同时里程计发布/odom定位模块通常是AMCL发布机器人在地图中的位姿/amcl_pose。这几个话题喂给move_base后维护两张代价地图global_costmap和local_costmap。全局规划器基于全局代价地图算出一条路径作为参考航线发布到move_base/GlobalPlanner相关话题。local planner订阅这条全局路径结合局部代价地图上的实时障碍信息以20Hz左右的频率计算速度指令发布到/cmd_vel。底盘驱动节点订阅/cmd_vel控制电机转动。这套链路里local planner就是最下游的“大脑”。它接收的信息有一半是参考性的全局路径另一半是实时性的局部代价地图输出的却是直接作用于硬件的速度指令。这就意味着上游任何一点的误差——雷达标定偏差、里程计漂移、定位抖动——都会在local planner这级被放大反映到机器人实际运动轨迹上。2. 主流 local planner 选型DWA、TEB 和其他ROS导航栈里常用的local planner有好几种我实际深度用过的是DWADynamic Window Approach和TEBTime Elastic Band。这两种思路完全不同选型时不能盲目跟风得看你的机器人底盘类型、应用场景和算力资源。2.1 DWA原理简单、计算量小适合入门DWA的核心思想很直白在每个控制周期里根据机器人当前速度构造一个“动态窗口”——这个窗口内的速度组合线速度v和角速度ω都是机器人能在下一个周期内实际达到的。然后在这个窗口内采样若干组(v, ω)对每一组模拟出一条短时间内的运动轨迹最后用评分函数给每条轨迹打分选得分最高的执行。DWA的评分主要看三项轨迹终点方向跟全局路径的贴合程度路径贴合度、轨迹终点朝向与目标点的夹角目标朝向度、轨迹是否会碰到障碍物障碍物距离。这三项分别对应三个权重参数path_distance_bias、goal_distance_bias、occdist_scale。DWA的优点非常明显计算量小在低配工控机上也能跑得很流畅参数少核心就十几个入门阶段很容易上手对差速驱动机器人特别友好两轮差速和全向底盘的场景下表现稳定。缺点同样明显它本质上是短视的“贪心算法”只考虑接下来几秒的运动遇到复杂地形比如U形障碍、窄缝通行容易陷入局部最优表现就是机器人进退两难。2.2 TEB能倒车、会绕路灵活但调参费劲TEB的思路完全不一样。它把机器人从当前位置到目标点或一段全局路径之间的轨迹想象成一根有弹性的带子。障碍物给这根带子施加“斥力”目标点和路径末端施加“拉力”时间最优性施加“收紧力”。每个控制周期TEB都在优化这条带子的形状让它在避开障碍物的同时尽量平滑、尽量快速。TEB最大的能力优势是支持倒车、支持阿克曼底盘的最小转弯半径约束、能够在路径被阻断时主动修改轨迹而不是死板地跟随全局路径。如果你的机器人是阿克曼转向像汽车一样前轮转向或者需要在窄小空间里反复腾挪TEB几乎是首选。但TEB的代价是参数爆炸。光move_base_params和TebLocalPlannerROS相关的可调参数就有四五十个权重项包括障碍物权重、动态障碍物权重、时间权重、运动学前向驱动权重、加速度惩罚权重等等。调TEB就像调一台光学相机拧任何一个旋钮都可能牵动全局效果新手很容易陷入调一天都没调明白的困境。2.3 其他方案了解即可按需选择除了DWA和TEB还有几个值得了解的方案MPC模型预测控制每个周期求解一个带约束的最优控制问题效果最好但计算量也最大通常需要配合较好的CPU才能在嵌入式平台上实时运行。如果做科研、追求极限控制精度可以研究。Pure Pursuit纯追踪最朴素的路径跟踪算法不避障只负责“沿着给定路径走”。常用于路径已经明确、动态障碍物很少的场景比如仓储AGV的轨道跟踪。Follow the Gap间距跟随原理是找传感器数据中最大的无障碍间隙朝间隙中点走。适合极度追求响应速度的避障场景但路径平滑性很差。2.4 选型对比表按场景对号入座项目DWATEBMPC底盘适配差速/全向最佳阿克曼勉强差速/阿克曼都支持能倒车通用但需精确模型计算开销低中高参数数量少15个左右核心多40个以上视求解器而定复杂地形通过能力一般易陷入局部最优强可主动改轨迹强但依赖建模精度动态障碍物避让靠costmap实时刷新响应中等支持动态障碍物预测响应好支持预测但求解慢入门推荐度高中低我个人给的建议是如果是第一次做机器人导航或者底盘是差速驱动优先上DWA。先把DWA调明白理解清楚local planner的思考逻辑再换TEB深入了解参数之间的关系会轻松得多。我见过太多人一上来就上TEB结果被几十个参数搞得怀疑人生反而连基础导航都没跑通。3. local planner 的“眼睛”局部代价地图local planner到底靠什么感知障碍物答案就是局部代价地图local costmap。很多人调local planner只盯着速度参数却忽略了代价地图这层基础导致调了半天还是撞墙。3.1 三层机制静态层、障碍层、膨胀层ROS的costmap插件架构把地图信息分成了好几层跟local planner直接相关的主要是三层静态层Static Layer加载预先建好的静态地图主要给全局代价地图用。local costmap里一般也会开启但作用有限因为局部实时性主要靠传感器层。障碍层Obstacle Layer实时接收激光雷达/深度相机数据把传感器检测到的障碍物点投影到代价地图栅格上。这一层是关键它决定了机器人“当下看到”的障碍物在哪。障碍层的更新频率直接决定了local planner对动态障碍物的响应速度。update_frequency和publish_frequency我一般分别设成5.0和3.0单位Hz太低了会漏掉快速移动的障碍物太高了浪费CPU。膨胀层Inflation Layer把障碍物按照机器人半径进行向外膨胀膨胀出的区域标记出“危险程度”。离障碍物越近代价值越高。这一层的inflation_radius和cost_scaling_factor直接决定了机器人在离障碍物多远的距离开始“害怕”。inflation_radius设置为机器人内切半径的2倍左右比较常见。cost_scaling_factor则是控制代价衰减的快慢取值越小代价衰减越慢机器人越早开始避让路径更安全但绕的路也更多取值越大机器人越敢贴着障碍物走路径更紧凑但风险更高。3.2 传感器接入和雷达安装位置local planner的实时避障能力完全取决于传感器数据的质量。我踩过的一个大坑是激光雷达安装高度和安装位置没有跟机器人的footprint对齐。激光雷达数据本身只是一个二维平面扫描。如果雷达安装位置跟规划时设定的机器人中心不重合就会出现运动方向跟障碍物位置错位的情况——明明激光扫描到左边有障碍物机器人却已经向左转过去了雷达数据才更新到位。正确的做法是先量准雷达相对于机器人base_link的坐标偏移在配置文件中通过laser_scan插件的frame_id和TF树关系准确描述。我建议把raytrace_footprint和footprint配置得跟真实底盘外形一致特别是对于有轮子外凸、车头突出的机器人如果只是简单用矩形包围盒窄通道里很容易发生“雷达说能过实际车身刮到”的情况。3.3 膨胀半径、机器人半径和速度的三角关系这里有一个容易忽略的联动关系膨胀半径和机器人最大速度必须匹配。原因很简单local planner是周期性规划的每个周期之间机器人还在运动。假设控制周期是0.2秒机器人最大线速度是1m/s那一个周期内机器人最多前进0.2米。如果膨胀半径只比障碍物外扩了0.1米那机器人很可能在规划器“意识到”危险之前就已经贴上障碍物了。我一般参照这个经验公式来设定膨胀半径 ≥ 机器人半径 最大线速度 × 控制周期 × 1.5。预留1.5倍的裕量是为了给刹车和加速度留出缓冲。把这个关系理顺后再去调local planner你会发现很多诡异的问题比如为什么老是擦到墙角不用调速度参数就解决了。4. 实操从仿真到实物的参数调试记录这一节我把实际调参的过程原原本本写出来先讲DWA再讲TEB重点是每个参数背后的逻辑和调参的先后顺序。4.1 DWA 参数逐项拆解照着抄也能跑下面是一份我调好的差速底盘DWA配置底盘最大线速度1.0m/s最大角速度1.5rad/s。放在这里当模板DWAPlannerROS: # 速度限制 max_vel_x: 0.8 # 最大前进线速度 m/s min_vel_x: -0.2 # 最小线速度允许轻微后退 max_vel_y: 0.0 # 差速底盘没有y向速度 min_vel_y: 0.0 max_vel_trans: 0.8 # 最大平移速度 min_vel_trans: 0.05 # 最小平移速度低于此速度停车 max_vel_theta: 1.2 # 最大旋转角速度 rad/s min_vel_theta: 0.3 # 最小旋转角速度低于此值不旋转 acc_lim_x: 0.5 # x向加速度限制 m/s^2 acc_lim_y: 0.0 acc_lim_theta: 1.0 # 角加速度限制 rad/s^2 # 目标容差 xy_goal_tolerance: 0.15 # 到达目标点的xy距离容差 m yaw_goal_tolerance: 0.2 # 到达目标点的朝向角容差 rad latch_xy_goal_tolerance: true # 前瞻与仿真参数 sim_time: 2.0 # 轨迹模拟时长 s sim_granularity: 0.025 # 轨迹模拟步长 m vx_samples: 30 # 线速度采样数量 vy_samples: 0 vyth_samples: 40 # 角速度采样数量 # 评分权重 path_distance_bias: 32.0 # 贴合全局路径的权重 goal_distance_bias: 20.0 # 朝向目标点的权重 occdist_scale: 0.2 # 避障权重越大越保守 heading_lookahead: 0.6 # 计算朝向打分时的前瞻距离 m oscillation_reset_dist: 0.05 # 防抖动重置距离 m这里面几个参数的含义我详细解释一下sim_time是轨迹模拟的时长。它决定了规划器“往前看多久”。设太短小于1.0机器人只顾眼前容易在障碍物附近急转弯设太长大于3.0计算量增大而且远期的轨迹跟实际偏差大。我实测2.0秒左右对室内机器人是个不错的平衡点。acc_lim_x和acc_lim_theta是加速度限制这两个参数不仅限制规划的加速度还会影响实际速度的变化率。它们必须跟底盘驱动器的能力匹配设大了机器人起步刹车太猛设小了机器人反应迟钝遇到突然出现的障碍物刹不住。我给的0.5m/s²和1.0rad/s²适用于大多数室内底盘如果你用的是大型底盘需要根据电机和减速箱的加速能力重新标定。occdist_scale是最容易让人困惑的参数。它越大机器人越远离障碍物但路径会绕更远它越小机器人越敢贴边走但容易蹭到障碍物边缘。我建议新手从0.1开始往上加观察机器人过窄通道的表现。0.2是典型值如果发现在宽阔场地里机器人也走得很“怂”就是它偏大了。4.2 调参顺序先速度后权重一步一步来我刚学local planner时有个坏毛病一上来就同时改五六个参数结果出了问题根本不知道是哪一步改坏了。后来总结出一套相对靠谱的调参顺序第一步先定速度上限和加速度限制。这些是物理约束应该由底盘能力决定不是调出来的。把max_vel_x先设成底盘标称最大速度的80%加速度设成底盘能承受的中等值。这样保证后面调权重时机器人有足够的“行动范围”来展示问题。第二步调sim_time和采样数。把sim_time从1.5到2.5之间试一遍观察机器人过窄通道、拐直角弯时的表现。采样数不足时速度空间覆盖不全机器人会表现出“犹豫不决”——要么选不到合适的路径要么轨迹很生硬。vx_samples30、vyth_samples40是我常用的起点。第三步调评分权重。先保证障碍物权重能有效避障occdist_scale0.15到0.3然后调path_distance_bias和goal_distance_bias的比例。这两个权重决定了一个关键行为当局部路径跟全局路径发生冲突时机器人是“忠于全局路线”还是“直奔目标”。比重越大越忠于全局路线比重越大越急于朝向目标点容易抄近路撞障碍物。我常用的比例是path : goal 1.6 : 1这样既能保证机器人沿着全局路径走又不会在目标点附近绕远。第四步跑真实场景微调。前面三步基本能在仿真里完成但真实环境的雷达噪声、地面打滑会导致参数需要小范围回调。回调时每次只改一个参数改动幅度不要超过原值的20%跑一圈看效果再决定下一步。4.3 TEB 关键参数调整记录TEB的参数多但核心其实是几组。我整理了一份调TEB时的重点参数记录速度与加速度组max_vel_x最大线速度、max_vel_theta最大角速度、acc_lim_x、acc_lim_theta。这组参数跟DWA一样是物理上限按底盘能力设。避障与膨胀组min_obstacle_dist离障碍物的最小距离默认0.2建议不小于机器人半径0.05、inflation_dist障碍物的影响范围即“多远就开始躲避”默认0.6、penalty_epsilon惩罚系数控制避障的“柔和度”值越大越安全路径越绕。权重组weight_obstacle障碍物权重默认100、weight_dynamic_obstacle动态障碍物权重默认50如果环境里人多这个要加大、weight_goal朝向目标点权重默认1、weight_time时间最优权重默认1决定机器人有多“急躁”。weight_time特别有意思它越大机器人越追求走快路径越短越激进设小一点机器人更稳重。我一般先设0.8跑一段时间再微调。运动学组min_turning_radius最小转弯半径阿克曼底盘必须设置、footprint_model明确机器人的轮廓模型支持点、圆、矩形、多边形。调TEB的过程比较痛苦因为权重之间的交互非常强。我的经验是用默认参数起底然后只调三个参数——min_obstacle_dist、inflation_dist、weight_obstacle——先把基础避障跑稳再去动其他权重。如果一开始就同时调七八个权重大概率会越调越乱。4.4 从仿真转到实物三个最容易出问题的地方我在Gazebo仿真里把导航调到自认为完美后信心满满地往实物机器人上一搬结果立刻翻车。教训非常深刻第一个差异是雷达数据的噪声。仿真里激光雷达是理想传感器数据平滑、精度高。真实雷达有测量噪声、有丢帧、有吸光材质造成的盲区。后果是局部代价地图里障碍物的边缘不稳定local planner在同一个障碍物边缘反复犹豫。解决办法是我降低障碍层的obstacle_range从5.0降到3.5让更远距离的读数不参与代价计算减少噪点干扰。第二个差异是里程计精度。仿真里里程计几乎是真值真实机器人的轮子打滑、编码器误差会让local planner“以为自己走过去了”实际并没有。后果是机器人不停重复调整位置甚至在目标点前转圈。解决办法是给机器人加陀螺仪融合里程计或者用更准确的里程计发布源。第三个差异是底盘的实际响应。仿真里的速度指令会被完美执行真实底盘有惯性、有响应延迟。acc_lim_*设得太乐观时真实机器人根本达不到规划器认为能到达的速度导致规划轨迹跟实际轨迹严重偏差。我的建议是实物调试时把仿真参数里的所有加速度限制降低30%把最大速度降低20%先跑一个保守版本确认稳定后再慢慢放开。5. 常见问题排查实录照着排错能省两天时间导航出问题的时候新手最容易陷入“怀疑人生”的状态改了这个参数那里又不行了。我把这一年多实际见到的local planner相关问题整理成速查表按现象排查效率很高。现象可能原因优先排查项处理建议机器人原地转圈、来回摆动目标点附近速度低导致打分扰动oscillation_reset_dist过小目标容差、最小速度增大oscillation_reset_dist到0.1检查xy_goal_tolerance贴着障碍物走、车身剐蹭膨胀半径太小footprint设置偏小inflation_radius、footprint按“膨胀半径≥半径速度×周期×1.5”重设窄通道里反复犹豫sim_time太短采样数不足sim_time、采样数sim_time加到2.0以上vx/vyth_samples分别至少30/40机器人突然急停加速度限制过小、雷达检测到局部代价高的区域acc_lim_x、costmap障碍层检查局部代价地图里是否有瞬态噪点规划路径频繁跳动全局路径不平滑local planner前瞻距离太短heading_lookahead增大前瞻距离到0.5以上TEB倒车过度行进中频繁后退weight_kinematics_forward_drive太低前向驱动权重调大该项限制倒车行为到目标点后姿态不正yaw_goal_tolerance过小或旋转速度太低朝向容差、最小角速度适当放宽yaw_goal_tolerance到0.25.1 机器人原地旋转的深层原因关于“原地旋转”这个高频问题我再多说一句。它往往不是local planner一个环节的错而是整条链路的问题xy_goal_tolerance设得太小比如小于定位精度机器人到了目标点附近AMCL认为位姿偏差超差于是规划出旋转修正路径修正完定位又更新又超差又旋转形成死循环。这种情况下先检查定位模块的位姿协方差再检查目标容差跟定位精度是否匹配。室内机器人定位精度一般在±5cm左右xy_goal_tolerance设0.1以下就是在逼机器人走单摆。老老实实设0.15到0.2绝大多数目标点抖动问题直接消失。5.2 贴墙走的深层原因如果机器人总是贴着墙边走看着都吓人问题多半不在local planner里而在代价地图的膨胀参数上。inflation_radius设得比机器人半径还小的时候算法会认为“贴着墙沿走收益最高”因为它评分时更看重贴合全局路径而不是避障。调试技巧把局部代价地图的发布频率调高用RViz实时查看膨胀层显示效果。直观确认“红色危险区域”是不是完全覆盖了机器人footprint的边缘。如果没有就是膨胀半径参数的问题先改这个再考虑调速度权重。5.3 仿真转实物后频繁重规划这个问题非常典型。仿真环境干净雷达噪声小全局路径一旦规划好机器人沿着走就行。到了实物雷达噪点导致的局部代价波动会让local planner对“当前位置是否偏航”的判断高频跳动于是频繁上报路径不可行触发move_base的重新规划。对策有两个方向。一是在TrajectoryPlannerROS或DWAPlannerROS里检查有没有“重规划触发差值”类参数在move_base层面可通过planner_patience和conservative_reset_dist等参数允许一定时长内规划失败不触发全局重规划。二是降低局部代价地图的obstacle_range减少远处噪点的影响。实际调试中后者通常立竿见影。6. 从导航到抓取local planner 的质量决定下游一切最近很火的机器人导航仿真抓取实物本质上就是导航和机械臂操作的衔接问题。很多人以为导航只负责“把机器人运到机械臂能抓到的地方”然后机械臂自己干活就行。这个理解太天真了。6.1 停不准抓取就全是白搭机械臂的抓取往往依赖机器人底座的精确停位。你规划好的机械臂轨迹全部基于“机器人底座在某个精确位姿”的假设。可实际执行时local planner到达目标点的误差可能有好几厘米底座朝向也可能偏个几度。更麻烦的是如果local planner在目标点附近反复调整就是我前面说的原地摆动的场景机器人停稳时跟规划位置偏差更大机械臂的逆解根本算不出有效的抓取位姿。解决思路是在导航任务里把目标点拆成两步第一步导航到目标点前50cm的“预备点”第二步以较低速度精确靠近目标点并调整朝向。预备点用常规的DWA参数跑精确靠近阶段可以临时降低速度上限、收紧目标容差避免大范围调整让机器人以一种“小心翼翼”的方式走完最后一截。6.2 局部路径平滑度对抓取稳定性的影响机械臂抓取时如果底座还在左右晃动——哪怕只有几毫米——末端执行器的位置误差会被机械臂的“杠杆效应”放大。local planner输出的轨迹如果不够平滑频繁加减速机器人到达目标点后的残留抖动会很严重。解决办法有两个层面的在导航层面适当调大weight_kinematics_forward_driveTEB或增加路径平滑相关的权重让机器人“走得稳”在系统层面让机械臂的视觉伺服模块等机器人底座完全静止后再触发抓取通过监测/cmd_vel和实际速度为零持续1秒钟以上来触发“已停稳”信号。仿真里Gazebo的物理引擎对地面摩擦和惯量的模拟相对理想实物抓取时底座晃动的幅度往往比仿真里大不少。我的经验是在实物上做抓取时导航速度上限只开仿真里的60%左右并且一定要给机械臂的视觉识别留足稳定时间。6.3 仿真和实物的联合调试心得那一段把导航和抓取打通的日子是我收获最大的一段。总结下来仿真最大的价值在于帮我把整套流程的坐标系关系、话题通信、时序逻辑跑通。真正决定项目成败的永远是实物联调后的那几轮微调。具体来说仿真里可以放心大胆地把local planner参数来回折腾走极端值看看效果。但一旦决定上实物我就用“备份-调参-回退”的方式每次实调前把当前参数备份好一次只调一个方向跑完记录现象不好就回退好就继续往下试。这个方法虽然土但比“一把梭”高效太多。另外一定要把仿真和实物的差异参数单独拆分出来管理。我习惯用一套参数文件加条件加载的方式速度、加速度、膨胀半径这类受物理环境影响大的参数分别维护sim_params.yaml和real_params.yaml避免每次切换环境时遗漏某个关键参数导致安全事故。7. 最后再说两句实在话local planner确实是整个导航系统里最让我“又爱又恨”的部分。爱的是它直观——调参数立刻能在轨迹上看到变化恨的是它是一个高度耦合的系统任何上游的问题都能在这里暴露出来。我个人在实际操作中有两点体会最深。第一遇到问题先看数据再看参数。打开RViz把局部代价地图、全局路径、机器人的速度指令全部显示出来观察个一两分钟往往就能定位问题比闷头改参数快十倍。第二参数不是越“跑得快”越好而是要匹配你的硬件能力和场景需求。一台实验室里慢悠悠走得稳的机器人比一个在走廊里能跑到1.5m/s却动不动撞墙的机器人强太多。这个系列后续我还会写一写导航跟上层任务调度怎么衔接、多传感器融合定位的话题。如果你在调local planner时遇到这里没讲到的问题建议先把本章的参数表挨个在自己机器人上试一遍把每个参数的“手感”摸清楚——这比任何教程都有用。