在上一篇文章对local_trajectory_builder的部分代码进行了阅读,接下来,将对ScanMatch及InserttoSubmap进行代码的阅读。代码位于Cartographer/mapping/internal/2d/scan_matching/local_trajectory_builder_2d.cc
1.扫描匹配,ScanMatch 在扫描匹配方面,Cartographer采用的是CSM(相关性扫描匹配)+基于梯度优化的方法(ceres)来实现,CSM可以简单地理解为暴力搜索,即每一个激光数据与子图里的每一个位姿进行匹配,直到找到最优位姿,进行三层for循环,不过谷歌采用多分辨率搜索的方式,来降低计算量,在后续也会进行解读。基于优化的方法是谷歌的ceres库来实现,原理就是构造误差函数,使其对最小化,其精度高,但是对初始值敏感。因此需要结合两种方法
//进行scanMatch,CSM+Ceres,
std::unique_ptr LocalTrajectoryBuilder2D::ScanMatch(
const common::Time time,
const transform::Rigid2d& pose_prediction,
const sensor::PointCloud& filtered_gravity_aligned_point_cloud)
{
if (active_submaps_.submaps().empty())
{
return absl::make_unique(pose_prediction);
}//进行匹配的子图,采用激光数据与子图进行匹配的方法,因此肯定要有子图
std::shared_ptr matching_submap =
active_submaps_.submaps().front();
// The online correlative scan matcher will refine the initial estimate for
// the Ceres scan matcher.
// 初始的位姿,由PoseExtrapolator预测的初始位姿pose_prediction
transform::Rigid2d initial_ceres_pose = pose_prediction;
//是否进行real-time csm的匹配,默认是fasle但建议使用
//采用CSM(在线相关扫描匹配器将)对Ceres扫描匹配器的初始估计进行细化。
返回一个更好的值initial_ceres_poseif (options_.use_online_correlative_scan_matching())
{
const double score = real_time_correlative_scan_matcher_.Match(
pose_prediction,
filtered_gravity_aligned_point_cloud,
*matching_submap->grid(),
&initial_ceres_pose);
kRealTimeCorrelativeScanMatcherScoreMetric->Observe(score);
//返回一个更好的值initial_ceres_pose,传给ceres进行优化
}//进行ceres的匹配.--即基于优化的方法来进行匹配.
auto pose_observation = absl::make_unique();
ceres::Solver::Summary summary;
ceres_scan_matcher_.Match(pose_prediction.translation(), initial_ceres_pose,
filtered_gravity_aligned_point_cloud,
*matching_submap->grid(), pose_observation.get(),
&summary);
// 统计误差,如果扫描获得位姿,则更新当前时刻的状态,
if (pose_observation)
{
kCeresScanMatcherCostMetric->Observe(summary.final_cost);
const double residual_distance =//误差
(pose_observation->translation() - pose_prediction.translation())
.norm();
kScanMatcherResidualDistanceMetric->Observe(residual_distance);
const double residual_angle =
std::abs(pose_observation->rotation().angle() -
pose_prediction.rotation().angle());
kScanMatcherResidualAngleMetric->Observe(residual_angle);
}
return pose_observation;
//返回扫描匹配后的位姿
}
阅读代码可以知道,CSM将姿态外推器插值得到的位姿进行细分优化,然后传给ceres进行扫描匹配,来解决基于优化的方法对初始值敏感的缺点。
2.InsertToSubmap ScanMatch获得了最优的匹配位姿,接下来InsertToSubmap将按照获得的最优位姿,把激光数据插入子图。
//插入到局部子图中.
std::unique_ptr
LocalTrajectoryBuilder2D::InsertIntoSubmap(
const common::Time time, const sensor::RangeData& range_data_in_local,
const sensor::PointCloud& filtered_gravity_aligned_point_cloud,
const transform::Rigid3d& pose_estimate,
const Eigen::Quaterniond& gravity_alignment)
{//如果没有被MotionFilter滤掉
if (motion_filter_.IsSimilar(time, pose_estimate))
{
return nullptr;
}//把当前帧数据,插入到地图中.
//调用的插入函数是子图的工具函数
// 把active_submaps_中维护的Submap列表放到insertion_submaps这个向量中
std::vector::shared_ptr> insertion_submaps =
active_submaps_.InsertRangeData(range_data_in_local);
//返回匹配结果
return absl::make_unique(InsertionResult{
std::make_shared(TrajectoryNode::Data{
time,
gravity_alignment,
filtered_gravity_aligned_point_cloud,
{},// 'high_resolution_point_cloud' is only used in 3D.
{},// 'low_resolution_point_cloud' is only used in 3D.
{},// 'rotational_scan_matcher_histogram' is only used in 3D.
pose_estimate}),
std::move(insertion_submaps)});
}
将激光束插入子图实际是调用的子图的插入工具函数,后面我们也将进行阅读。
3.AddImuData加入IMU数据
//加入IMU数据,用来初始化和更新PoseExtrapolator
void LocalTrajectoryBuilder2D::AddImuData(const sensor::ImuData& imu_data)
{
CHECK(options_.use_imu_data()) << "An unexpected IMU packet was added.";
InitializeExtrapolator(imu_data.time);
extrapolator_->AddImuData(imu_data);
}
4.AddOdometryData
//加入里程计数据,用来初始化更新PoseExtrapolator
void LocalTrajectoryBuilder2D::AddOdometryData(
const sensor::OdometryData& odometry_data)
{
if (extrapolator_ == nullptr)
{
// Until we've initialized the extrapolator we cannot add odometry data.
LOG(INFO) << "Extrapolator not yet initialized.";
return;
}extrapolator_->AddOdometryData(odometry_data);
}
5.初始化PoseExtrapolator.
/初始化PoseExtrapolator.
void LocalTrajectoryBuilder2D::InitializeExtrapolator(const common::Time time)
{
if (extrapolator_ != nullptr)
{
return;
}// We derive velocities from poses which are at least 1 ms apart for numerical
// stability. Usually poses known to the extrapolator will be further apart
// in time and thus the last two are used.
// 用相差至少1ms的两个位姿来计算速度.
constexpr double kExtrapolationEstimationTimeSec = 0.001;
// TODO(gaschler): Consider using InitializeWithImu as 3D does.
extrapolator_ = absl::make_unique(
::cartographer::common::FromSeconds(kExtrapolationEstimationTimeSec),
options_.imu_gravity_time_constant());
extrapolator_->AddPose(time, transform::Rigid3d::Identity());
}
小节 【Cartographer源码阅读03-local_trajectory_builder.cc(2)-前端匹配】到目前为止,local_trajectory_builder的核心代码阅读完成,从总体上对前端匹配有了了解,具体的实现还有待进一步探索,不如说位姿外推器,csm相关性扫描匹配,ceres,子图的插入数据的工具函数等。我们依然按照传感器数据从输入到输出的流程来阅读代码,其中也会对其分支进行阅读。当我们按照这一个流程走过一遍,代码也就可以读懂了。
推荐阅读
- 概率机器人笔记|概率机器人(测距仪的地图匹配模型)
- Cartorgrapher源码阅读04- PoseExtrapolator-前端匹配-位姿外插器
- Linux|移植安装cartographer_ros
- 3D-2D: PnP ——SLAM14讲内容
- SLAM|3D Vision、SLAM求职宝典 | 图像处理篇(C)
- ROS|思岚rplidar A1激光点云数据读取
- 三维空间刚体运动1(旋转矩阵与变换矩阵)
- SLAM学习(Solidworks2018导出小车URDF模型下遇到的问题)