1. 项目概述:将Apollo规划模块移植到ROS生态
在自动驾驶领域,Apollo的规划模块一直以其稳定性和成熟度著称,而ROS(Robot Operating System)则是机器人开发的事实标准框架。将Apollo的规划算法移植到ROS环境,可以让开发者在一个更灵活、更开放的生态系统中利用Apollo的强大功能。
这个项目的核心目标是将Apollo的规划模块完整地移植到ROS中,使其能够与Lanelet2地图框架无缝协作。Lanelet2是Autoware项目中使用的高精地图格式,相比Apollo原生的地图格式,它更加轻量级且易于修改。通过这种移植,开发者可以在ROS环境中快速验证和迭代自动驾驶规划算法,而无需完全依赖Apollo的整套系统。
提示:这个移植过程特别适合那些希望在ROS环境中快速验证规划算法,但又不想从头开发整套规划系统的团队。它保留了Apollo规划的核心优势,同时提供了ROS生态的灵活性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境配置与依赖管理
2.1 基础环境准备
首先需要准备一个干净的Ubuntu 20.04系统,这是ROS Noetic的官方支持版本。安装ROS Noetic完整版:
bash复制sudo apt update
sudo apt install ros-noetic-desktop-full
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
接下来安装必要的依赖项:
bash复制sudo apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential
sudo rosdep init
rosdep update
2.2 Lanelet2定制版本安装
由于官方Lanelet2仓库不完全兼容ROS的消息类型,我们需要使用一个经过修改的分支:
bash复制mkdir -p ~/autoware_ws/src
cd ~/autoware_ws/src
git clone --branch ros_compatible https://github.com/your_fork/lanelet2.git
cd ..
rosdep install --from-paths src --ignore-src -y
catkin_make
这个定制版本解决了ROS消息类型与Lanelet2原生数据结构的兼容性问题,特别是几何类型转换和坐标系处理方面。
2.3 Protobuf版本管理
Apollo规划模块依赖特定版本的Protobuf(3.19.4),而ROS Noetic默认可能安装不同版本。为了避免冲突,我们需要手动安装并指定版本:
bash复制wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protobuf-cpp-3.19.4.tar.gz
tar -xzf protobuf-cpp-3.19.4.tar.gz
cd protobuf-3.19.4
./configure --prefix=/usr/local/protobuf-3.19.4
make -j$(nproc)
sudo make install
然后在工程的顶层CMakeLists.txt中明确指定使用这个版本:
cmake复制find_package(Protobuf REQUIRED 3.19.4 EXACT)
include_directories(${Protobuf_INCLUDE_DIRS})
link_directories(/usr/local/protobuf-3.19.4/lib)
注意:Protobuf版本不匹配是编译失败的最常见原因,务必确保所有相关组件都使用相同版本。
3. 核心移植工作
3.1 地图服务适配
Apollo原生的ReferenceLineProvider需要替换为Lanelet2的地图服务。关键点在于路径数据的转换:
cpp复制void convertLaneletPath(const lanelet::LaneletPath& path, std::vector<common::PathPoint>* points) {
for (const auto& p : path) {
auto centerline = p.centerline();
for (const auto& pt : centerline) {
points->emplace_back(pt.x(), pt.y(), pt.z());
// 重新计算s值确保与Apollo的期望一致
if (!points->empty()) {
auto& last = points->back();
last.set_s(common::util::Distance(last, points->back()));
}
}
}
}
这个转换函数有几个关键细节:
- z轴处理:Apollo和Lanelet2对高程的处理方式不同,可能需要额外转换
- s值计算:必须重新计算累积距离,不能直接使用Lanelet2的投影距离
- 点密度:Apollo规划对路径点密度有特定要求,可能需要插值
3.2 坐标系转换
Apollo使用ENU(东-北-天)坐标系,而Lanelet2通常使用UTM或其他局部坐标系。需要在初始化时建立正确的转换关系:
cpp复制class CoordinateTransformer {
public:
void initialize(const lanelet::GPSPoint& origin) {
// 初始化坐标系转换参数
origin_ = convertToUTM(origin);
}
Point3d toLocal(const lanelet::GPSPoint& gps) const {
auto utm = convertToUTM(gps);
return {utm.x - origin_.x, utm.y - origin_.y, utm.z};
}
private:
Point3d origin_;
};
提示:在实际应用中,建议使用tf2库管理坐标系转换,这样可以利用ROS现有的工具链进行可视化调试。
4. 调试与可视化
4.1 Rviz可视化插件
在ROS中,使用Rviz进行可视化比Apollo原生的工具更加灵活。下面是一个发布规划结果的MarkerArray示例:
python复制def publish_debug_markers(ego_pose, trajectory):
marker_array = MarkerArray()
# 轨迹线
marker = Marker()
marker.header.frame_id = "map"
marker.type = Marker.LINE_STRIP
marker.scale.x = 0.1
marker.color.a = 0.8
marker.color.r = 1.0
for point in trajectory:
p = Point()
p.x = point.x
p.y = point.y
marker.points.append(p)
marker_array.markers.append(marker)
# 车辆位置
car_marker = Marker()
car_marker.type = Marker.CUBE
car_marker.pose = ego_pose
car_marker.scale.x = 4.9 # 车长
car_marker.scale.y = 1.8 # 车宽
car_marker.color.b = 1.0
car_marker.color.a = 0.5
marker_array.markers.append(car_marker)
debug_pub.publish(marker_array)
这个可视化工具对于调试轨迹平滑性、碰撞检测等问题非常有用。建议扩展它来显示更多信息,如速度剖面、曲率变化等。
4.2 性能分析与内存检测
使用Valgrind进行内存检测:
bash复制valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \
--log-file=valgrind.out ./planning_node
常见问题包括:
- Protobuf对象的重复注册
- 回调函数中的内存泄漏
- 静态变量的不当使用
对于性能分析,可以使用gperftools:
bash复制LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
CPUPROFILE=planning.prof ./planning_node
然后使用pprof生成分析报告:
bash复制pprof --web ./planning_node planning.prof
5. 功能扩展与优化
5.1 紧急制动逻辑
在Apollo的规划框架中添加紧急制动功能:
cpp复制class EmergencyBrakeDecider : public Decider {
public:
void UpdateBrakeStatus(const LocalizationEstimate& localization) override {
if (collision_checker_.ImminentCollision()) {
double current_speed = localization.speed();
for (auto& point : *trajectory_->mutable_trajectory_point()) {
// 使用tanh函数平滑减速
double target_speed = current_speed * 0.5 * (1 - tanh(10*(point.relative_time()-0.5)));
point.set_v(std::max(0.0, target_speed));
adjustSteeringForEmergency(point);
}
}
}
};
这个实现有几个关键点:
- 使用tanh函数实现平滑减速,避免阶跃变化
- 同时调整转向角度防止甩尾
- 保留最小速度为0,避免出现负值
5.2 动态地图加载
为了避免全量加载高精地图的内存消耗,可以实现动态地图加载:
cpp复制class DynamicMapLoader {
public:
void updateArea(const Point3d& center, double radius) {
auto new_map = lanelet::load(lanelet_map_path_,
BoundingBox2d{
{center.x - radius, center.y - radius},
{center.x + radius, center.y + radius}
});
std::lock_guard<std::mutex> lock(map_mutex_);
current_map_ = std::move(new_map);
}
private:
std::mutex map_mutex_;
lanelet::LaneletMapPtr current_map_;
std::string lanelet_map_path_;
};
这个功能特别适合大型园区或城市环境,可以显著降低内存占用。
6. 测试与验证
6.1 仿真环境配置
使用LGSVL或CARLA仿真器进行测试。建议从简单场景开始:
- 直线道路上的匀速行驶
- 车道保持测试
- 变道测试
- 障碍物避让测试
在LGSVL中,可以通过修改传感器配置来匹配你的感知模块输出:
json复制"Perception": {
"Type": "GroundTruth",
"Filters": ["Obstacle", "Lane"],
"Range": 100.0
}
6.2 轨迹质量评估
评估规划结果的关键指标:
- 曲率连续性:检查曲率变化是否平滑
- 加速度限制:确保横向和纵向加速度在合理范围内
- 舒适性指标:jerk(加速度变化率)的大小
- 执行偏差:规划轨迹与控制实际执行轨迹的差异
可以使用rqt_plot实时监控这些指标:
bash复制rosrun rqt_plot rqt_plot /planning/trajectory/curvature /planning/trajectory/acceleration
7. 性能调优经验
7.1 规划周期调整
Apollo默认使用100ms的规划周期,但在ROS中可能需要调整:
cpp复制ros::Rate rate(10); // 10Hz = 100ms
while (ros::ok()) {
planner.RunOnce();
rate.sleep();
}
在某些情况下,将周期调整为150ms(约6.67Hz)反而能获得更好的效果,特别是当控制模块的预测时域较长时。
7.2 内存优化技巧
- 使用对象池管理频繁创建销毁的对象
- 避免在回调函数中创建大型临时对象
- 使用智能指针管理资源
- 对Protobuf消息进行复用
一个典型的内存优化示例:
cpp复制class MessagePool {
public:
std::shared_ptr<PlanningMessage> getMessage() {
std::lock_guard<std::mutex> lock(mutex_);
if (pool_.empty()) {
return std::make_shared<PlanningMessage>();
}
auto msg = pool_.back();
pool_.pop_back();
msg->Clear();
return msg;
}
void returnMessage(std::shared_ptr<PlanningMessage> msg) {
std::lock_guard<std::mutex> lock(mutex_);
pool_.push_back(msg);
}
private:
std::vector<std::shared_ptr<PlanningMessage>> pool_;
std::mutex mutex_;
};
8. 常见问题排查
8.1 编译问题
-
Protobuf版本冲突:
- 症状:链接错误或运行时崩溃
- 解决方案:确保所有组件使用相同版本,清理旧版本
-
缺少ROS消息依赖:
- 症状:找不到消息头文件
- 解决方案:检查package.xml中的依赖声明
8.2 运行时问题
-
轨迹跳变:
- 可能原因:坐标系转换错误或s值计算不一致
- 检查方法:可视化原始Lanelet2路径和转换后的路径
-
规划模块响应慢:
- 可能原因:地图太大或算法复杂度高
- 优化方法:实现动态地图加载或优化搜索算法
8.3 控制执行问题
-
车辆震荡:
- 可能原因:规划轨迹不够平滑或控制参数不匹配
- 调试方法:检查轨迹的曲率和加速度变化
-
紧急制动不触发:
- 可能原因:碰撞检测参数过于保守
- 调整方法:重新标定传感器参数和安全距离
在实际项目中,我们通常会建立一个问题排查清单,记录常见问题现象和对应的解决方案,这对团队协作特别有帮助。
