本系列博客旨在记录自己在学习百度无人驾驶开源框架Apollo的心得和体会,欢迎大家阅读和点赞,并提出宝贵意见,大家相互学习,如需转载,请注明出处,谢谢!
文章目录
- 1、前言
- 2、车辆传感器布局
- 3、传感器外参标定关系
- 4、具体实现细节
- 4.1 外参标定结果的管理与维护
- 4.2 本车全局位姿的发布
- 4.3 坐标变换树的应用
1、前言
对于一个自动驾驶系统来说,一般都会有激光雷达、毫米波雷达、超声波雷达、相机、IMU/GPS等各种传感器,而每个传感器都有自己的坐标系定义,为了描述自动驾驶车辆在地图中的位置,一般还涉及到全局定位,这就还会包含全局坐标系,也称为世界坐标系。这些错综复杂的坐标系定义,不仅涉及到了不同传感器之间的内外参标定关系,也包含了许多与坐标系转换之间的操作,需要我们重点掌握和理解。
由于来自不同传感器的数据都是带有坐标系,即数据的表示其实都是某个特定的坐标系的,不同传感器之间的外参标定等内容本博客暂时不说,本篇博客的主要目的是带大家整体理解和掌握Apollo中涉及到的各种坐标系定义和使用注意事项,这能帮我们在阅读Apollo源码特别是感知部分代码时快速理解各种坐标变换的含义和目的。
注: 本篇博客主要是对Apollo 5.5的感知模块相关坐标系进行分析,Apollo 5.0应该差别不大。
2、车辆传感器布局
首先我们整体来看一下Apollo官方提供的传感器布置图:
从上面这张图中我们可以看出Apollo的Lincoln MKZ自动驾驶车辆上在车上不同位置布置有数量不同、种类繁多的传感器。
光从上面这张俯视图中我们可能没法清楚看出不同传感器的安装位置及坐标系定义,我们可以看下面这张图:
注意区分其中相机坐标系和激光雷达坐标系的方向定义,其中激光雷达坐标系X轴向前,Y轴向左,Z轴向上,而相机坐标系Z轴向前,X轴向右,Y轴向下。另外在车的中间位置有一个紫色的传感器,上面写着
【参考】:
1、Apollo Architecture
2、
3、传感器外参标定关系
了解了Apollo自动驾驶车辆上整体的传感器布局后,接下来我们就来看下这些传感器所定义的坐标系以及他们的之间的转换关系,其实也就是标定结果中的
对于标定结果的描述,常用刚体变换中的旋转变换和平移变换来表示,其中涉及到
下面的坐标系变换关系图中箭头起点表示
从图中可以看出,Apollo将激光雷达、相机都标定到了
4、具体实现细节
理解了传感器的布局以及Apollo中对这些传感器所完成的标定任务之后,下一个关键的问题就是Apollo中是如何管理和维护这些传感器外参的标定关系以及本车的全局位姿信息呢?又是如何在程序中实现方便查询这些变换关系,完成我们想要的坐标变换呢?
4.1 外参标定结果的管理与维护
我们知道对于不同传感器之间的外参变换关系来说,由于它们都是固连在自动驾驶车辆上而且在车辆运动过程中它们的相对位置并没有发生变化,所以它们的变换关系是固定不变的。对于拥有这种特征的变换关系,在ROS中我们可以发布
Apollo中使用transform模块完成对传感器外参标定结果的发布工作。
为了方便分析,下面贴出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | extrinsic_file { frame_id: "novatel" child_frame_id: "velodyne128" file_path: "/apollo/modules/drivers/velodyne/params/velodyne128_novatel_extrinsics.yaml" enable: true } extrinsic_file { frame_id: "localization" child_frame_id: "novatel" file_path: "/apollo/modules/localization/msf/params/novatel_localization_extrinsics.yaml" enable: true } extrinsic_file { frame_id: "velodyne128" child_frame_id: "front_6mm" file_path: "/apollo/modules/perception/data/params/front_6mm_extrinsics.yaml" enable: true } extrinsic_file { frame_id: "velodyne128" child_frame_id: "front_12mm" file_path: "/apollo/modules/perception/data/params/front_12mm_extrinsics.yaml" enable: true } extrinsic_file { frame_id: "novatel" child_frame_id: "lidar16_front_up" file_path: "/apollo/modules/drivers/velodyne/params/lidar16_front_up_novatel_extrinsics.yaml" enable: true } extrinsic_file { frame_id: "novatel" child_frame_id: "lidar16_front" file_path: "/apollo/modules/drivers/velodyne/params/lidar16_front_novatel_extrinsics.yaml" enable: true } extrinsic_file { frame_id: "novatel" child_frame_id: "lidar16_rear_left" file_path: "/apollo/modules/drivers/velodyne/params/lidar16_rear_left_novatel_extrinsics.yaml" enable: true } extrinsic_file { frame_id: "novatel" child_frame_id: "lidar16_rear_right" file_path: "/apollo/modules/drivers/velodyne/params/lidar16_rear_right_novatel_extrinsics.yaml" enable: true } extrinsic_file { frame_id: "velodyne128" child_frame_id: "radar_front" file_path: "/apollo/modules/perception/data/params/radar_front_extrinsics.yaml" enable: true } extrinsic_file { frame_id: "velodyne128" child_frame_id: "radar_rear" file_path: "/apollo/modules/perception/data/params/radar_rear_extrinsics.yaml" enable: true } |
从中可以看出该配置文件定义了
在
4.2 本车全局位姿的发布
除了transform模块中的
Apollo中通过localization模块中的
发布函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | void LocalizationMsgPublisher::PublishPoseBroadcastTF( const LocalizationEstimate& localization) { // broadcast tf message apollo::transform::TransformStamped tf2_msg; auto mutable_head = tf2_msg.mutable_header(); mutable_head->set_timestamp_sec(localization.measurement_time()); mutable_head->set_frame_id(broadcast_tf_frame_id_); tf2_msg.set_child_frame_id(broadcast_tf_child_frame_id_); auto mutable_translation = tf2_msg.mutable_transform()->mutable_translation(); mutable_translation->set_x(localization.pose().position().x()); mutable_translation->set_y(localization.pose().position().y()); mutable_translation->set_z(localization.pose().position().z()); auto mutable_rotation = tf2_msg.mutable_transform()->mutable_rotation(); mutable_rotation->set_qx(localization.pose().orientation().qx()); mutable_rotation->set_qy(localization.pose().orientation().qy()); mutable_rotation->set_qz(localization.pose().orientation().qz()); mutable_rotation->set_qw(localization.pose().orientation().qw()); tf2_broadcaster_.SendTransform(tf2_msg); } |
其中
1 2 3 | DEFINE_string(broadcast_tf_frame_id, "world", "world frame id in tf"); DEFINE_string(broadcast_tf_child_frame_id, "localization", "localization frame id in tf"); |
至此,这颗完整的坐标系变换树就建立起来了。接下来我们以感知模块为例来看下如何利用这颗建立起来的坐标变换树来完成我们需要的坐标变换。