cartographer_occupancy_grid_node获取地图

Rviz中的地图绘制使用了cairo库。注意gflag的参数include_frozen_submapsinclude_unfrozen_submaps不能都为false,否则会报错

cartographerrviz的绘制方式,就是`submap_node控制一个子图的绘制,每个子图有一个子图原点,这个是submap_query中的slice_pose。而每个子图之间是有微弱旋转的,而控制旋转的中心也是这个slice_pose。而每个子图之间的旋转和平移可以通过submap_list`的pose得到。

main函数值得看的就一句::cartographer_ros::Node node(FLAGS_resolution, FLAGS_publish_period_sec);,构造函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Node::Node(const double resolution, const double publish_period_sec)
: resolution_(resolution),
// submap_query 服务的客户端
client_(node_handle_.serviceClient<::cartographer_ros_msgs::SubmapQuery>(
kSubmapQueryServiceName)),
// submap_list话题的订阅者,回调函数 HandleSubmapList
submap_list_subscriber_(node_handle_.subscribe(
kSubmapListTopic, kLatestOnlyPublisherQueueSize,
boost::function<void(
const cartographer_ros_msgs::SubmapList::ConstPtr&)>(
[this](const cartographer_ros_msgs::SubmapList::ConstPtr& msg) {
HandleSubmapList(msg);
}))),
// 发布话题 map
occupancy_grid_publisher_(
node_handle_.advertise<::nav_msgs::OccupancyGrid>(
kOccupancyGridTopic, kLatestOnlyPublisherQueueSize,
true /* latched */)),
// timer执行DrawAndPublish函数
occupancy_grid_publisher_timer_(
node_handle_.createWallTimer(::ros::WallDuration(publish_period_sec),
&Node::DrawAndPublish, this)) {}

Node::DrawAndPublish是画图和发布map,不是重点


HandleSubmapList函数大部分都很简单,终点是下面这句:

1
2
auto fetched_textures =
::cartographer_ros::FetchSubmapTextures(id, &client_);

内部其实就是submap_query的客户端发起请求,服务端的注册在Node的构造函数里,回调是Node::HandleSubmapQuery

代码调用顺序是: Node::HandleSubmapQuery —— MapBuilderBridge::HandleSubmapQuery —— MapBuilder::SubmapToProto —— PoseGraph2D::GetSubmapDataSubmap2D::ToResponseProto —— ProbabilityGrid::DrawToSubmapTexture

重点:

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
// 这里比较简单,最终返回的是后端传来的 global坐标系下的位姿
PoseGraphInterface::SubmapData PoseGraph2D::GetSubmapDataUnderLock(
const SubmapId& submap_id) const
{
const auto it = data_.submap_data.find(submap_id);
if (it == data_.submap_data.end())
return {};
auto submap = it->data.submap;
if (data_.global_submap_poses_2d.Contains(submap_id) )
{
// We already have an optimized pose.
return {submap,
transform::Embed3D(
data_.global_submap_poses_2d.at(submap_id).global_pose) };
}
// We have to extrapolate.
return {submap, ComputeLocalToGlobalTransform(data_.global_submap_poses_2d,
submap_id.trajectory_id) *
submap->local_pose() };
}

void Submap2D::ToResponseProto(
const transform::Rigid3d&,
proto::SubmapQuery::Response* const response) const
{
if (!grid_) return;
// 这个 version 是子图插入了多少雷达数据,完成的子图是 2* num_range_data
response->set_submap_version( num_range_data() );
proto::SubmapQuery::Response::SubmapTexture* const texture =
response->add_textures();
grid()->DrawToSubmapTexture(texture, local_pose());
}

再看DrawToSubmapTexture

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
bool ProbabilityGrid::DrawToSubmapTexture(
proto::SubmapQuery::Response::SubmapTexture* const texture,
transform::Rigid3d local_pose) const
{
Eigen::Array2i offset;
CellLimits cell_limits;
ComputeCroppedLimits(&offset, &cell_limits);

std::string cells;
for (const Eigen::Array2i& xy_index : XYIndexRangeIterator(cell_limits))
{
if (!IsKnown(xy_index + offset)) {
cells.push_back(0 /* unknown log odds value */);
cells.push_back(0 /* alpha */);
continue;
}
// We would like to add 'delta' but this is not possible using a value and
// alpha. We use premultiplied alpha, so when 'delta' is positive we can
// add it by setting 'alpha' to zero. If it is negative, we set 'value' to
// zero, and use 'alpha' to subtract. This is only correct when the pixel
// is currently white, so walls will look too gray. This should be hard to
// detect visually for the user, though.

// delta范围[-127, 127]
const int delta =
128 - ProbabilityToLogOddsInteger(GetProbability(xy_index + offset));
const uint8 alpha = delta > 0 ? 0 : -delta;
const uint8 value = delta > 0 ? delta : 0;
// 存数据有2个值,栅格值value和alpha透明度
cells.push_back(value);
cells.push_back((value || alpha) ? alpha : 1);
}
// 保存地图栅格数据时进行压缩
common::FastGzipString(cells, texture->mutable_cells());
// 地图描述信息赋值,省略
......
*texture->mutable_slice_pose() = transform::ToProto(
local_pose.inverse() *
transform::Rigid3d::Translation(Eigen::Vector3d(max_x, max_y, 0.)) );

return true;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 解压后的栅格数据
struct SubmapTexture {
struct Pixels {
std::vector<char> intensity; // 栅格值
std::vector<char> alpha; // 透明度
};
Pixels pixels;
int width;
int height;
double resolution;
::cartographer::transform::Rigid3d slice_pose;
};

// 压缩后的栅格数据
struct SubmapTextures {
int version;
std::vector<SubmapTexture> textures;
};