☰
TEB局部规划器调参与排障:从图优化原理到典型症状排查
2026/11/3 23:22:29 网站建设 项目流程

局部运动规划器TEB(Timed Elastic Band)在ROS里是个很特殊的存在:它不是靠采样、打分、挑最优那种"试出来"的规划方式,而是把一条由全局规划器给出的粗路径,连同时间信息一起当成一根有弹性的橡皮筋,用图优化一次性揉成一条满足运动学、避障和时间要求的可执行轨迹。很多人第一次接触它,是被它优雅的建模思路吸引;真正用起来以后,又会被那一长串权重参数和偶尔冒出来的"原地抖""贴墙走""规划不出来"折磨得够呛。我对TEB的理解,是在几个不同地盘和底盘上反复调、反复翻车之后慢慢拼起来的,这篇就按我自己的理解顺序拆开讲,从它到底在算什么,到参数之间的拉扯关系,再到几类典型症状的排查链路。适合已经能把TEB跑起来、但还不清楚它为什么这么动的人看,也适合刚接手一个别人调好的参数文件、不敢乱改的人看。

1. TEB不是在"找路",而是在"把一条粗线揉成可执行的轨迹"

先说清楚一件事:TEB不负责发现新路线。它的输入是一条全局路径(通常是A*或Dijkstra在一张静态地图上算出来的折线),它的输出是一条位姿序列,加上相邻位姿之间的时间间隔。它做的工作是在这条折线附近做局部变形,让结果满足机器人自身的运动学限制、满足与障碍物的最小距离、同时尽量短、尽量快。这个定位搞明白了,后面所有参数你都能知道它在影响哪一部分。

1.1 弹性带的数学形态:位姿序列加时间间隔

TEB把轨迹描述成两串东西:一串二维位姿(每个位姿是x、y、yaw),以及一串时间间隔ΔT。位姿序列决定"走哪儿",时间间隔决定"走多快"。这两串量被放进同一个优化图里一起求解,这就是它和普通路径规划最大的区别——它从一开始就是时空联合优化的,不是先算路径再算速度。

把时间间隔也当变量,好处很直接:在障碍物密集的地方,优化器会自动把对应的ΔT拉长(减速),在空旷直道上把ΔT压短(加速),不需要你手写一个速度规划器来插值。代价是优化维度变高了,而且时间变量没有上界约束的时候容易往奇怪的方向跑,所以后面才会有一堆跟速度、加速度相关的硬约束边。

我记得第一次看它的可视化输出,看到轨迹点上标着每个点的速度大小,还以为是后处理算出来的。实际上那些速度就是从相邻两个位姿的距离除以ΔT得到的,是优化结果的一部分。

1.2 优化器里到底放了哪些边

理解TEB最有效的方式,是把它的超图(hyper-graph)在脑子里过一遍。图里的节点是位姿和时间间隔,边就是各个代价项和约束项。我按自己的理解分三类:

类别典型的边作用属于
软约束(代价)障碍物距离、膨胀层、路径长度、总时间、途经点、动态障碍物优先生成"更好"的轨迹可调权重
硬约束(不等式)最大速度、最大角速度、最大线加速度、最大角加速度、非完整约束、转向半径不许越界权重极大或直接判不可行
结构约束相邻位姿之间的连接、首尾位姿固定保证轨迹连续性不可调

软约束之间是可以互相让步的,比如路径长度想短、障碍物想远,两者冲突时谁权重高听谁的。硬约束是"要么满足要么这条轨迹废掉",这就是为什么你的acc_lim_x设得太小、而max_vel_x设得太大的时候,规划器会直接告诉你找不到可行解——不是它不想,是这个组合在数学上就是矛盾的。

1.3 为什么选图优化而不是采样

DWA那类方法是在速度空间里采样一堆(v, ω)组合,往前推演一段,打分选最好的。它简单、实时性好,但缺点是短视:只看一两秒后的结果,容易掉进局部极小值,走那种贴着障碍物扭来扭去的路。

TEB的做法是把整条局部轨迹一起优化,所以它"看得更远"——理论上它能为了绕开后面一个障碍物,在前面就提前偏出去。这也是它在地盘窄、障碍物密集的场景里表现明显优于DWA的原因。但代价也很实在:图优化的计算量跟轨迹点数成正比,跟迭代次数成正比,如果局部costmap开很大、全局路径截得很长,单次规划耗时会从几毫秒涨到几十毫秒。我见过一次局部规划占了控制器周期一大半的情况,查下来就是max_samples设太大加上同伦类规划开着,等于每次都在算四五条路径。

2. 五个代价项的真实分工与相互拉扯

参数文件里那一排weight_*,不是各自独立的旋钮,它们是一组跷跷板。你动了一个,另外几个的效果就会变。我按实际影响从大到小讲。

2.1 weight_obstacle 和 weight_inflation 是两层结构,不要当一回事

很多人以为weight_obstacle越大越安全,于是直接拉到几百。结果机器人开始在通道里左右横跳,因为它对每一条边上的障碍物都过度敏感,一点点不对称就让它觉得"这边更险",于是往另一边躲,躲过去又觉得那边险。

要理解这两个参数,得先理解min_obstacle_dist和inflation_dist这两个距离阈值构成的双层结构:在min_obstacle_dist以内,代价是惩罚函数,越近涨得越快;在min_obstacle_dist到inflation_dist之间,是一个软性的"尽量别靠太近"的缓冲区;超出inflation_dist,基本不管。weight_obstacle控制的是第一层的陡峭程度,weight_inflation控制第二层的软推力。

我的经验值是:min_obstacle_dist按机器人实际半宽加个5到10厘米的安全余量设,inflation_dist比它大0.1到0.2米,weight_obstacle保持在几十的量级,weight_inflation保持很小(零点几甚至更小)。真正让机器人不贴墙的应该是costmap的膨胀层加上min_obstacle_dist的实际值,而不是把权重怼上去让它自己害怕。

2.2 weight_optimaltime 与速度约束的隐性冲突

weight_optimaltime控制"总时间"这一项的权重。调大它,轨迹会更倾向于走直线、走近路、加速通过。听起来挺好,但它的效果会被速度约束卡住:你把时间压得很狠,可max_vel_x就那么点,优化器只能把每个ΔT压到极小值,这时候轨迹点之间的位移相对于时间就"超速"了,速度约束边就会大量报错,最后要么退化成不可行,要么解出来的速度曲线在限幅后跟轨迹形状对不上,表现出来就是"轨迹是一条平滑曲线,机器人实际走的是折线"。

我一般的做法是weight_optimaltime保持默认的1左右,想快就老老实实调max_vel_x和acc_lim_x,让物理约束自己决定速度上限。想让它更"抄近路",我更倾向动路径长度那项的权重,而不是时间项。

2.3 weight_kinematics_forward_drive 决定机器人"愿不愿意倒车"

这个参数很有意思。非完整约束(weight_kinematics_nh)保证机器人只能沿着车头方向走,但它不区分往前还是往后——倒着走同样满足非完整约束。weight_kinematics_forward_drive就是用来惩罚"倒着走"的。

设得太小,机器人在狭窄处会疯狂倒车微调,看起来像在"倒库",视觉上很不自然,而且倒车时后方的传感器盲区风险高。设得太大,机器人在死胡同里就完全不愿意后退,宁可原地画圈。我通常把它设到一个中等偏上的值,让倒车成为"不得已才用"的手段,同时在狭窄通道场景里适当降低一点,允许它小幅度倒车调整姿态。

提示:这个参数和prefer_rotdir(偏好旋转方向)是两个东西,前者管平动方向偏好,后者管原地转向时更喜欢左转还是右转。调参时别混。

3. 参数表里没写清楚的那几个数:dt_ref、迭代次数与采样上限

这几个参数在很多教程里被一句"保持默认"带过,但它们其实是决定TEB性格的关键。默认值我按手上的版本大概记一下,具体以你装的包里的参数声明为准,不同分支会有出入。

3.1 dt_ref 与 dt_hysteresis 决定的是轨迹的"分辨率"

dt_ref是期望的时间间隔,默认大概0.3秒。它的真正作用不是"让机器人每0.3秒走一步",而是控制轨迹点的疏密:优化过程中,如果某个ΔT超过dt_ref + dt_hysteresis,就在中间插一个新位姿;如果短于dt_ref - dt_hysteresis,就删掉一个。这是一个自适应的重采样机制。

这意味着dt_ref直接影响优化变量个数,进而影响计算量。设得小(比如0.1),轨迹点变密,轨迹更平滑、避障更精细,但计算量翻倍;设得大(比如0.5),点变稀,计算快,但在窄通道里可能因为采样太粗而"看不见"障碍物之间的缝隙,直接判定不可行。

我的经验是:小车、低速(1 m/s以下)、场地窄,dt_ref往0.2到0.25调;大车、高速、场地开阔,保持0.3甚至0.4。dt_hysteresis别设得太小,否则点数会频繁增减,优化结果抖动;也别设太大,否则自适应失去意义。0.05到0.1之间比较稳妥。

3.2 no_inner_iterations 与 no_outer_iterations 是在拿精度换时间

TEB用的是g2o的稀疏求解器,每次规划做若干轮外层迭代,每轮里做若干次内层迭代。默认大概是内外各4次,这个组合在大多数场景下够用。

什么时候需要加?当你的机器人走出来的实际轨迹明显比可视化里那条更"折",或者规划器经常报"优化未收敛"时,适当加内层迭代会有改善。什么时候需要减?当你的控制周期很紧(比如10ms),单次规划耗时吃掉了大半预算时,把外层迭代降到2,通常轨迹质量下降不明显,但耗时能砍掉不少。

这里有个容易被忽略的细节:TEB对初始解很敏感。如果上一個周期的轨迹被成功复用(它会把上一周期的解作为这一周期的初值),收敛会快很多。如果全局路径频繁大幅变化,或者局部costmap里障碍物位置一直在跳,初始解质量差,迭代次数不够就会解出很奇怪的形状。所以遇到"偶尔抽风"的情况,先看是不是上游给的东西在抖。

3.3 max_samples 是硬上限,不是目标值

max_samples限制单条轨迹最多多少个位姿,默认500。它是个保险丝,防止dt_ref设太小导致变量爆炸。正常情况下轨迹点数远小于这个值,除非你的局部规划范围特别长。

真正要警惕的是局部costmap的尺寸和全局路径截取长度。TEB优化的轨迹长度,大致就是局部costmap覆盖的那一段。把局部costmap开到10米×10米,dt_ref又设0.2,那就是50个位姿点起步,再加上同伦类规划要算好几条,计算量直接上天。

4. 障碍物关联:TEB最容易崩掉的一环

如果说TEB有哪个部分最容易出问题,我认为不是权重,是障碍物关联。优化器本身很健壮,但"把costmap里的栅格变成优化图里的障碍物边"这个转换过程,坑特别多。

4.1 legacy 关联与 costmap_converter 的区别

不装costmap_converter的时候,TEB走的是legacy模式:直接把局部costmap里的障碍物栅格点,按一定规则关联到最近的轨迹点上。这种方式简单粗暴,问题是栅格点数量可能很大,而且同一个障碍物会被拆成几十个点分别关联,优化的时候相当于被罚了几十次,导致机器人对单个障碍物反应过度。

装了costmap_converter之后,栅格会被聚类成多边形或点集,关联时以"障碍物"为单位而不是以"栅格"为单位。结果是对障碍物的反应更"整体",不会因为栅格边缘的毛刺而误判。我的建议是只要条件允许就装上,尤其是场地里有柱子、桌腿这种离散小障碍物的时候,差别非常明显。

有一个参数值得单独提:costmap_obstacles_behind_robot_dist,默认大概1米,意思是机器人身后1米内的障碍物也参与优化。设太大,机器人会因为身后的东西而不敢前进;设太小,倒车时容易撞。如果你发现机器人在空旷地方也"犹犹豫豫",可以先查这个。

4.2 footprint 模型选错,前面所有调参都白搭

TEB里有个参数叫footprint模型,可选点模型、圆模型、线段模型、双圆模型、多边形模型。默认往往是点模型,这是最省计算但也最危险的:优化器认为机器人是一个质点,那么它算出来的"最小障碍物距离"跟机器人的实际外形完全对不上。

我见过最典型的翻车是:差速小车用了点模型,min_obstacle_dist设0.3,结果车头两侧的角在转弯时蹭到了门框。查了半天以为是权重问题,最后发现是footprint模型的问题。

模型适用代价备注
点快速验证、极小机器人最低不安全,别上实车
圆圆形底盘、全向底盘低min_obstacle_dist要包含半径
线段窄长形底盘中适合差速小车
双圆有明确前后轴的底盘中比圆模型贴合度好
多边形非规则外形最高最准,但计算量大

换成多边形以后,注意min_obstacle_dist的含义变了——它变成了多边形边界到障碍物的距离,不再是中心点距离。这时候原来的数值要重新调,通常会调小。

4.3 动态障碍物的关联超时与剔除

开启动态障碍物支持以后,TEB会把上一帧关联到的障碍物保留一段时间,如果这一帧没关联上就逐渐衰减剔除。这块在人多、物体移动快的场景里很容易出问题:障碍物突然出现,关联不上,机器人反应慢半拍;障碍物已经走了,关联还留着,机器人绕着一个不存在的东西走。

我一般的处理是:把动态障碍物相关的权重设得比静态障碍物低一些,让它起"辅助避让"的作用而不是主导;同时确认上游的感知频率跟得上控制频率。如果感知只有5Hz,而控制器跑20Hz,那动态障碍物这块基本没有意义,不如关掉,靠costmap的实时更新来间接处理。

5. 多拓扑路径:homotopy class 到底值不值得开

同伦类规划是TEB比较有特色的功能:它不只优化一条轨迹,而是在机器人周围生成一张简化的路线图,找出几条"拓扑上不同"的绕行方案(比如从障碍物左边绕、从右边绕),每条都优化一遍,最后挑一个代价最低的。

5.1 h-signature 是怎么区分左右绕行的

它给每个障碍物一个标记,然后计算轨迹环绕这些障碍物的"签名"。从障碍物左边绕和从右边绕,签名的符号不同,于是被判定为不同的同伦类。这样即使两条轨迹的几何代价很接近,也能被区分开,不会因为数值噪声在两者之间反复横跳。

这个机制解决的是一个很实际的问题:当机器人正对一根柱子时,"从左绕"和"从右绕"在局部看代价几乎一样,普通优化器会在这个对称点附近震荡,一会儿想左一会儿想右。有了同伦类筛选,它会明确选一条并保持一段时间,行为上稳定得多。

5.2 开着它CPU翻倍,什么时候该关

代价就是计算量。默认会尝试好几个类,每个类都要独立优化一遍,参数里那个最大类别数就是上限。我实测下来,在中低算力的板子上,开着同伦类规划会让单次规划耗时增加一倍以上,如果控制频率高,很容易拖垮整个周期。

我的取舍原则:场地里有大量离散柱状障碍物、且通道宽度允许左右两种走法时,开着它收益明显;场地是长廊、走廊、只有一条路可走时,开了也是白开,直接关掉省算力。另外那个"类别选择迟滞"参数值得调一下,调大一点可以让选择更稳定,减少在两条路径之间来回切换。

5.3 via_points 的替代用法

有时候你不想开同伦类规划,但又想让机器人从障碍物的某一侧绕。这时候可以用途经点:从全局路径上按一定间隔采样出若干点作为软约束,让局部轨迹尽量贴合全局路径。TEB里有个参数控制这个采样间隔,设成正值就启用了。

这个做法的好处是计算量小,坏处是它只是"软吸引",遇到强障碍物还是会偏出去,而且如果采样点落在障碍物里面,会跟避障项打架。我的用法是把它当成"轻度引导",间隔设在半米到一米之间,权重不要太高。真正需要强制走某条路的时候,还是得在全局规划层面解决,靠局部规划器拧不过来。

6. 现场排障:五类典型症状的排查链路

下面这些是我真实遇到过的,按症状分类,把我当时的排查顺序写出来。这个顺序很重要——先说结论没用,你得知道怎么一步步缩小范围。

6.1 原地摆头与低速抖动

现象:机器人在直道上正常,一进狭窄处就开始左右小幅摆动,或者原地反复转向。

第一步,关掉同伦类规划看还抖不抖。如果关了就好,说明是多个候选路径在争抢,去调类别选择的迟滞参数。

第二步,如果还抖,把weight_kinematics_forward_drive往上加,同时确认非完整约束的权重足够大。有时候是机器人被允许"侧着走"(非完整约束太弱),优化器就找到了这种不合物理的捷径。

第三步,检查dt_ref是不是太小、dt_hysteresis是不是也太小,导致点数频繁增减。我遇到过一次就是dt_hysteresis设成了0.01,优化结果每个周期点数都不一样,轨迹自然抖。

第四步,看上游全局路径是不是在抖。全局规划器如果每次重规划给出略微不同的折线,局部规划器就得跟着改,表现出来也是抖。这种情况要去治全局,不是调TEB。

6.2 贴墙走、蹭膨胀层

现象:明明通道很宽,机器人偏偏贴着一边走。

先确认footprint模型,点模型是最常见的元凶。然后看min_obstacle_dist是不是设得太小——有人为了"通过窄通道"把它调到接近0,结果在所有地方都贴着走。

再往下查,就是costmap的膨胀层半径和TEB的inflation_dist是否匹配。如果costmap的膨胀层只有0.1米,而TEB的inflation_dist是0.6,那TEB认为的"缓冲区"大部分落在costmap认为的"自由空间"里,没有实际障碍物支撑,推力就失效了。

最后一个容易被忽略的点:weight_optimaltime过大也会导致贴墙。因为贴墙往往是最短路径,时间项权重高,优化器宁可贴着障碍物走直线也不愿意绕开。

6.3 轨迹穿过障碍物

现象:可视化里轨迹明明穿过了障碍物,但规划器不报错。

这个一定优先查关联,不要怀疑优化器。常见原因有三个:一是costmap里那个位置确实没有障碍物(传感器没看到、或者被过滤掉了),TEB只是忠实地反映了它看到的世界;二是障碍物关联的距离阈值设得太小,远处的障碍物根本没被关联进来;三是局部costmap的更新频率太低,用的是过期数据。

我遇到过一次很典型的:机器人贴着玻璃走,激光直接穿透,costmap上一片空白,TEB当然认为可以走。这种情况不是规划器的问题,是感知的问题,得在传感器配置或者地图层面解决。

6.4 规划器报"no feasible trajectory"或直接停住

这个错误信息的信息量其实很大,它说的是"在给定的约束下找不到可行解",通常意味着约束之间互相矛盾。

排查顺序:先看速度约束和加速度约束是否自洽,max_vel_x大而acc_lim_x小到根本达不到,某些情况下会出问题;再看min_obstacle_dist是不是大于通道实际半宽,如果通道只有0.6米宽而min_obstacle_dist是0.4,那机器人认为自己过不去,这是正确的判断;然后看可行性检查的位姿数参数,它控制往前检查多少个点,设太大容易误判,设太小可能漏判。

还有一个隐蔽的原因:机器人当前位姿本身就在"膨胀区"里。比如它被人推着靠墙了,或者地图和实际有偏差,这时候所有轨迹的起点都违反约束,自然找不到解。这种情况需要允许它"先走出来",有些实现里会有相应的恢复机制。

6.5 规划周期跟不上控制周期

现象:控制器频率设的20Hz,但实际执行频率只有8Hz,机器人动作一顿一顿的。

先测单次规划耗时。如果确实是TEB太慢,按这个顺序砍:关同伦类规划、把max_samples降下来、把局部costmap尺寸减小、把dt_ref适当调大、减少迭代次数。这几个手段的收益是递减的,一般砍到前两个就够用了。

另外一个常被忽略的点:可视化。开着大量的Marker发布,在低算力平台上会显著拖慢周期。调试的时候开,跑起来的时候关掉。

7. 与DWA的分工边界和工程落地细节

最后说说选型。TEB不是万能的,我在一些场景里会主动换回更简单的方案。

7.1 什么场景应该换回DWA

第一,算力极其受限的嵌入式平台。TEB的图优化对CPU的要求比采样类方法高一个档次,如果板子本来就紧张,DWA更稳妥。

第二,场地极其开阔、几乎没有障碍物。这种场景下TEB的建模优势发挥不出来,反而白白付出计算代价。

第三,机器人模型特别特殊(比如某些非标的全向底盘),TEB的运动学模型未必能准确描述,硬套反而出问题,不如用能自定义运动模型的采样法。

反过来,窄通道、密集障碍物、需要精细避障、需要平滑轨迹的场景,TEB的优势非常明显,尤其是它输出的轨迹天然带时间信息,对下游的速度控制很友好。

7.2 局部costmap尺寸与全局路径的配合

这个是我踩过最久的一个坑。局部costmap设得太大,规划慢、容易陷入局部最优(因为它"看到"太多东西,反而不知道该往哪走);设得太小,遇到大障碍物时会因为看不到绕行空间而卡死。

我的经验是:局部costmap的边长,取机器人能看到"至少一个完整绕行方案"的最小值。走廊场景2到3米够,开阔车间可能要到5米以上。另外rolling window的更新频率要跟得上,不然机器人是在一张过期的地图上做优化。

全局路径的截取也有讲究。如果只取全局路径最前面的一小段喂给TEB,它没有足够的"前瞻"来做平滑;如果取太长,计算量又上去了。通常按局部costmap的对角线长度来截比较合适。

7.3 ROS1与ROS2移植版的功能差

这一点必须提醒。TEB在ROS2下的移植版本,功能完整度和ROS1的原版并不一致,有些高级特性(比如部分动态障碍物处理、部分可视化调试工具)在移植版里可能没有实现或者实现方式不同。参数文件的组织方式也变了,从.yaml嵌套结构变成了通过controller插件加载。

我一般的做法是:拿到一个新环境的TEB包,先去翻它的参数声明文件,看哪些参数是真正被读取的。别照着ROS1的老教程一顿配,配完发现一半参数根本没生效,然后在错误的方向上查半天。

提示:不同分支的默认值差异不小,尤其是权重类的默认值,同一个参数名在ROS1和ROS2版本里可能差一个数量级。调参前先把自己环境里的默认值打印出来,作为基准。

我个人在实际操作中的体会是,TEB这类优化型规划器,最忌讳的就是"头痛医头"式调参。它内部各个项是耦合的,你在障碍物项上加一点,可能就在时间项上减了一点,表现出来是另一个症状。真正有效的做法是先确认输入是干净的(costmap准确、全局路径合理、footprint模型正确、上游频率稳定),再去动权重。我见过的绝大多数"TEB不好用"的案例,追到最后都不是权重问题,而是输入数据的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询