位姿估计器就是用一段时间的位姿数据估计线速度和角速度,进而预测运动。
位姿估计器在Node
中有一个(一条轨迹一个),在LocalTrajectoryBuilder
也有一个。 Node
中的估计器只有在Node::PublishTrajectoryStates
有用,也就只有执行发布数据的时候才会将local_slam_pose
添加到其中。所以重点看后者。
看LocalTrajectoryBuilder::AddRangeData
函数,发现估计器会在两种情况下初始化:如果没在lua里启用IMU,就会在LocalTrajectoryBuilder2D
里直接初始化;如果在lua里启用了,会在收到IMU数据后再初始化,否则一直等待,此时也不会处理里程计数据。
从Cartographer流程图可以看出,PoseExtrapolator输入为scan match反馈的位姿、 IMU、里程计三个数据源,估计出下一时刻的位姿,用来作为下次scan match的初值。
scan match
可以获取较高精度的位姿,但是两次扫描匹配之间的运动只有里程计数据,如果里程计误差严重,将影响定位。
InitializeExtrapolator
初始化需要一个pose_queue_duration_
(1ms) 和 重力加速度时间常数(10s)。
初始化之后立刻添加一个位姿为0的位姿。添加位姿的过程:实例化imu_tracker_
,更新速度信息;处理imu和 odom 数据;初始化odometry_imu_tracker_
和extrapolator_imu_tracker_
都指向 imu_tracker_
1
2
3
4
5
6
7
8
9
10
11if(extrapolator_ != nullptr)
return;
CHECK(!options_.pose_extrapolator_options().use_imu_based());
// 创建一个PoseExtrapolator类型的位姿推理器
// pose queue 用于速度估计,pose_queue_duration是估计所用的时间
extrapolator_ = absl::make_unique<PoseExtrapolator>(
::cartographer::common::FromSeconds(
options_.pose_extrapolator_options().constant_velocity().pose_queue_duration() ),
options_.pose_extrapolator_options().constant_velocity().imu_gravity_time_constant() );
// 添加一个初始位姿,Rigid3d::Identity()对变换矩阵赋值,位移为0,旋转为1
extrapolator_->AddPose(time, transform::Rigid3d::Identity() );
PoseExtrapolator 的三条线
PoseExtrapolator中管理了三个队列:imu_data_
: 保存imu的双向队列 std::dequeodometry_data_
: 保存里程计数据的双向队列timed_pose_queue_
: 保存估计的位姿的双向队列 std::deque。只保存时间在pose_queue_duration_
范围内的位姿,旧的数据要删除掉。 位姿包括从IMU测量得到的Pose,以及从Scan Match输出的PoseObservation。
AddImuData 和 AddOdometryData
PoseExtrapolator
管理里程计数据和经过ImuTracker的IMU数据,这两个函数的调用的最终源头是LocalTrajectoryBuilder2D::AddOdometryData
和LocalTrajectoryBuilder2D::AddImuData
AddImuData
比较简单,主要是把IMU数据加入imu_data_
队列,清除旧的imu数据,保证队列中第2个数据的时间大于最后一个位姿的时间。AddOdometryData
除了添加里程计数据,删除比最新位姿旧的odom数据,还根据首尾的odometry数据,计算tracking 坐标系下的线速度和角速度。
AddPose
, ExtrapolatePose
, EstimateGravityOrientation
,这三个函数在LocalTrajectoryBuilder2D::AddRangeData
中调用
ExtrapolatePose
目的是预测time时刻tracking坐标系在local坐标系下的位姿。假设传感器数据之间,机器人以恒定线速度和恒定角速度移动( local坐标系 )
估计的线速度有2种方式组成,优先级参见ExtrapolateTranslation
:
- 由于里程计的更新速度更快,优先使用里程计获得线速度
- 采用两次scan-match位置进行微分获得
估计的角速度有3种方式,按优先级(参见AdvanceImuTracker
开始部分):
- 采优先使用IMU数据获取角速度
- 采用里程计进行微分
- 采用两次scan-match的朝向进行微分获得,频率最低
以上估计线速度和角速度所用的两次scan mathch的位姿的处理都在UpdateVelocitiesFromPoses
。如果出现了报警: Queue too short for velocity estimation
,可以尝试降低odometry_sampling_ratio
和 imu_sampling_ratio
,先从1降到0.5
最后针对线速度和角速度进行实时积分,输出当前时间的融合的估计位姿
AddPose
存入的是前端一帧一帧激光匹配的位姿,就是两个node之间的位姿。由于pose是由激光匹配产生的,而激光的频率是比IMU和odom要低的,所以这里pose的传入频率比IMU、odom低,比如传入5帧IMU,才插入一个pose。
位姿估计器十分相信scan match得到的位姿,如果遇到长走廊等环境相似的区域,这个位姿就会有问题
只在Node::PublishTrajectoryStates
和LocalTrajectoryBuilder2D::AddAccumulatedRangeData
调用。重点是后者,这个pose是Scan Match反馈给估计器以修正估计值, 也就是添加到timed_pose_queue_
队列
函数大致流程:
确保ImuTracker实例化:实例化时需要用到重力加速度常数,初始时间。重力常数主要用来做距离的修正。
将该位姿pose加入到
timed_pose_queue_
中,删除其中比加入时间早pose_queue_duration_
的位姿。根据
timed_pose_queue_
中的位姿估计速度:找出队列中最久和最新的位姿和时间, 通过除以时间得到线速度(linear_velocity_from_poses_
)和角速度(angular_velocity_from_poses_
)。 计算角速度的时候是使用四元数old_q.inverse() * new_q
得到old_q 到 new_q 的角位移,然后除以时间得到的。队列中的位姿时间要求如下:
一般选择1ms以内的姿态来估计,而且旧的数据要删除掉。进一步处理
ImuTracker
:如果到该时间之前没有imu数据,那么使用从poses中推断的角速度和一个假的重力来保障2d的稳定。如果有IMU,使用IMU数据进行姿态估计(imu_tracker_
),并将此时估计出的姿态用于计算 里程计线速度 和最新估计姿态 的初值(odometry_imu_tracker_
extrapolation_imu_tracker_
)
GetLastPoseTime
1 | common::Time PoseExtrapolator::GetLastPoseTime() const { |
就是返回队列最后一个pose对应的时间
在位姿融合中,因为IMU和里程计的采集速率比scan match快很多,因而在一定时间段内,可能没有scan match估计的位姿数据,这时需要通过 IMU 和里程计数据进行推断,但因为这两个传感器存在相对激光扫描较大的累积误差,所以需要对两者进行一些更新和初始化消除累积误差
最后以里程计数据为例,补上整个的处理流程
为什么每次新建地图的角度不同:每次开始的方向不同,从第一帧雷达数据到来时,前端子图的坐标原点来自ExtrapolatePose
,此时机器人发生旋转,ExtrapolatePose
输出的位姿朝向会不同;另外还有IMU的数据问题。