# 轻量阿克曼 Hybrid A* 路径规划方案说明 ## 1. 方案目标 本方案用于阿克曼底盘小车的轻量路径规划,重点满足以下要求: - 考虑小车最小转弯半径。 - 支持前进、倒车和转向。 - 支持运动过程中动态添加锥桶障碍物。 - 支持锥桶障碍物累计保留和去重。 - 使用融合后的定位和代价地图,不直接处理雷达、相机、IMU、轮式里程计原始数据。 - 输出仍保持 ROS 标准 `/plan nav_msgs/Path`,避免引入复杂自定义消息。 当前实现是在 `planner` 包已有代码基础上升级完成的,核心规划器仍使用原可执行文件名 `grid_astar_theta_node`,但内部已经从二维 A*/Theta* 改为轻量阿克曼 Hybrid A*/State Lattice。 ## 2. 总体架构 系统由两个主要节点组成: | 节点 | 文件 | 作用 | |---|---|---| | `grid_astar_theta_planner` | `src/planner/src/grid_astar_theta_node.cpp` | 轻量 Hybrid A* 路径规划,融合 costmap 和锥桶障碍物,输出 `/plan` | | `topology_pure_pursuit` | `src/planner/scripts/topology_pure_pursuit_node.py` | 跟踪 `/plan`,根据路径 yaw 判断前进或倒车,输出 `/cmd_vel` | 典型数据流: ```text /odom /goal_pose /global_costmap/costmap /random_obstacles | v grid_astar_theta_planner | v /plan | v topology_pure_pursuit | v /cmd_vel | v ackermann_cmd_bridge.py | v /ackermann_cmd ``` ## 3. 输入与输出 ### 3.1 规划器输入 | Topic | 类型 | 说明 | |---|---|---| | `/odom` | `nav_msgs/Odometry` | 小车当前融合定位,至少需要 `x, y, yaw` | | `/goal_pose` | `geometry_msgs/PoseStamped` | 目标位姿,必须包含 `x, y, yaw` | | `/global_costmap/costmap` | `nav_msgs/OccupancyGrid` | 全局代价地图 | | `/random_obstacles` | `std_msgs/Float32MultiArray` | 锥桶障碍物,格式 `[x, y, radius, ...]` | | `/clear_random_obstacles` | `std_msgs/Bool` | 清空已累计锥桶 | 所有坐标默认工作在 `map` 坐标系下。如果 `/odom` 或 `/goal_pose` 的 `frame_id` 不是 `map`,则必须存在 TF 变换。 ### 3.2 规划器输出 | Topic | 类型 | 说明 | |---|---|---| | `/plan` | `nav_msgs/Path` | 规划结果 | | `/ackermann_lattice_status` | `std_msgs/String` | 规划状态和失败原因 | `/plan` 的每个 Pose 有特殊语义: - `pose.position.x/y` 表示轨迹点位置。 - `pose.orientation.yaw` 表示小车车身朝向。 - 如果路径点前进方向与车身 yaw 相反,则该段表示倒车。 ## 4. 代价地图处理 规划器订阅 `/global_costmap/costmap`,类型为 `nav_msgs/OccupancyGrid`。 默认规则: | costmap 值 | 解释 | |---|---| | `>= 50` | 障碍物 | | `-1` | 未知区域,默认视为障碍 | | `< 50` 且非 `-1` | 可通行 | 相关参数: ```yaml costmap_topic: /global_costmap/costmap costmap_occupied_threshold: 50 unknown_is_obstacle: true outside_costmap_is_obstacle: true ``` 当前规划器内部搜索分辨率默认是 `0.10m`。如果收到 costmap,且 `fit_map_to_costmap: true`,规划器会用 costmap 的物理边界更新规划范围。 需要注意: - costmap 的实际像素宽高以运行时 `OccupancyGrid.info.width/height` 为准。 - 静态地图 `my_map.pgm` 是 `199 x 266` 像素,分辨率 `0.05m/像素`。 - 静态地图 `zhihui.pgm` 是 `109 x 115` 像素,分辨率 `0.05m/像素`。 运行时查看 costmap 信息: ```bash ros2 topic echo --once /global_costmap/costmap --field info ``` ## 5. 锥桶障碍物处理 锥桶输入 topic: ```text /random_obstacles ``` 消息类型: ```text std_msgs/Float32MultiArray ``` 数据格式: ```text [x, y, radius, x, y, radius, ...] ``` 示例: ```bash ros2 topic pub -1 /random_obstacles std_msgs/msg/Float32MultiArray \ "{data: [1.2, 0.6, 0.15]}" ``` ### 5.1 增量添加 当前默认开启: ```yaml accumulate_obstacles: true ``` 这表示每次发布新锥桶后,规划器不会清空旧锥桶,而是将新锥桶合并到已有锥桶集合中。 例如先发布: ```bash ros2 topic pub -1 /random_obstacles std_msgs/msg/Float32MultiArray \ "{data: [1.2, 0.6, 0.15]}" ``` 再发布: ```bash ros2 topic pub -1 /random_obstacles std_msgs/msg/Float32MultiArray \ "{data: [2.0, 1.1, 0.15]}" ``` 规划器内部会保留两个锥桶。 ### 5.2 去重规则 默认参数: ```yaml obstacle_merge_distance: 0.20 ``` 如果新锥桶中心与已有锥桶中心距离小于 `0.20m`,认为是同一个锥桶: - 更新锥桶中心位置。 - 半径取新旧较大值。 ### 5.3 清空锥桶 如果误检或需要重新开始累计,可以发布: ```bash ros2 topic pub -1 /clear_random_obstacles std_msgs/msg/Bool "{data: true}" ``` ## 6. 障碍物膨胀 锥桶障碍物不会只按原始半径参与碰撞检测,而是会进行安全膨胀。 膨胀半径计算: ```text inflated_radius = max(obstacle_radius, unknown_obstacle_radius) + robot_radius + localization_error + latency_margin + safety_margin ``` 默认参数: ```yaml default_obstacle_radius: 0.15 unknown_obstacle_radius: 0.15 robot_radius: 0.14 localization_error: 0.10 latency_margin: 0.05 safety_margin: 0.05 ``` 按默认值,一个半径 `0.15m` 的锥桶最终膨胀半径约为: ```text 0.15 + 0.14 + 0.10 + 0.05 + 0.05 = 0.49m ``` 这个值偏保守,适合定位误差较大或控制延迟明显的情况。如果实际通道较窄,可以降低 `localization_error` 或 `safety_margin`。 ## 7. Hybrid A* 搜索模型 规划器状态为: ```text x, y, yaw, gear ``` 其中: - `x, y` 是位置。 - `yaw` 是车身朝向。 - `gear` 是挡位方向,包含前进和倒车。 ### 7.1 状态离散 默认参数: ```yaml resolution: 0.10 yaw_bins: 72 ``` 含义: - 平面位置按 `0.10m` 分辨率离散。 - yaw 被离散成 72 份,即每份约 5 度。 ### 7.2 运动原语 每次扩展使用 6 类动作: | gear | steering | 含义 | |---|---|---| | 前进 | 直行 | 向前直行 | | 前进 | 左转 | 向前左转 | | 前进 | 右转 | 向前右转 | | 倒车 | 直行 | 向后直行 | | 倒车 | 左转 | 向后左转 | | 倒车 | 右转 | 向后右转 | 默认运动原语长度: ```yaml primitive_length: 0.12 ``` 碰撞检测采样间隔: ```yaml collision_sample_step: 0.03 ``` 也就是说每条原语会被分成多个点做碰撞检测,而不是只检测终点。 ### 7.3 最小转弯半径 默认: ```yaml min_turning_radius: 0.40 ``` 转弯原语按该半径积分生成,规划阶段会保证轨迹曲率不小于这个半径限制。 ## 8. 目标判定 目标输入为 `/goal_pose`,必须包含 `x, y, yaw`。 默认到达条件: ```yaml goal_xy_tolerance: 0.20 goal_yaw_tolerance: 0.174533 ``` 含义: - 位置误差小于 `0.20m`。 - 朝向误差小于约 10 度。 如果目标只给 `x, y` 而 yaw 没有意义,那么最终车头方向可能不符合预期。因此对于阿克曼小车,建议目标始终提供合理 yaw。 ## 9. 代价函数 搜索过程中使用以下代价项: ```yaml reverse_penalty: 1.3 turn_penalty: 0.05 gear_change_penalty: 0.20 steering_change_penalty: 0.02 ``` 含义: | 参数 | 作用 | |---|---| | `reverse_penalty` | 倒车惩罚,越大越不愿意倒车 | | `turn_penalty` | 转弯惩罚,减少不必要转向 | | `gear_change_penalty` | 前进/倒车切换惩罚,减少频繁换挡 | | `steering_change_penalty` | 转向变化惩罚,让路径更稳定 | 当前默认允许倒车,但不会无理由倒车。只有倒车明显降低路径代价或满足空间约束时,才更容易出现倒车段。 ## 10. 重规划机制 重规划触发条件: - 收到新的 `/goal_pose`。 - 收到新的 `/global_costmap/costmap`。 - 收到新的 `/random_obstacles`。 - 收到清空锥桶命令。 新目标会立即规划。 障碍物和 costmap 更新会通过定时器限频: ```yaml replan_rate: 2.0 ``` 表示最多 2Hz 重规划,避免障碍物高频更新导致 CPU 被规划器占满。 单次规划时间预算: ```yaml planning_timeout_ms: 100.0 ``` 如果超过该时间还没有找到路径,则本次规划失败。 失败策略: - 如果旧路径仍然没有碰撞,则继续使用旧路径。 - 如果旧路径已被障碍物挡住,则发布空 `/plan`,跟踪器停车。 ## 11. 跟踪器与倒车执行 跟踪器读取 `/plan`,并根据路径点 yaw 判断当前段是前进还是倒车。 判断逻辑: ```text 如果 路径段方向 与 车身 yaw 方向相反,则认为是倒车段 ``` 默认速度: ```yaml linear_speed: 0.30 reverse_speed: 0.12 ``` 前进段: ```text /cmd_vel.linear.x > 0 ``` 倒车段: ```text /cmd_vel.linear.x < 0 ``` 实车启动时由 `planner/scripts/ackermann_cmd_bridge.py` 桥接到 `/ackermann_cmd`。 ## 12. 启动方式 构建: ```bash colcon build --packages-select planner source install/setup.bash ``` 启动规划器和跟踪器: ```bash ros2 launch planner grid_astar_theta_with_tracker.launch.py ``` 发布目标: ```bash ros2 topic pub -1 /goal_pose geometry_msgs/msg/PoseStamped \ "{header: {frame_id: map}, pose: {position: {x: 3.0, y: 1.5, z: 0.0}, orientation: {w: 1.0}}}" ``` 发布锥桶: ```bash ros2 topic pub -1 /random_obstacles std_msgs/msg/Float32MultiArray \ "{data: [1.2, 0.6, 0.15]}" ``` 清空锥桶: ```bash ros2 topic pub -1 /clear_random_obstacles std_msgs/msg/Bool "{data: true}" ``` 查看规划输出: ```bash ros2 topic echo /plan ``` 查看状态: ```bash ros2 topic echo /ackermann_lattice_status ``` ## 13. 当前方案优点 - 轻量,主要逻辑是 C++ 实现。 - 不依赖高频雷达融合或复杂局部规划器。 - 规划阶段考虑阿克曼最小转弯半径。 - 支持倒车和换挡。 - 支持运动过程中动态添加锥桶。 - 支持锥桶累计、去重和清空。 - 仍使用标准 `/plan nav_msgs/Path` 输出,接口简单。 ## 14. 当前限制 - `/plan` 本身没有显式 gear 字段,倒车语义依赖 Pose yaw 约定。 - 目前没有单独实现局部地图或局部避障器。 - costmap 的动态障碍处理依赖上游是否正确发布 `/global_costmap/costmap`。 - Hybrid A* 是轻量版本,不包含复杂解析扩展或高级平滑器。 - 如果地图非常大,`100ms` 超时可能导致复杂场景找不到路径,需要调整分辨率、搜索范围或超时时间。 ## 15. 推荐调参顺序 建议按以下顺序调参: 1. `min_turning_radius` 2. `robot_radius` 3. `localization_error` 4. `safety_margin` 5. `obstacle_merge_distance` 6. `primitive_length` 7. `planning_timeout_ms` 8. `reverse_penalty` 9. `lookahead_distance` 10. `reverse_speed` 如果小车过于保守、容易找不到路,优先降低: ```yaml localization_error safety_margin unknown_is_obstacle ``` 如果小车离障碍物太近,优先增加: ```yaml robot_radius safety_margin localization_error ``` 如果规划太慢,优先调整: ```yaml resolution planning_timeout_ms map_min_x/map_max_x/map_min_y/map_max_y ``` ## 16. 总结 这套方案本质上是一个面向低算力阿克曼小车的轻量 Hybrid A* 规划框架。它不处理底层传感器融合,只消费已经融合好的定位、代价地图和锥桶全局坐标。 相比普通二维 A*,它在规划阶段加入了车身朝向、最小转弯半径、前进/倒车和换挡代价,因此输出路径更符合阿克曼底盘运动约束。 相比完整 Nav2 Hybrid A* 或 MPPI,它更轻量、接口更简单、可控性更强,适合竞赛场景中地图规模较小、障碍物数量有限、需要快速重规划的任务。