第四节:Stanley方法——横向误差与航向误差的协同控制

Stanley方法,说白了就是让车像老司机一样,同时盯着两个东西:车头朝向对不对,以及车身离参考线偏了多少。我最早接触这个算法是在2016年,当时做园区物流车项目,Pure Pursuit在低速还行,一上20km/h就开始画龙。后来换成Stanley,嗯,稳多了。

4.1 核心思想:前轮转角 = 航向误差 + 横向误差的非线性反馈

Stanley的控制律非常简洁,就一个公式:

δ(t) = θ_e(t) + arctan(k * e(t) / v(t))

其中:

  • δ(t) — 前轮转角(控制量)
  • θ_e(t) — 航向误差(车头方向与参考线切线方向的夹角)
  • e(t) — 横向误差(车辆前轴中心到参考线的垂直距离)
  • k — 增益系数(我一般取0.5~2.0之间)
  • v(t) — 当前车速

你想想看,这个公式妙在哪里?
航向误差是直接补偿,横向误差通过反正切函数做非线性映射。车速越快,横向误差的权重自动降低——这很符合直觉:高速时你不敢猛打方向。

关键洞察:Stanley本质上是一个状态反馈控制器,它把横向误差和航向误差统一映射到前轮转角上。与Pure Pursuit的几何预瞄不同,Stanley更接近现代控制理论中的李雅普诺夫设计法。

4.2 横向误差与航向误差的数学定义

这里我习惯用向量法来定义,避免坐标系搞混。假设参考线上最近点为P,车辆前轴中心为C:

  • 横向误差 e(t):向量CP在参考线法向上的投影。左偏为正,右偏为负。
  • 航向误差 θ_e(t):车辆纵轴与P点切线方向的夹角。车头偏左为正。

计算时有个坑:最近点的搜索。我曾经在项目中直接用欧氏距离遍历,结果在弯道处跳点,导致控制量突变。后来改用投影法+局部搜索窗口才解决。

避坑指南:我曾经在高速测试时发现横向误差突然跳变,排查了半天,原来是参考线采样不均匀导致的。建议参考线点间距不超过0.5米,且做平滑处理。

4.3 非线性反馈的物理意义

为什么用反正切?我解释给你听:

  • 当横向误差很大时(比如1米),arctan(k*e/v) 趋近于 ±90°,给出最大转角——快速回正。
  • 当横向误差很小时(比如0.05米),arctan(k*e/v) ≈ k*e/v,近似线性——微调即可。
  • 车速v在分母上:高速时横向误差的贡献被压缩,避免过度转向。

这个非线性特性,说白了就是大误差大增益,小误差小增益。我做过对比实验:用线性反馈(直接比例)在弯道入口处容易超调,而Stanley的反正切曲线天然带有饱和特性,控制更平滑。

个人经验:k值的选取跟车辆轴距和最大转角有关。轴距3米的车,k=1.0是个不错的起点。如果发现车辆在直线上来回摆动,说明k太大了;如果弯道跟踪滞后明显,说明k太小。

4.4 与Pure Pursuit的对比分析

这两个方法我都在实车上跑过,下面这张表是我自己的总结:

对比维度 Stanley方法 Pure Pursuit
控制原理 状态反馈(横向+航向误差) 几何预瞄(圆弧拟合)
预瞄机制 无显式预瞄,依赖最近点 有预瞄距离Ld
高速表现 优秀(车速自适应) 一般(预瞄距离需随速调整)
弯道跟踪 平滑,无超调 可能内切或外切
参数调优 1个参数(k) 1个参数(Ld)
计算复杂度 低(只需最近点) 低(需预瞄点)
适用场景 结构化道路、高速公路 低速、越野、大曲率弯道

我个人认为:Stanley更适合高速场景,因为它的车速自适应是天然的;Pure Pursuit更适合低速大弯,因为预瞄机制可以提前规划路径。我在一个项目中把两者做了融合:低速用Pure Pursuit,高速切Stanley,效果还不错。

4.5 代码实现与调参建议

下面是一个简化的Stanley控制器实现,我删掉了滤波和异常处理,保留核心逻辑:

def stanley_control(vehicle_state, reference_path, k=1.0):
    # 1. 找到参考线上最近点
    nearest_idx = find_nearest_point(vehicle_state.position, reference_path)
    nearest_point = reference_path[nearest_idx]
    
    # 2. 计算横向误差(带符号)
    e = compute_lateral_error(vehicle_state, nearest_point)
    
    # 3. 计算航向误差
    heading_error = normalize_angle(
        vehicle_state.yaw - nearest_point.tangent_yaw
    )
    
    # 4. 计算前轮转角
    v = max(vehicle_state.speed, 0.1)  # 防止除零
    delta = heading_error + math.atan2(k * e, v)
    
    # 5. 限幅
    delta = clamp(delta, -MAX_STEER_ANGLE, MAX_STEER_ANGLE)
    
    return delta

调参时我有个习惯:先固定k=1.0,在直线上跑,观察横向误差的收敛速度。如果收敛太慢,增大k;如果出现振荡,减小k。然后在弯道上微调,一般k在0.8~1.5之间就能有不错的效果。

注意:代码中的find_nearest_point如果直接用暴力搜索,在长路径上会很慢。我建议用KD树或者分段线性搜索,实时性会好很多。

4.6 本章知识体系

下面这张图展示了Stanley方法的完整知识结构,我画了很久才理清楚:

Stanley方法知识体系 核心控制律 δ = θ_e + arctan(k·e / v) 横向误差 e (带符号) 航向误差 θ_e (归一化) 车速 v (分母防零) 关键技术点 • 最近点搜索(KD树) • 横向误差符号约定 • 航向误差归一化 调参经验 • k=0.8~1.5(常用) • 高速减小k • 大弯道增大k 对比Pure Pursuit • 无预瞄 vs 有预瞄 • 高速优 vs 低速优 • 1参数 vs 1参数 总结:Stanley = 状态反馈 + 非线性映射 + 车速自适应 适合结构化道路,高速场景首选

嗯,到这里Stanley的核心内容就讲完了。我个人觉得,理解这个算法的关键在于想明白一件事:为什么反正切函数比线性比例更适合做横向控制?答案就在它的饱和特性——大误差时快速响应,小误差时精细调节,这种非线性特性天然适合车辆这种非完整约束系统。

最后一个小技巧:如果你发现Stanley在弯道出口处有轻微的超调,可以尝试在航向误差项上加一个小的阻尼项,比如 -kd * dθ_e/dt。我试过,效果立竿见影。

可可精品资料库