由于SLAM过程中需要用到不同的传感器对环境进行感知和观测,而每一个传感器都应一种坐标系,所以在整个SLAM过程中涉及到非常多次的坐标变换,想要搞清楚SLAM的过程,需要对这些坐标变换有充分的理解,本文主要记录一下我对于激光SLAM中坐标变换的理解(ROS下)
在ROS中,常见的坐标系有map,odom,base_link,laser_link,下面分别对这几个坐标系之间的变换进行阐述
base_link
laser_link
两个坐标系的理解:
base_link一般就是指以机器人为原点的坐标系,可以直接理解成机器人本身,laser_link指以激光雷达为中心的坐标系(其余类似于imu这些装在机器人身上的传感器坐标系都类似laser_link),这两个坐标系之间的变换比较简单,一般都是静态的,因为传感器一旦安装在机器人身上就不会再发生移动,所以只需要发布一个静态的TF变换即可完成base_link到laser_link之间的变换。
如图所示,雷达扫描到前方3m处有一个障碍物,但是对于机器人来说,该障碍物在前方的6m处,造成这个差距的原因就是两者在不同的坐标系下观测同一障碍物,坐标变换的目的就是把这种观测进行统一,要么在雷达坐标系下认为3m处有障碍,要么在机器人坐标系下认为6m处有障碍。
坐标变换何时用到?
当机器人身上的传感器采集到数据时,这些数据都是位于各个传感器坐标系下的,比如雷达通过icp算法得到的行进里程,如果想要在世界坐标下绘制机器人的运动轨迹,就需要用到laser_link到base_link的坐标变换,把里程数据从雷达坐标系转换到机器人坐标系,再逐层向odom和map进行转换。
谁来发布?
一般由我们直接编写静态坐标变换来发布
odom
base_link
两个坐标系的理解:
odom是里程计坐标系,在这个坐标系下已经可以描述机器人的运动了,由机器人里程计发布的信息转换到odom坐标系后,就能够直观的反映机器人的运动里程。odom与base_link之间的坐标变换就由里程计进行发布。机器人运动的时候,是相对于odom这个系在运动,机器人往前运动了10m,是指在odom这个系上运动了10m,但是注意odom和map这两个坐标系不是重合的,要知道机器人在map上运动了多少,还需要做一个odom到map的转换。odom和map不重合的原因就是存在误差,在odom上前进了10m是传感器测出来的,但是真实移动的距离不一定是10m。
坐标变换何时用到?
机器人的里程计,如雷达等,通过icp等算法获得了机器人在两帧数据之间的运动(x,y,theta),把一段时间内的(x,y,theta)进行累加,就获得了这段时间内机器人的运动轨迹和里程,但是(x,y,theta)是传感器得到的,处于laser_link下,所以需要转换到base_link再转换到odom才能够得到相应的轨迹和里程。
谁来发布?
一般由里程计发布
map
odom
两个坐标系的理解:
map是世界坐标,一般我们就站在map的角度观测机器人的运动,机器人在SLAM过程中建立的地图也是在map坐标系下的,ROS中机器人的运动可以理解成发布从base_linkmap的坐标变换,让base_link在map中动起来,map到base_link的坐标转换是被定位模块计算出来的。但定位模块并不发布map到base_link的转换,相反它先接受从odom到base_link的转换, 再计算并广播map到odom的位置转换关系,是一个间接的过程。
坐标变换何时用到?
map主要在建图时用到,建图的过程实际上就是在map中的正确位置扫描周围信息,建立栅格地图的过程,而正确的位置,就是通过把odom中的位置变换到map中得到的。
谁来发布?
一般由定位模块进行发布
代码中如何体现坐标变换?
在代码中,一般通过tf这个库来进行坐标变换的处理,通过建立一个变换矩阵T来存储两个坐标系之间的坐标变换,在进行坐标变换时,遵循以下步骤:
- 把数据转换为TF所需要的格式,从TF中创建一个容器V存储这个数据
- 通过乘以变换矩阵T的方式进行坐标变换,V(base_link) = V(laser_link) * T(laser_link to base_link)
- 得到V(base_link)后再把数据从TF格式转换为原始格式,供进一步处理
举例:
tf2::Transform corr_ch;
if (output_.valid)
{
// 将雷达数据转换为TF格式数据corr_ch_1
tf2::Transform corr_ch_l;
CreateTfFromXYTheta(output_.x[0], output_.x[1], output_.x[2], corr_ch_l);
// 将雷达坐标系下的数据 转换成 base_link坐标系下
corr_ch = base_to_laser_ * corr_ch_l * laser_to_base_;
// 将base_link坐标系下的数据,转换到odom坐标系下
base_in_odom_ = base_in_odom_keyframe_ * corr_ch;
}