Cartographer源码阅读2D&3D-PGM生成及地图发布
pgm生成
cartographer提供了pbstream转pgm的service,当然,也可以改写相关函数,直接从cartographer的后端取出数据生成pgm,但是需要修改相关代码。将pbstream转为pgm的节点在pbstream_to_ros_map_main.cc文件里实现。主要函数为:
void Run(const std::string& pbstream_filename, const std::string& map_filestem,
const double resolution) {
::cartographer::io::ProtoStreamReader reader(pbstream_filename);
::cartographer::io::ProtoStreamDeserializer deserializer(&reader);
// 从pbstream中读取pose graph
const auto& pose_graph = deserializer.pose_graph();
LOG(INFO) << "Loading submap slices from serialized data.";
// 存放submap的slice
std::map<::cartographer::mapping::SubmapId, ::cartographer::io::SubmapSlice>
submap_slices;
::cartographer::mapping::proto::SerializedData proto;
while (deserializer.ReadNextSerializedData(&proto)) {
// 只取出submap
if (proto.has_submap()) {
const auto& submap = proto.submap();
// 获取id
const ::cartographer::mapping::SubmapId id{
submap.submap_id().trajectory_id(),
submap.submap_id().submap_index()};
// 获取global pose
const ::cartographer::transform::Rigid3d global_submap_pose =
::cartographer::transform::ToRigid3(
pose_graph.trajectory(id.trajectory_id)
.submap(id.submap_index)
.pose());
// 填充该submap的slice
FillSubmapSlice(global_submap_pose, submap, &submap_slices[id]);
}
}
CHECK(reader.eof());
LOG(INFO) << "Generating combined map image from submap slices.";
// 画上网格
auto result =
::cartographer::io::PaintSubmapSlices(submap_slices, resolution);
::cartographer::io::StreamFileWriter pgm_writer(map_filestem + ".pgm");
::cartographer::io::Image image(std::move(result.surface));
// 生成pgm
WritePgm(image, resolution, &pgm_writer);
const Eigen::Vector2d origin(
-result.origin.x() * resolution,
(result.origin.y() - image.height()) * resolution);
::cartographer::io::StreamFileWriter yaml_writer(map_filestem + ".yaml");
// 生成yaml
WriteYaml(resolution, origin, pgm_writer.GetFilename(), &yaml_writer);
}
submapQuery.srv和SubmapTexture.msg
SubmapQuery:
int32 trajectory_id
int32 submap_index
---
cartographer_ros_msgs/StatusResponse status
int32 submap_version // submap包含的node的数量
cartographer_ros_msgs/SubmapTexture[] textures
SubmapTexture:
// 存放的有cell的value和alpha值
uint8[] cells
int32 width
int32 height
float64 resolution
geometry_msgs/Pose slice_pose
submap slice:
using UniqueCairoSurfacePtr =
std::unique_ptr<cairo_surface_t, void (*)(cairo_surface_t*)>;
UniqueCairoSurfacePtr MakeUniqueCairoSurfacePtr(cairo_surface_t* surface) {
return UniqueCairoSurfacePtr(surface, cairo_surface_destroy);
}
struct SubmapSlice {
SubmapSlice()
: surface(::cartographer::io::MakeUniqueCairoSurfacePtr(nullptr)) {}
// Texture data.
int width;
int height;
int version;
double resolution;
// global pose
::cartographer::transform::Rigid3d slice_pose;
// 网格数据
::cartographer::io::UniqueCairoSurfacePtr surface;
// Pixel data used by 'surface'. Must outlive 'surface'.
// 网格数据转换成pgm需要的格式
std::vector<uint32_t> cairo_data;
// Metadata.
// local pose网格在submap坐标系下的位姿
::cartographer::transform::Rigid3d pose;
int metadata_version = -1;
};
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;
};
slice填充:
// 填充slice
void FillSubmapSlice(
const ::cartographer::transform::Rigid3d& global_submap_pose,
const ::cartographer::mapping::proto::Submap& proto,
SubmapSlice* const submap_slice) {
// 生成SubmapQuery 的response
::cartographer::mapping::proto::SubmapQuery::Response response;
::cartographer::transform::Rigid3d local_pose;
if (proto.has_submap_3d()) {
mapping::Submap3D submap(proto.submap_3d());
local_pose = submap.local_pose();
submap.ToResponseProto(global_submap_pose, &response);
} else {
::cartographer::mapping::Submap2D submap(proto.submap_2d());
// local pose 后面没用到啊
local_pose = submap.local_pose();
submap.ToResponseProto(global_submap_pose, &response);
}
// slice的global pose
submap_slice->pose = global_submap_pose;
// 取出texture
auto& texture_proto = response.textures(0);
// 解压得到cell存放的数值:value和alpha
const SubmapTexture::Pixels pixels = UnpackTextureData(
texture_proto.cells(), texture_proto.width(), texture_proto.height());
// 设置宽
submap_slice->width = texture_proto.width();
// 设置高
submap_slice->height = texture_proto.height();
// 设置分辨率
submap_slice->resolution = texture_proto.resolution();
// 设置local pose
submap_slice->slice_pose =
::cartographer::transform::ToRigid3(texture_proto.slice_pose());
// 生成surface 和 cairo_data
submap_slice->surface =
DrawTexture(pixels.intensity, pixels.alpha, texture_proto.width(),
texture_proto.height(), &submap_slice->cairo_data);
}
// 生成SubmapQuery 的response
void Submap2D::ToResponseProto(
const transform::Rigid3d&,
proto::SubmapQuery::Response* const response) const {
if (!grid_) return;
// 填充submap数量
response->set_submap_version(num_range_data());
// 填充texture
proto::SubmapQuery::Response::SubmapTexture* const texture =
response->add_textures();
// 将网格数据放入texture中
grid()->DrawToSubmapTexture(texture, local_pose());
}
// 网格数据放入texture中
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;
// 遍历所有的网格,生成网格的string
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.
const int delta =
128 - ProbabilityToLogOddsInteger(GetProbability(xy_index + offset));
const uint8 alpha = delta > 0 ? 0 : -delta;
const uint8 value = delta > 0 ? delta : 0;
cells.push_back(value);
cells.push_back((value || alpha) ? alpha : 1);
}
// 压缩string
common::FastGzipString(cells, texture->mutable_cells());
// 设置宽
texture->set_width(cell_limits.num_x_cells);
// 设置高
texture->set_height(cell_limits.num_y_cells);
// 设置分辨率
const double resolution = limits().resolution();
texture->set_resolution(resolution);
// 设置Local pose,该local pose是网格在submap坐标系下的位姿
const double max_x = limits().max().x() - resolution * offset.y();
const double max_y = limits().max().y() - resolution * offset.x();
*texture->mutable_slice_pose() = transform::ToProto(
local_pose.inverse() *
transform::Rigid3d::Translation(Eigen::Vector3d(max_x, max_y, 0.)));
return true;
}
// 生成surface 和 cairo_data
UniqueCairoSurfacePtr DrawTexture(const std::vector<char>& intensity,
const std::vector<char>& alpha,
const int width, const int height,
std::vector<uint32_t>* const cairo_data) {
CHECK(cairo_data->empty());
// Properly dealing with a non-common stride would make this code much more
// complicated. Let's check that it is not needed.
const int expected_stride = 4 * width;
CHECK_EQ(expected_stride, cairo_format_stride_for_width(kCairoFormat, width));
for (size_t i = 0; i < intensity.size(); ++i) {
// We use the red channel to track intensity information. The green
// channel we use to track if a cell was ever observed.
const uint8_t intensity_value = intensity.at(i);
const uint8_t alpha_value = alpha.at(i);
const uint8_t observed =
(intensity_value == 0 && alpha_value == 0) ? 0 : 255;
// 逐个将cell数据生成uint32_t数据,放入cairo_data中
cairo_data->push_back((alpha_value << 24) | (intensity_value << 16) |
(observed << 8) | 0);
}
// 构造UniqueCairoSurfacePtr
auto surface = MakeUniqueCairoSurfacePtr(cairo_image_surface_create_for_data(
reinterpret_cast<unsigned char*>(cairo_data->data()), kCairoFormat, width,
height, expected_stride));
CHECK_EQ(cairo_surface_status(surface.get()), CAIRO_STATUS_SUCCESS)
<< cairo_status_to_string(cairo_surface_status(surface.get()));
return surface;
}
画网格:
struct PaintSubmapSlicesResult {
PaintSubmapSlicesResult(::cartographer::io::UniqueCairoSurfacePtr surface,
Eigen::Array2f origin)
: surface(std::move(surface)), origin(origin) {}
::cartographer::io::UniqueCairoSurfacePtr surface;
// Top left pixel of 'surface' in map frame.
Eigen::Array2f origin;
};
PaintSubmapSlicesResult PaintSubmapSlices(
const std::map<::cartographer::mapping::SubmapId, SubmapSlice>& submaps,
const double resolution) {
// 计算所有的submap的外包矩形
Eigen::AlignedBox2f bounding_box;
{
auto surface = MakeUniqueCairoSurfacePtr(
cairo_image_surface_create(kCairoFormat, 1, 1));
auto cr = MakeUniqueCairoPtr(cairo_create(surface.get()));
const auto update_bounding_box = [&bounding_box, &cr](double x, double y) {
cairo_user_to_device(cr.get(), &x, &y);
bounding_box.extend(Eigen::Vector2f(x, y));
};
CairoPaintSubmapSlices(
1. / resolution, submaps, cr.get(),
[&update_bounding_box](const SubmapSlice& submap_slice) {
update_bounding_box(0, 0);
update_bounding_box(submap_slice.width, 0);
update_bounding_box(0, submap_slice.height);
update_bounding_box(submap_slice.width, submap_slice.height);
});
}
// 两边分别扩展5个Pixel
const int kPaddingPixel = 5;
// 得到新的box
const Eigen::Array2i size(
std::ceil(bounding_box.sizes().x()) + 2 * kPaddingPixel,
std::ceil(bounding_box.sizes().y()) + 2 * kPaddingPixel);
// 计算新的box的中心
const Eigen::Array2f origin(-bounding_box.min().x() + kPaddingPixel,
-bounding_box.min().y() + kPaddingPixel);
// 画上网格数据
auto surface = MakeUniqueCairoSurfacePtr(
cairo_image_surface_create(kCairoFormat, size.x(), size.y()));
{
auto cr = MakeUniqueCairoPtr(cairo_create(surface.get()));
cairo_set_source_rgba(cr.get(), 0.5, 0.0, 0.0, 1.);
cairo_paint(cr.get());
cairo_translate(cr.get(), origin.x(), origin.y());
CairoPaintSubmapSlices(1. / resolution, submaps, cr.get(),
[&cr](const SubmapSlice& submap_slice) {
cairo_set_source_surface(
cr.get(), submap_slice.surface.get(), 0., 0.);
cairo_paint(cr.get());
});
cairo_surface_flush(surface.get());
}
return PaintSubmapSlicesResult(std::move(surface), origin);
}
void CairoPaintSubmapSlices(
const double scale,
const std::map<::cartographer::mapping::SubmapId, SubmapSlice>& submaps,
cairo_t* cr, std::function<void(const SubmapSlice&)> draw_callback) {
cairo_scale(cr, scale, scale);
for (auto& pair : submaps) {
// 获取submap slice
const auto& submap_slice = pair.second;
if (submap_slice.surface == nullptr) {
return;
}
// global pose * local pose
const Eigen::Matrix4d homo =
ToEigen(submap_slice.pose * submap_slice.slice_pose).matrix();
cairo_save(cr);
cairo_matrix_t matrix;
cairo_matrix_init(&matrix, homo(1, 0), homo(0, 0), -homo(1, 1), -homo(0, 1),
homo(0, 3), -homo(1, 3));
cairo_transform(cr, &matrix);
const double submap_resolution = submap_slice.resolution;
cairo_scale(cr, submap_resolution, submap_resolution);
// Invokes caller's callback to utilize slice data in global cooridnate
// frame. e.g. finds bounding box, paints slices.
draw_callback(submap_slice);
cairo_restore(cr);
}
}
生成pgm和yaml:
void WritePgm(const ::cartographer::io::Image& image, const double resolution,
::cartographer::io::FileWriter* file_writer) {
const std::string header = "P5\n# Cartographer map; " +
std::to_string(resolution) + " m/pixel\n" +
std::to_string(image.width()) + " " +
std::to_string(image.height()) + "\n255\n";
file_writer->Write(header.data(), header.size());
for (int y = 0; y < image.height(); ++y) {
for (int x = 0; x < image.width(); ++x) {
const char color = image.GetPixel(x, y)[0];
file_writer->Write(&color, 1);
}
}
}
void WriteYaml(const double resolution, const Eigen::Vector2d& origin,
const std::string& pgm_filename,
::cartographer::io::FileWriter* file_writer) {
// Magic constants taken directly from ros map_saver code:
// https://github.com/ros-planning/navigation/blob/ac41d2480c4cf1602daf39a6e9629142731d92b0/map_server/src/map_saver.cpp#L114
const std::string output =
"image: " + pgm_filename + "\n" +
"resolution: " + std::to_string(resolution) + "\n" + "origin: [" +
std::to_string(origin.x()) + ", " + std::to_string(origin.y()) +
", 0.0]\nnegate: 0\noccupied_thresh: 0.65\nfree_thresh: 0.196\n";
file_writer->Write(output.data(), output.size());
}
地图发布
地图发布节点在occupancy_grid_node_main.cc文件中,发布的地图的topic名称为/map(在node_constants.h中定义 kOccupancyGridTopic),发布的消息为:nav_msgs::OccupancyGrid,入口函数为HandleSubmapList,其接收到SubmapList消息即发布占据栅格数据。
class Node {
public:
// 网格分辨率和发布频率构造Node
explicit Node(double resolution, double publish_period_sec);
~Node() {}
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
private:
void HandleSubmapList(const cartographer_ros_msgs::SubmapList::ConstPtr& msg);
void DrawAndPublish(const ::ros::WallTimerEvent& timer_event);
void PublishOccupancyGrid(const std::string& frame_id, const ros::Time& time,
const Eigen::Array2f& origin,
cairo_surface_t* surface);
::ros::NodeHandle node_handle_;
const double resolution_;
::cartographer::common::Mutex mutex_;
::ros::ServiceClient client_ GUARDED_BY(mutex_);
::ros::Subscriber submap_list_subscriber_ GUARDED_BY(mutex_);
::ros::Publisher occupancy_grid_publisher_ GUARDED_BY(mutex_);
// submap id和submap slice组合
std::map<SubmapId, SubmapSlice> submap_slices_ GUARDED_BY(mutex_);
::ros::WallTimer occupancy_grid_publisher_timer_;
std::string last_frame_id_;
ros::Time last_timestamp_;
};
void Node::HandleSubmapList(
const cartographer_ros_msgs::SubmapList::ConstPtr& msg) {
::cartographer::common::MutexLocker locker(&mutex_);
// We do not do any work if nobody listens.
if (occupancy_grid_publisher_.getNumSubscribers() == 0) {
return;
}
// Keep track of submap IDs that don't appear in the message anymore.
std::set<SubmapId> submap_ids_to_delete;
// 上一次发布后的submap slice中所有的submap id放入submap_ids_to_delete
for (const auto& pair : submap_slices_) {
submap_ids_to_delete.insert(pair.first);
}
for (const auto& submap_msg : msg->submap) {
const SubmapId id{submap_msg.trajectory_id, submap_msg.submap_index};
// 当前接收到的submap list 中包含该id,则将其从submap_ids_to_delete删除,剩余的为可能要删除的id
submap_ids_to_delete.erase(id);
// 获取上次发布的submap slice
SubmapSlice& submap_slice = submap_slices_[id];
// 更新global pose
submap_slice.pose = ToRigid3d(submap_msg.pose);
// 更新submap 包含的node的数量
submap_slice.metadata_version = submap_msg.submap_version;
if (submap_slice.surface != nullptr &&
submap_slice.version == submap_msg.submap_version) {
continue;
}
// 获取submap slice的texture数据
auto fetched_textures =
::cartographer_ros::FetchSubmapTextures(id, &client_);
if (fetched_textures == nullptr) {
continue;
}
CHECK(!fetched_textures->textures.empty());
submap_slice.version = fetched_textures->version;
// We use the first texture only. By convention this is the highest
// resolution texture and that is the one we want to use to construct the
// map for ROS.
const auto fetched_texture = fetched_textures->textures.begin();
// slice 数据赋值
submap_slice.width = fetched_texture->width;
submap_slice.height = fetched_texture->height;
submap_slice.slice_pose = fetched_texture->slice_pose;
submap_slice.resolution = fetched_texture->resolution;
submap_slice.cairo_data.clear();
// 更新slice数据的cairo_data和surface
submap_slice.surface = ::cartographer::io::DrawTexture(
fetched_texture->pixels.intensity, fetched_texture->pixels.alpha,
fetched_texture->width, fetched_texture->height,
&submap_slice.cairo_data);
}
// Delete all submaps that didn't appear in the message.
for (const auto& id : submap_ids_to_delete) {
submap_slices_.erase(id);
}
last_timestamp_ = msg->header.stamp;
last_frame_id_ = msg->header.frame_id;
}
// 通过服务获取submap slice的texture数据
std::unique_ptr<::cartographer::io::SubmapTextures> FetchSubmapTextures(
const ::cartographer::mapping::SubmapId& submap_id,
ros::ServiceClient* client) {
::cartographer_ros_msgs::SubmapQuery srv;
srv.request.trajectory_id = submap_id.trajectory_id;
srv.request.submap_index = submap_id.submap_index;
if (!client->call(srv) ||
srv.response.status.code != ::cartographer_ros_msgs::StatusCode::OK) {
return nullptr;
}
if (srv.response.textures.empty()) {
return nullptr;
}
auto response =
::cartographer::common::make_unique<::cartographer::io::SubmapTextures>();
response->version = srv.response.submap_version;
for (const auto& texture : srv.response.textures) {
// 获取cell数据的string
const std::string compressed_cells(texture.cells.begin(),
texture.cells.end());
// 解压string,生成cell数据
response->textures.emplace_back(::cartographer::io::SubmapTexture{
::cartographer::io::UnpackTextureData(compressed_cells, texture.width,
texture.height),
texture.width, texture.height, texture.resolution,
ToRigid3d(texture.slice_pose)});
}
return response;
}
void Node::DrawAndPublish(const ::ros::WallTimerEvent& unused_timer_event) {
if (submap_slices_.empty() || last_frame_id_.empty()) {
return;
}
::cartographer::common::MutexLocker locker(&mutex_);
// 画网格
auto painted_slices = PaintSubmapSlices(submap_slices_, resolution_);
std::unique_ptr<nav_msgs::OccupancyGrid> msg_ptr = CreateOccupancyGridMsg(
painted_slices, resolution_, last_frame_id_, last_timestamp_);
occupancy_grid_publisher_.publish(*msg_ptr);
}
// 生成occupancy grid message
std::unique_ptr<nav_msgs::OccupancyGrid> CreateOccupancyGridMsg(
const cartographer::io::PaintSubmapSlicesResult& painted_slices,
const double resolution, const std::string& frame_id,
const ros::Time& time) {
auto occupancy_grid =
::cartographer::common::make_unique<nav_msgs::OccupancyGrid>();
const int width = cairo_image_surface_get_width(painted_slices.surface.get());
const int height =
cairo_image_surface_get_height(painted_slices.surface.get());
const ros::Time now = ros::Time::now();
occupancy_grid->header.stamp = time;
occupancy_grid->header.frame_id = frame_id;
occupancy_grid->info.map_load_time = time;
occupancy_grid->info.resolution = resolution;
occupancy_grid->info.width = width;
occupancy_grid->info.height = height;
occupancy_grid->info.origin.position.x =
-painted_slices.origin.x() * resolution;
occupancy_grid->info.origin.position.y =
(-height + painted_slices.origin.y()) * resolution;
occupancy_grid->info.origin.position.z = 0.;
occupancy_grid->info.origin.orientation.w = 1.;
occupancy_grid->info.origin.orientation.x = 0.;
occupancy_grid->info.origin.orientation.y = 0.;
occupancy_grid->info.origin.orientation.z = 0.;
const uint32_t* pixel_data = reinterpret_cast<uint32_t*>(
cairo_image_surface_get_data(painted_slices.surface.get()));
occupancy_grid->data.reserve(width * height);
for (int y = height - 1; y >= 0; --y) {
for (int x = 0; x < width; ++x) {
const uint32_t packed = pixel_data[y * width + x];
const unsigned char color = packed >> 16;
const unsigned char observed = packed >> 8;
const int value =
observed == 0
? -1
: ::cartographer::common::RoundToInt((1. - color / 255.) * 100.);
CHECK_LE(-1, value);
CHECK_GE(100, value);
occupancy_grid->data.push_back(value);
}
}
return occupancy_grid;
}