Files
RCS-3000/docs/ALGORITHM.md
CaiXiang 7b6c956b6a initial
2025-11-27 15:20:21 +08:00

493 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 算法说明
本文档详细说明 AGV 路径跟踪与控制系统中使用的算法原理和实现。
## 目录
- [AGV 运动学模型](#agv-运动学模型)
- [Pure Pursuit 算法](#pure-pursuit-算法)
- [Stanley 算法](#stanley-算法)
- [路径平滑算法](#路径平滑算法)
- [算法比较](#算法比较)
---
## AGV 运动学模型
### 阿克曼转向模型
本系统使用阿克曼转向几何模型来描述 AGV 的运动。
#### 模型假设
1. AGV 在平面上运动
2. 使用前轮转向,后轮驱动
3. 不考虑侧滑
4. 速度和转向角在限制范围内
#### 状态变量
AGV 的状态由四个变量描述:
```
x - x 坐标(米)
y - y 坐标(米)
θ - 航向角(弧度)
v - 速度(米/秒)
```
#### 控制输入
```
v - 驱动速度(米/秒)
δ - 前轮转向角(弧度)
```
#### 运动学方程
```
ẋ = v · cos(θ)
ẏ = v · sin(θ)
θ̇ = (v / L) · tan(δ)
v̇ = a
```
其中:
- `L` 是轴距(前后轮距离)
- `a` 是加速度
#### 离散化实现
使用欧拉法进行离散化:
```cpp
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 是一种基于几何的路径跟踪算法,通过追踪前视点来计算转向角。
### 算法原理
1. 在参考路径上选择一个前视点lookahead point
2. 计算从当前位置到前视点的曲率半径
3. 根据曲率计算转向角
### 数学推导
#### 前视距离
```
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`: 前视距离
### 实现代码
```cpp
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 算法由两部分组成:
1. **航向误差控制**:修正车辆朝向与路径切线的偏差
2. **横向误差控制**:修正车辆位置与路径的垂直距离
### 数学公式
```
δ = θ_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)
```
正值表示在路径左侧,负值表示右侧。
### 实现代码
```cpp
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}]
```
满足条件:
1. 通过所有路径点
2. 一阶导数连续(速度连续)
3. 二阶导数连续(加速度连续)
#### 实现
使用自然样条(边界二阶导数为零):
```cpp
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
- 路径复杂多变
- 有充足计算资源
### 混合策略
可以根据速度动态切换
```cpp
std::string selectAlgorithm(double velocity) {
if (velocity > 2.0) {
return "pure_pursuit";
} else {
return "stanley";
}
}
```
---
## 误差分析
### 跟踪误差定义
```
e_lateral = |位置到路径的垂直距离|
e_heading = |车辆朝向 - 路径切线方向|
```
### 误差来源
1. **算法固有误差**
- Pure Pursuit: 前视点选择
- Stanley: 参数设置
2. **离散化误差**
- 时间步长 dt 的选择
- 路径点密度
3. **模型误差**
- 理想运动学模型 vs 实际物理
- 侧滑轮胎摩擦等忽略因素
### 误差优化
- 减小时间步长 dt
- 增加路径点密度
- 调整算法参数
- 使用更精确的模型
---
## 实际应用示例
### 示例 1: 圆形路径跟踪
```cpp
// 生成半径 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: 复杂路径跟踪
```cpp
// 从 CSV 加载路径
PathCurve path = PathCurve::loadFromCSV("complex_path.csv");
// 使用 Stanley 以获得更高精度
tracker.generateControlSequence("stanley", 0.05, 15.0, 1.0);
```
---
## 参考文献
1. **Pure Pursuit**:
- Coulter, R. C. (1992). "Implementation of the Pure Pursuit Path Tracking Algorithm". Carnegie Mellon University.
2. **Stanley**:
- Hoffmann, G. M., et al. (2007). "Autonomous Automobile Trajectory Tracking for Off-Road Driving: Controller Design, Experimental Validation and Racing". American Control Conference.
3. **阿克曼转向**:
- Rajamani, R. (2011). "Vehicle Dynamics and Control". Springer.
---
**最后更新**: 2025-11-27