教程 · 中级 · 30 分钟

纯跟踪算法:只用一个参数跟随路径

把一串航点变成转向指令的控制器。本文讲前视距离的几何关系、为什么最佳取值随速度变化,以及它在弯道处的代价。

没人会教的那一级

规划器给你一条路径。A* 给你一串栅格;一次测绘给你一串航点;一个任务给你地图上的一条线。它们谁都没告诉轮子该做什么。

纯跟踪就住在这个空隙里,而它只是一段几何:

找出路径上在你前方固定距离处的那个点。开出经过它的圆弧。重复。

没有误差信号,没有积分项,没有增益调度。只有一个数——前视距离——而它决定一切。

几何

把目标点放到机器人自己的坐标系里:x 朝前,y 朝左,距离是 L_d。从机器人当前位置经过该点的一段圆弧,其曲率为

κ = 2y / L_d²

这一行值得读两遍,因为一切都从它来。横向偏移 y 在分子上,所以目标点离轴越远,弧就弯得越狠。前视距离在分母上是平方,所以把它翻倍会把曲率变成四分之一。

换成半径,再换成轴距为 b 的车辆的转向角:

R = L_d² / 2y            δ = atan(2 b sin α / L_d)

对差速底盘来说没有转向角——你直接下发旋转速率,ω = v κ。同样的曲率,不同的执行器。

唯一的旋钮,以及它为什么自己跟自己打

**短前视。**目标点很近,所以一点小的横向偏移就产生很大的曲率。机器人跟得很紧——而且对每一次抖动都剧烈反应,包括那些它自己的转向刚刚造成的抖动。

长前视。目标点很远,曲率平缓,转向平滑——而在弯道处,目标点已经绕过了弯,机器人却还没有,于是机器人切弯。

两者朝相反方向拉扯,而算法里没有任何东西去平衡它们。你只能选一个数。

上下两幅图。上面一幅是 1 到 3 米每秒四种速度下,均方根横向误差对前视距离的曲线:每条都是 U 形,从很短的前视距离陡然下降,达到一个标出的最小值,然后缓慢上升;随着速度提高,最小值向右上方移动。下面一幅是 2 米每秒下两种前视距离的绝对横向误差沿路径的分布:0.75 米的短前视到处抖动,而 4 米的长前视在直道上干净、每个弯道处出现尖峰。
在路径跟踪模拟器里实测。最佳前视距离是1 m/s 时 0.9 m、3 m/s 时 1.4 m——而那个最优处的误差会翻三倍,从 0.048 m 涨到 0.229 m。速度换来的精度,是任何调参都还不回来的。 下载 SVG

每本教科书推导里的陷阱

把纯跟踪写在纸上,配一台瞬间就能转向的机器人,于是越短总是越好。误差随着前视缩短单调下降。没有最优,上面那条 U 形曲线根本不存在。

真实的机器人有滞后。从下发转向指令到达到那个角度需要时间;到你动作时所依据的那个位姿,在轮子响应之前已经老了几十毫秒。模拟器建模了一个 0.25 s 的转向时间常数和 0.15 s 的感知加计算延迟——总共 0.4 s 的滞后,对一台业余漫游车来说平平无奇。

那份滞后就是最优值存在的全部原因。短前视下发一个修正,修正迟到,机器人已经走过误差所在的位置,下一条指令就朝反方向过度修正。**一个比自己的执行器还快的控制器会振荡。**这和支配 PID 微分增益的是同一个事实,也是停车距离背后的那个:延迟不是细节,它是决定整定的那件事。

那么该怎么选

实测出来的最优值,以及换算成可迁移单位之后的样子:

速度 最佳前视距离 折合时间 与 v × lag 之比
1.0 m/s 0.9 m 0.90 s 2.25×
1.5 m/s 1.1 m 0.73 s 1.83×
2.0 m/s 1.3 m 0.65 s 1.62×
3.0 m/s 1.4 m 0.47 s 1.17×

这张表有两个诚实的读法。

**前视距离随速度增长,但不成比例。**常见的建议是“设成固定几秒的行程”,接近但不准确——在这个范围内时间从 0.90 s 掉到 0.47 s。

**它落在一到两倍“你在一份控制滞后期间走过的距离”上。**这就是那条有用的规则,因为它可测量:找出你的滞后,乘以你的速度,从那里开始。在这台机器人上 v × lag 是 0.4 m 到 1.2 m,而最优值是 0.9 m 到 1.4 m。

实践中,把它做成一条小的线性调度并钳位:

float lookahead = constrain(K_LOOK * speed + L_MIN, L_MIN, L_MAX);

L_MIN 比 K_LOOK 重要得多。静止时调度想要的前视距离是零,而而那就是伪装成别的东西的除以零。

把它实现出来

// Pure pursuit over a waypoint list. Returns the curvature to command.
float pursue(float x, float y, float heading, float lookahead) {
  // 1. Where am I on the path? Search only near the arc length I last reached —
  //    a global search latches onto whichever segment happens to be nearest, and a
  //    path that doubles back will teleport your progress to a corner you passed
  //    twenty seconds ago.
  float s = projectOntoPath(x, y, lastS);
  lastS = max(lastS, s);

  // 2. The goal point, one lookahead further along.
  Point goal = pointAtArcLength(lastS + lookahead);

  // 3. Into the robot frame, then straight into the curvature formula.
  float dx = goal.x - x, dy = goal.y - y;
  float lateral = -sin(heading) * dx + cos(heading) * dy;
  float ld = max(0.3f, hypotf(dx, dy));
  return 2.0f * lateral / (ld * ld);
}

十二行里有三样东西比其余的加起来更值得注意:

  • **lastS 是状态,而且只往前走。**没有它,任何会自我交叉的路径都会让控制器搁浅。
  • **钳住 ld。**随着目标点接近,曲率趋向无穷。
  • **目标点在路径上,而不是最近的航点。**瞄航点会让车在它们之间左右摇摆着走;瞄线段上的一点,才是让运动平滑的原因。

它能做什么,不能做什么

**它不会站着不动、转过来面对路径。**纯跟踪没有“站着不动”这个概念——曲率是一个形状,而形状需要前进运动。让机器人大致对齐着起步,或者先开一小段起始弧。

**它有一个最小转弯半径,而路径未必尊重它。**如果一个弯比 b / tan(δ_max) 更急,机器人无论怎么调都跟不过。要么把路径软化,要么在弯道减速——运动曲线就是干这个的。

**它总是切弯,这是设计使然。**在 2 m/s 的实测中,它比指令路径少开了 0.8%。如果这不能接受,你要的是一个不同的控制器——Stanley 改为按横向误差转向,它会从外侧甩过而不是切内。

**它修不好一个糟糕的位置估计。**上面一切都假设机器人知道自己在哪里。喂它 1 Hz 的 GPS 定位,均方根误差就从 0.09 m 涨到 1.25 m——见GPS 航点导航。

出问题时

现象 通常原因
直道上蛇形摆动 前视相对滞后太短;加长它或量出滞后
每个弯都切过去 前视太长——这是取舍,不是缺陷
横切到路径很靠后的一段 用了全局投影,而不是跟着弧长的窗口投影
起步时原地旋转 前视调度在零速时算出零;钳住 L_MIN
慢速跟得好、快速乱走 最优值移动了;把前视随速度调度
冲过最后一个航点 没有任何东西告诉它停下——纯跟踪只管转向
模拟里完美、车上很差 模拟里没有转向滞后。加上它,然后重新整定

在路径跟踪模拟器里让两个控制器跑同一条赛道——拖动前视距离,整趟曲线会重画。然后去做GPS 航点车,在那里位置估计会变成最难的部分。

探索关联图谱

本教程所属的搭建

包含本教程的项目和学习路线。

延伸阅读

参考资料