11 KiB
11 KiB
算法说明
本文档详细说明 AGV 路径跟踪与控制系统中使用的算法原理和实现。
目录
AGV 运动学模型
阿克曼转向模型
本系统使用阿克曼转向几何模型来描述 AGV 的运动。
模型假设
- AGV 在平面上运动
- 使用前轮转向,后轮驱动
- 不考虑侧滑
- 速度和转向角在限制范围内
状态变量
AGV 的状态由四个变量描述:
x - x 坐标(米)
y - y 坐标(米)
θ - 航向角(弧度)
v - 速度(米/秒)
控制输入
v - 驱动速度(米/秒)
δ - 前轮转向角(弧度)
运动学方程
ẋ = v · cos(θ)
ẏ = v · sin(θ)
θ̇ = (v / L) · tan(δ)
v̇ = a
其中:
L是轴距(前后轮距离)a是加速度
离散化实现
使用欧拉法进行离散化:
State AGVModel::step(double v, double delta, double dt) const {
State new_state = current_state_;
// 限制转向角
delta = std::max(-max_steering_angle_,
std::min(max_steering_angle_, delta));
// 更新状态
new_state.x += v * cos(current_state_.theta) * dt;
new_state.y += v * sin(current_state_.theta) * dt;
new_state.theta += (v / wheelbase_) * tan(delta) * dt;
new_state.v = v;
// 归一化角度到 [-π, π]
while (new_state.theta > M_PI) new_state.theta -= 2 * M_PI;
while (new_state.theta < -M_PI) new_state.theta += 2 * M_PI;
return new_state;
}
Pure Pursuit 算法
Pure Pursuit 是一种基于几何的路径跟踪算法,通过追踪前视点来计算转向角。
算法原理
- 在参考路径上选择一个前视点(lookahead point)
- 计算从当前位置到前视点的曲率半径
- 根据曲率计算转向角
数学推导
前视距离
L_d = k_v · v + L_base
其中:
k_v: 速度相关系数(通常为 1.0-2.0)v: 当前速度L_base: 基础前视距离
前视点选择
沿参考路径找到距离当前位置约为 L_d 的点。
转向角计算
根据几何关系:
α = atan2(l_y, l_x) # 前视点相对角度
δ = atan(2 · L · sin(α) / L_d)
其中:
L: 轴距α: 前视点相对于车辆坐标系的角度L_d: 前视距离
实现代码
double ControlGenerator::purePursuit(
const AGVModel::State& state,
const PathCurve& path,
double lookahead_distance
) {
const auto& path_points = path.getPathPoints();
if (path_points.empty()) return 0.0;
// 找到最近点
size_t closest_idx = 0;
double min_dist = 1e9;
for (size_t i = 0; i < path_points.size(); ++i) {
double dx = path_points[i].x - state.x;
double dy = path_points[i].y - state.y;
double dist = sqrt(dx * dx + dy * dy);
if (dist < min_dist) {
min_dist = dist;
closest_idx = i;
}
}
// 找到前视点
size_t lookahead_idx = closest_idx;
for (size_t i = closest_idx; i < path_points.size(); ++i) {
double dx = path_points[i].x - state.x;
double dy = path_points[i].y - state.y;
double dist = sqrt(dx * dx + dy * dy);
if (dist >= lookahead_distance) {
lookahead_idx = i;
break;
}
}
// 计算转向角
const auto& target = path_points[lookahead_idx];
double dx = target.x - state.x;
double dy = target.y - state.y;
// 转换到车体坐标系
double cos_theta = cos(state.theta);
double sin_theta = sin(state.theta);
double local_x = dx * cos_theta + dy * sin_theta;
double local_y = -dx * sin_theta + dy * cos_theta;
// 计算转向角
double alpha = atan2(local_y, local_x);
double L = 2.0; // 轴距
double steering = atan(2 * L * sin(alpha) / lookahead_distance);
return steering;
}
参数调整
- lookahead_distance:
- 过小:响应快,但可能振荡
- 过大:平滑,但转弯时可能跟踪不及时
- 推荐:速度的 1-2 倍
优缺点
优点:
- 算法简单,计算量小
- 对路径平滑性要求低
- 适合高速场景
缺点:
- 跟踪精度相对较低
- 不考虑横向误差
- 低速时性能下降
Stanley 算法
Stanley 算法结合了横向误差和航向误差,提供更精确的路径跟踪。
算法原理
Stanley 算法由两部分组成:
- 航向误差控制:修正车辆朝向与路径切线的偏差
- 横向误差控制:修正车辆位置与路径的垂直距离
数学公式
δ = θ_e + atan(k · e / v)
其中:
θ_e: 航向误差(车辆朝向与路径切线的夹角)e: 横向误差(车辆到路径的垂直距离)k: 增益参数v: 当前速度
详细推导
航向误差
θ_e = θ_path - θ_vehicle
归一化到 [-π, π]。
横向误差
计算车辆前轮中心到路径的垂直距离:
e = (x_vehicle - x_path) · sin(θ_path) - (y_vehicle - y_path) · cos(θ_path)
正值表示在路径左侧,负值表示右侧。
实现代码
double ControlGenerator::stanley(
const AGVModel::State& state,
const PathCurve& path,
double k
) {
const auto& path_points = path.getPathPoints();
if (path_points.empty()) return 0.0;
// 找到最近的路径点
size_t closest_idx = 0;
double min_dist = 1e9;
for (size_t i = 0; i < path_points.size(); ++i) {
double dx = path_points[i].x - state.x;
double dy = path_points[i].y - state.y;
double dist = sqrt(dx * dx + dy * dy);
if (dist < min_dist) {
min_dist = dist;
closest_idx = i;
}
}
const auto& closest_point = path_points[closest_idx];
// 计算横向误差
double dx = state.x - closest_point.x;
double dy = state.y - closest_point.y;
double path_theta = closest_point.theta;
double cross_track_error = -dx * sin(path_theta) + dy * cos(path_theta);
// 计算航向误差
double heading_error = state.theta - path_theta;
while (heading_error > M_PI) heading_error -= 2 * M_PI;
while (heading_error < -M_PI) heading_error += 2 * M_PI;
// Stanley 控制律
double v = std::max(0.1, std::abs(state.v)); // 避免除零
double steering = heading_error + atan(k * cross_track_error / v);
return steering;
}
参数调整
- k(增益参数):
- 过小:横向误差响应慢
- 过大:可能引起振荡
- 推荐范围:0.5 - 2.0
- 低速场景:使用较大的 k
- 高速场景:使用较小的 k
优缺点
优点:
- 跟踪精度高
- 同时考虑位置和方向误差
- 适合低速和高精度场景
- 理论上能保证稳定性
缺点:
- 对路径平滑性要求较高
- 需要路径切线信息
- 参数调整相对复杂
路径平滑算法
三次样条插值
为了生成平滑的路径,系统使用三次样条插值。
算法原理
对于 n 个路径点,三次样条插值构造 n-1 段三次多项式:
S_i(t) = a_i + b_i·t + c_i·t² + d_i·t³, t ∈ [t_i, t_{i+1}]
满足条件:
- 通过所有路径点
- 一阶导数连续(速度连续)
- 二阶导数连续(加速度连续)
实现
使用自然样条(边界二阶导数为零):
PathCurve PathCurve::generateSmoothPath(
const std::vector<PathPoint>& waypoints,
int points_per_segment
) {
std::vector<PathPoint> smooth_points;
for (size_t i = 0; i < waypoints.size() - 1; ++i) {
const auto& p0 = waypoints[i];
const auto& p1 = waypoints[i + 1];
for (int j = 0; j < points_per_segment; ++j) {
double t = static_cast<double>(j) / points_per_segment;
// 三次 Hermite 插值
double h00 = 2*t*t*t - 3*t*t + 1;
double h10 = t*t*t - 2*t*t + t;
double h01 = -2*t*t*t + 3*t*t;
double h11 = t*t*t - t*t;
PathPoint pt;
pt.x = h00 * p0.x + h10 * dx0 + h01 * p1.x + h11 * dx1;
pt.y = h00 * p0.y + h10 * dy0 + h01 * p1.y + h11 * dy1;
smooth_points.push_back(pt);
}
}
return PathCurve(smooth_points);
}
算法比较
性能对比表
| 特性 | Pure Pursuit | Stanley |
|---|---|---|
| 计算复杂度 | 低 | 中 |
| 跟踪精度 | 中 | 高 |
| 稳定性 | 好 | 很好 |
| 高速性能 | 优秀 | 良好 |
| 低速性能 | 一般 | 优秀 |
| 参数调整 | 简单 | 中等 |
| 适用场景 | 平滑路径 | 精确跟踪 |
选择建议
使用 Pure Pursuit 当:
- 速度较高(> 2 m/s)
- 路径相对平滑
- 对精度要求不高
- 计算资源有限
使用 Stanley 当:
- 需要高精度跟踪
- 低速场景(< 1 m/s)
- 路径复杂多变
- 有充足计算资源
混合策略
可以根据速度动态切换:
std::string selectAlgorithm(double velocity) {
if (velocity > 2.0) {
return "pure_pursuit";
} else {
return "stanley";
}
}
误差分析
跟踪误差定义
e_lateral = |位置到路径的垂直距离|
e_heading = |车辆朝向 - 路径切线方向|
误差来源
-
算法固有误差
- Pure Pursuit: 前视点选择
- Stanley: 参数设置
-
离散化误差
- 时间步长 dt 的选择
- 路径点密度
-
模型误差
- 理想运动学模型 vs 实际物理
- 侧滑、轮胎摩擦等忽略因素
误差优化
- 减小时间步长 dt
- 增加路径点密度
- 调整算法参数
- 使用更精确的模型
实际应用示例
示例 1: 圆形路径跟踪
// 生成半径 5m 的圆形路径
PathCurve path = PathCurve::generateCircularPath(5.0, 100);
// Pure Pursuit
tracker.generateControlSequence("pure_pursuit", 0.1, 10.0, 1.5);
// 前视距离: 1.5 * 1.5 = 2.25m
// Stanley
tracker.generateControlSequence("stanley", 0.1, 10.0, 0.5);
// k = 1.0(默认)
示例 2: 复杂路径跟踪
// 从 CSV 加载路径
PathCurve path = PathCurve::loadFromCSV("complex_path.csv");
// 使用 Stanley 以获得更高精度
tracker.generateControlSequence("stanley", 0.05, 15.0, 1.0);
参考文献
-
Pure Pursuit:
- Coulter, R. C. (1992). "Implementation of the Pure Pursuit Path Tracking Algorithm". Carnegie Mellon University.
-
Stanley:
- Hoffmann, G. M., et al. (2007). "Autonomous Automobile Trajectory Tracking for Off-Road Driving: Controller Design, Experimental Validation and Racing". American Control Conference.
-
阿克曼转向:
- Rajamani, R. (2011). "Vehicle Dynamics and Control". Springer.
最后更新: 2025-11-27