一、整体介绍
这部分是整个系统的核心,因为这个系统就是建图的呀,建图的核心就是综合所有信息进行优化呀。我们前面几节介绍的所有的信息都会发送到这个节点中来供它使用。这部分的代码在文件apps/hdl_graph_slam_nodelet.cpp中。
这部分的代码看着挺多,如果我们能够对它所有的函数分类,就很容易梳理它的内容了。
我们刚才提到,它需要接收之前几节介绍的所有的信息,并加入概率图中,所以我们以信息为切入点,去梳理它的内容。
我们先看都包含哪些信息:
1)前端里程计传入的位姿和点云
2)gps信息
3)Imu信息
4)平面拟合的参数信息
处理一个信息包括以下步骤:
1)在对应的callback函数中接收信息,并放入相应的队列
2)根据时间戳对队列中的信息进行顺序处理,加入概率图
四条信息,每条信息都执行两个步骤,这就已经包含了本文件大部分的代码。
剩下的就是一些其他内容,我们进一步梳理:
1)执行图优化,这是一个定时执行的函数,闭环检测也在这个函数里
2)生成全局地图并定时发送,即把所有关键帧拼一起,得到全局点云地图,然后在一个定时函数里发送到rviz上去
3)在rviz中显示顶点和边,如果运行程序,会看到rviz中把概率图可视化了
至此,整个文件的核心框ros架就梳理完了,剩下的一些细枝末节的东西应该都能看懂。
下面我们就按照这个框架对各部分内容进行梳理,这次梳理我们遵照以下原则:
1)和消息相关的代码我们之前在各自对应的章节中都梳理过了,这里只是使用,所以他们的代码就不贴出来了,不然会把本节内容搞得繁琐,反而不利于整体理解
2)概率图可视化的代码就不解释了,和核心算法无关
3)和算法无关的一些ros操作相关的东西,也不在这里解释了
二、代码详解
1. 前端里程计消息处理
还记得那两个步骤吗?
1)接收消息:cloud_callback
2)处理消息队列:flush_keyframe_queue
2. gps消息
1)接收消息:nmea_callback + navsat_callback + gps_callback
2)处理消息队列:flush_gps_queue
3. imu消息
1)接收消息:imu_callback
2)处理消息队列:flush_imu_queue
4. 地面拟合参数
1)接收消息:floor_coeffs_callback
2)处理消息队列:flush_floor_queue
5. 执行图优化
函数名是optimization_timer_callback,这个函数除了执行图优化意外,还做了以下两点:
1)闭环检测
// loop detection
std::vector<Loop::Ptr> loops = loop_detector->detect(keyframes, new_keyframes, *graph_slam);
for(const auto& loop : loops) {
Eigen::Isometry3d relpose(loop->relative_pose.cast<double>());
Eigen::MatrixXd information_matrix = inf_calclator->calc_information_matrix(loop->key1->cloud, loop->key2->cloud, relpose);
auto edge = graph_slam->add_se3_edge(loop->key1->node, loop->key2->node, relpose, information_matrix);
graph_slam->add_robust_kernel(edge, private_nh.param<std::string>("loop_closure_edge_robust_kernel", "NONE"), private_nh.param<double>("loop_closure_edge_robust_kernel_size", 1.0));
}
检测闭环并加到了概率图中,当然实在优化之前
2)生成简化版关键帧
还记得我们在关键帧那一节介绍的简化版关键帧(KeyFrameSnapshot)吗?
它从KeyFrame类中取出位姿(当然是优化之后的位姿)和点云,供地图拼接使用,这样我们得到的就是位姿经过优化的地图了。
std::vector<KeyFrameSnapshot::Ptr> snapshot(keyframes.size());
std::transform(keyframes.begin(), keyframes.end(), snapshot.begin(),
[=](const KeyFrame::Ptr& k) {
return std::make_shared<KeyFrameSnapshot>(k);
});
keyframes_snapshot_mutex.lock();
keyframes_snapshot.swap(snapshot);
keyframes_snapshot_mutex.unlock();
6. 生成地图并定时发送
生成地图并定时发送很明显包括两步:生成地图、定时发送,哈哈
1)生成地图
就是使用简化版关键帧进行拼接,这部分代码在文件src/hdl_graph_slam/map_cloud_generator.cpp中,更具体点说,就是里面的generate函数,其实就是个拼接的过程。
pcl::PointCloud<MapCloudGenerator::PointT>::Ptr MapCloudGenerator::generate(const std::vector<KeyFrameSnapshot::Ptr>& keyframes, double resolution) const {
if(keyframes.empty()) {
std::cerr << "warning: keyframes empty!!" << std::endl;
return nullptr;
}
pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>());
cloud->reserve(keyframes.front()->cloud->size() * keyframes.size());
for(const auto& keyframe : keyframes) {
Eigen::Matrix4f pose = keyframe->pose.matrix().cast<float>();
for(const auto& src_pt : keyframe->cloud->points) {
PointT dst_pt;
dst_pt.getVector4fMap() = pose * src_pt.getVector4fMap();
dst_pt.intensity = src_pt.intensity;
cloud->push_back(dst_pt);
}
}
cloud->width = cloud->size();
cloud->height = 1;
cloud->is_dense = false;
pcl::octree::OctreePointCloud<PointT> octree(resolution);
octree.setInputCloud(cloud);
octree.addPointsFromInputCloud();
pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>());
octree.getOccupiedVoxelCenters(filtered->points);
filtered->width = filtered->size();
filtered->height = 1;
filtered->is_dense = false;
return filtered;
}
2)定时发送
这部分内容在apps/hdl_graph_slam_nodelet.cpp文件中,简单,不解释了
void map_points_publish_timer_callback(const ros::WallTimerEvent& event) {
if(!map_points_pub.getNumSubscribers()) {
return;
}
std::vector<KeyFrameSnapshot::Ptr> snapshot;
keyframes_snapshot_mutex.lock();
snapshot = keyframes_snapshot;
keyframes_snapshot_mutex.unlock();
auto cloud = map_cloud_generator->generate(snapshot, 0.05);
if(!cloud) {
return;
}
cloud->header.frame_id = map_frame_id;
cloud->header.stamp = snapshot.back()->cloud->header.stamp;
sensor_msgs::PointCloud2Ptr cloud_msg(new sensor_msgs::PointCloud2());
pcl::toROSMsg(*cloud, *cloud_msg);
map_points_pub.publish(cloud_msg);
}
=============分割线===========
OK,整个hdl_graph_slam系统的介绍到这里就结束了,前后一共八篇文章。最后总结一下吧,虽然hdl_graph_slam的性能其实不是很好,具体原因在于帧间匹配和闭环检测的精度都不够,它在这两个环节的缺点我们在对应的章节已经解释过了。但是,但是,但是,我想说的是对于一套开源系统来讲,不能指望它拿来即用,小的缺点很容易改,都已经指出问题了,就那几行代码,改一改还不容易吗。我们更应该关心它的架构,这套系统的代码模块画做的很好,思路很清晰,整个系统对于对多信息源的融合做的很好,按照这种架构,再多来几个传感器也没关系,这就是系统的扩展性。综上,我认为对开发者来讲(注意这句话),这是一套不错的开源系统。
评论(0)
您还未登录,请登录后发表或查看评论