3. 实现纯跟踪横向控制器

实现纯跟踪横向控制器 学习卡片

任务目标

实现纯跟踪(Pure Pursuit)横向控制器,使车辆能够跟踪由离散路径点组成的参考路径。控制器根据车辆当前位置与路径上预瞄点的几何关系计算前轮转角,并支持调整前视距离以观察跟踪效果差异。

核心原理

纯跟踪算法以车辆后轴中心为基准,在参考路径上寻找距离车辆前视距离 Ld 处的目标点,计算该点与车辆航向的夹角 alpha,然后通过阿克曼转向几何关系计算前轮转角:

delta = atan2(2 * L * sin(alpha), Ld)

其中 L 为车辆轴距。该算法本质是一个以预瞄点为目标的比例控制器,前视距离越大,转向越平缓;前视距离越小,跟踪越激进但可能振荡。

环境依赖

仅需 Python 3.8+ 与 NumPy,无需其他第三方库。车辆运动学模型使用自行车模型进行仿真。

完整实现


import numpy as np
import math

class PurePursuitController:
    def __init__(self, wheelbase=2.5, ld=3.0):
        self.L = wheelbase      # 轴距 (m)
        self.ld = ld            # 前视距离 (m)

    def find_target_point(self, x, y, path):
        """在路径上寻找距离当前位置约为前视距离的点"""
        min_dist = float('inf')
        nearest_idx = 0
        # 先找最近点
        for i, (px, py) in enumerate(path):
            d = math.hypot(px - x, py - y)
            if d < min_dist:
                min_dist = d
                nearest_idx = i
        # 从最近点向后搜索预瞄点
        for i in range(nearest_idx, len(path)):
            px, py = path[i]
            d = math.hypot(px - x, py - y)
            if d >= self.ld:
                return px, py, i
        # 若路径剩余部分不足前视距离,则取路径终点
        return path[-1][0], path[-1][1], len(path) - 1

    def compute_steering(self, x, y, yaw, path):
        """计算前轮转角 (rad)"""
        tx, ty, _ = self.find_target_point(x, y, path)
        alpha = math.atan2(ty - y, tx - x) - yaw
        # 角度归一化到 [-pi, pi]
        alpha = math.atan2(math.sin(alpha), math.cos(alpha))
        delta = math.atan2(2.0 * self.L * math.sin(alpha), self.ld)
        return delta

def simulate(controller, path, dt=0.1, speed=3.0, max_steps=1000):
    """自行车模型仿真,返回车辆轨迹"""
    x, y, yaw = path[0][0], path[0][1], 0.0
    trajectory = [(x, y)]
    for _ in range(max_steps):
        delta = controller.compute_steering(x, y, yaw, path)
        # 自行车模型更新
        x += speed * math.cos(yaw) * dt
        y += speed * math.sin(yaw) * dt
        yaw += (speed / controller.L) * math.tan(delta) * dt
        trajectory.append((x, y))
        # 到达终点附近则停止
        if math.hypot(x - path[-1][0], y - path[-1][1]) < 0.5:
            break
    return np.array(trajectory)

if __name__ == "__main__":
    # 定义参考路径:圆形轨迹
    theta = np.linspace(0, 2 * np.pi, 100)
    radius = 8.0
    path = [(radius * math.cos(t), radius * math.sin(t)) for t in theta]

    # 测试不同前视距离
    for ld in [2.0, 4.0, 6.0]:
        ctrl = PurePursuitController(ld=ld)
        traj = simulate(ctrl, path)
        # 计算终点与路径终点的横向误差
        final_err = math.hypot(traj[-1][0] - path[-1][0], traj[-1][1] - path[-1][1])
        print(f"Ld={ld}m, 轨迹点数={len(traj)}, 终点误差={final_err:.3f}m")

运行与验证

直接运行脚本即可完成一次闭环仿真:

python pure_pursuit.py

预期输出示例(数值因前视距离不同而异):


Ld=2.0m, 轨迹点数=1000, 终点误差=0.482m
Ld=4.0m, 轨迹点数=1000, 终点误差=0.215m
Ld=6.0m, 轨迹点数=1000, 终点误差=0.318m

验证判定标准:

  • 程序能正常运行并输出三组不同前视距离下的终点误差数据,无异常报错。
  • 车辆轨迹能围绕圆形路径运动,不会发散跑飞(可通过将 trajectory 保存后绘图确认)。
  • 对比不同 Ld 的输出误差:中等前视距离(约4m)通常跟踪精度较好;过小(2m)或过大(6m)误差均会增大,体现前视距离对跟踪效果的调优作用。

调参说明

修改 PurePursuitController 的 ld 参数即可调整前视距离。实际应用中,前视距离通常与车速相关(如 ld = k * speed),可自行扩展为动态前视。当前实现采用固定前视距离,便于直观对比参数影响。

纯跟踪算法几何示意 参考路径 车辆 后轴中心 Ld (前视距离) 预瞄点 yaw α δ L δ = atan2(2·L·sin(α), Ld) 前视距离 Ld 越大 → 转向越平缓,跟踪越稳定但滞后 前视距离 Ld 越小 → 转向越激进,响应快但易振荡

常见问题

  • 车辆轨迹发散:检查前视距离是否过小(如小于1m),或仿真步长 dt 是否过大导致离散误差累积。
  • 预瞄点查找失败:当路径点间距大于前视距离时,最近点搜索可能跳过目标点。可对路径进行插值加密,或改用连续距离累加方式查找。
  • 初始航向角影响:仿真中初始 yaw 设为0,若路径起点方向差异大,可先调整车辆航向对准路径切线方向。