本次閱讀的源碼爲 release-1.0 版本的代碼
https://github.com/googlecartographer/cartographer_ros/tree/release-1.0
https://github.com/googlecartographer/cartographer/tree/release-1.0
也可以下載我上傳的 全套工作空間的代碼,包括 protobuf, cartographer, cartographer_ros, ceres,
https://download.csdn.net/download/tiancailx/11224156,現在上傳資源自己不能選下載需要的積分。。。完全靠系統。
本篇文章只簡要分析 cartographer_ros 下的代碼,cartogrpaher 下的代碼請見另一篇文章
cartographer_ros/cartographer_ros/node_main.cc
main()函數調用Run(), 在Run() 首先進行了參數的讀取,包括node_options 和 trajectory_options,然後初始化了 MapBuilder 和 Node,然後有條件的分別調用 Node 的如下方法。
if (!FLAGS_load_state_filename.empty()) {
node.LoadState(FLAGS_load_state_filename, FLAGS_load_frozen_state);
}
if (FLAGS_start_trajectory_with_default_topics) {
node.StartTrajectoryWithDefaultTopics(trajectory_options);
}
::ros::spin();
node.FinishAllTrajectories();
node.RunFinalOptimization();
if (!FLAGS_save_state_filename.empty()) {
node.SerializeState(FLAGS_save_state_filename);
}
在main()函數中有一個 ros::start(); ,我沒找到這個的定義。。。
cartographer_ros/cartographer_ros/node.cc
// Wires up ROS topics to SLAM. 將ROS主題連接到SLAM。
HandleStartTrajectory() --> AddTrajectory()
首先在Node的構造函數中 將 HandleStartTrajectory() 註冊到服務中,然後通過服務進行方法的調用。
HandleStartTrajectory() 函數首先檢查trajectory options,然後檢查topics,都通過了之後調用AddTrajectory()。
int Node::AddTrajectory(const TrajectoryOptions& options,
const cartographer_ros_msgs::SensorTopics& topics) {
const std::set<cartographer::mapping::TrajectoryBuilderInterface::SensorId>
expected_sensor_ids = ComputeExpectedSensorIds(options, topics);
const int trajectory_id =
map_builder_bridge_.AddTrajectory(expected_sensor_ids, options);
AddExtrapolator(trajectory_id, options);
AddSensorSamplers(trajectory_id, options);
LaunchSubscribers(options, topics, trajectory_id);
is_active_trajectory_[trajectory_id] = true;
for (const auto& sensor_id : expected_sensor_ids) {
subscribed_topics_.insert(sensor_id.id);
}
return trajectory_id;
}
AddTrajectory() 方法 根據配置文檔對 extrapolator 和 SensorSample 進行參數配置。然後通過 LaunchSubscribers() 方法將幾種傳感器的數據處理函數通過boost進行回調函數的註冊,即每次相應類型的消息到來,都會進行 HandleImuMessage()等 這幾個數據處理函數的調用。
void Node::HandleImuMessage(const int trajectory_id,
const std::string& sensor_id,
const sensor_msgs::Imu::ConstPtr& msg) {
carto::common::MutexLocker lock(&mutex_);
if (!sensor_samplers_.at(trajectory_id).imu_sampler.Pulse()) {
return;
}
auto sensor_bridge_ptr = map_builder_bridge_.sensor_bridge(trajectory_id);
auto imu_data_ptr = sensor_bridge_ptr->ToImuData(msg);
if (imu_data_ptr != nullptr) {
extrapolators_.at(trajectory_id).AddImuData(*imu_data_ptr);
}
sensor_bridge_ptr->HandleImuMessage(sensor_id, msg);
}
可以看到,如果存在IMU數據,就將IMU數據 輸入 到 extrapolator 中,如果有 Odom 數據,也將 odom 數據 輸入到 extrapolator 中。
IMU數據類型的處理是通過 map_builder_bridge_.sensor_bridge(trajectory_id)->HandleImuMessage(sensor_id, msg); 進行的。也就是轉到了 map_builder_bridge.h ,與 sensor_bridge.h , map_build 是 進行地圖構建的過程,sensor_bridge 是 轉換數據類型以及座標變換。
cartographer_ros/cartographer_ros/sensor_bridge.h
Converts ROS messages into SensorData in tracking frame for the MapBuilder.
ROS消息轉換爲 MapBuilder 的 tracking frame 下的 SensorData
// 將 里程計數據 傳入 carto::sensor::OdometryData, 返回指向 carto::sensor::OdometryData 的智能指針
std::unique_ptr<carto::sensor::OdometryData> SensorBridge::ToOdometryData(
const nav_msgs::Odometry::ConstPtr& msg) {
const carto::common::Time time = FromRos(msg->header.stamp);
const auto sensor_to_tracking = tf_bridge_.LookupToTracking(
time, CheckNoLeadingSlash(msg->child_frame_id));
if (sensor_to_tracking == nullptr) {
return nullptr;
}
return carto::common::make_unique<carto::sensor::OdometryData>(
carto::sensor::OdometryData{
time, ToRigid3d(msg->pose.pose) * sensor_to_tracking->inverse()});
}
通過 ToOdometryData() ToImuData() 將 ROS 下的消息類型轉換爲 Cartographer 的數據類型。並將 odom 的數據 通過 乘以一個 座標變化矩陣 轉換到 tracking frame 座標系下。
laserscan, MultiEchoLaserScanMessage 的數據處理是調用 HandleLaserScan() --> HandleRangefinder(),PointCloud2Message 的數據處理是直接調用 HandleRangefinder(), 代碼如下
void SensorBridge::HandleLaserScan() {...}
void SensorBridge::HandleRangefinder(
const std::string& sensor_id, const carto::common::Time time,
const std::string& frame_id, const carto::sensor::TimedPointCloud& ranges) {
const auto sensor_to_tracking =
tf_bridge_.LookupToTracking(time, CheckNoLeadingSlash(frame_id));
if (sensor_to_tracking != nullptr) {
trajectory_builder_->AddSensorData(
sensor_id, carto::sensor::TimedPointCloudData{
time, sensor_to_tracking->translation().cast<float>(),
carto::sensor::TransformTimedPointCloud(
ranges, sensor_to_tracking->cast<float>())});
}
}
所有的傳感器數據 包括 odom imu nav_fix landmark laser multiEchoLaser pointcloud2 的 處理都是先通過 cartographer_ros/msg_conversion.h 先將ROS 下的消息類型轉換成 Carto 的消息類型,PointCloud2 的消息類型是直接應用 PCL 的 pcl::fromROSMsg() 轉換的,在傳入 caro 的數據類型裏。
然後在將數據 傳入到 cartographer::mapping::TrajectoryBuilderInterface 的 AddSensorData() 的重載函數中。
cartographer/mapping/trajectory_builder_interface.h
// This interface is used for both 2D and 3D SLAM. Implementations wire up a
// global SLAM stack, i.e. local SLAM for initial pose estimates, scan matching
// to detect loop closure, and a sparse pose graph optimization to compute
// optimized pose estimates.用於2D和3D SLAM。 實現了整體的SLAM過程,即用於 初始姿勢估計的local SLAM,用於檢測循環閉合的掃描匹配,以及用於計算優化姿勢估計的稀疏姿勢圖優化。
cartographer_ros/cartographer_ros/map_builder_bridge.h
node.cc 調用瞭如下方法,也就是進入到了 sensor_bridge.h 文件。
SensorBridge* MapBuilderBridge::sensor_bridge(const int trajectory_id) {
return sensor_bridges_.at(trajectory_id).get();
}
首先,在頭文件中定義了 TrajectoryState 結構體。內置結構體 LocalSlamData 包含了匹配之後的座標變換 local_pose,以及估計 local_pose 的時刻,以及 RangeData 數據。之後 TrajectoryState 結構體 還包含 local - map 的 座標變換,local - tracking 的座標變換(爲什麼用指針???), 以及 trajectory_options.
總結,TrajectoryState 就是進行 SLAM 之後得到的 位姿座標,以及相對於 map 和 tracking frame 的座標變換。
struct TrajectoryState {
// Contains the trajectory state data received from local SLAM, after
// it had processed accumulated 'range_data_in_local' and estimated
// current 'local_pose' at 'time'.
struct LocalSlamData {
::cartographer::common::Time time;
::cartographer::transform::Rigid3d local_pose;
::cartographer::sensor::RangeData range_data_in_local;
};
std::shared_ptr<const LocalSlamData> local_slam_data;
cartographer::transform::Rigid3d local_to_map;
std::unique_ptr<cartographer::transform::Rigid3d> published_to_tracking;
TrajectoryOptions trajectory_options;
};
RangeData 包含了一幀雷達數據的 起點座標,hit障礙物的點 returns,以及 沒有掃到障礙物的點 misses ,並將 misses 用一個配置的距離代替。
// cartographer/sensor/range_data.h
// Rays begin at 'origin'. 'returns' are the points where obstructions were
// detected. 'misses' are points in the direction of rays for which no return
// was detected, and were inserted at a configured distance. It is assumed that
// between the 'origin' and 'misses' is free space.
struct RangeData {
Eigen::Vector3f origin;
PointCloud returns;
PointCloud misses;
};
node.cc 的 AddTrajectory() --> map_build_bridge.h 的 AddTrajectory() --> map_builder_interface.h 的 AddTrajectoryBuilder() , 並且將 OnLocalSlamResult() 方法通過回調函數的形式註冊進去。
並且對sensor_bridges_ 進行了第0個 trajectory_id 的初始化,並將 TrajectoryBuilder 傳進去,再調用 cartographer::mapping::TrajectoryBuilderInterface 的 AddSensorData() 方法進行傳感器數據的處理。
int MapBuilderBridge::AddTrajectory(
const std::set<cartographer::mapping::TrajectoryBuilderInterface::SensorId>&
expected_sensor_ids,
const TrajectoryOptions& trajectory_options) {
const int trajectory_id = map_builder_->AddTrajectoryBuilder(
expected_sensor_ids, trajectory_options.trajectory_builder_options,
::std::bind(&MapBuilderBridge::OnLocalSlamResult, this,
::std::placeholders::_1, ::std::placeholders::_2,
::std::placeholders::_3, ::std::placeholders::_4,
::std::placeholders::_5));
LOG(INFO) << "Added trajectory with ID '" << trajectory_id << "'.";
// Make sure there is no trajectory with 'trajectory_id' yet.
CHECK_EQ(sensor_bridges_.count(trajectory_id), 0);
sensor_bridges_[trajectory_id] =
cartographer::common::make_unique<SensorBridge>(
trajectory_options.num_subdivisions_per_laser_scan,
trajectory_options.tracking_frame,
node_options_.lookup_transform_timeout_sec, tf_buffer_,
map_builder_->GetTrajectoryBuilder(trajectory_id));
auto emplace_result =
trajectory_options_.emplace(trajectory_id, trajectory_options);
CHECK(emplace_result.second == true);
return trajectory_id;
}
OnLocalSlamResult()
void MapBuilderBridge::OnLocalSlamResult(
const int trajectory_id, const ::cartographer::common::Time time,
const Rigid3d local_pose,
::cartographer::sensor::RangeData range_data_in_local,
const std::unique_ptr<const ::cartographer::mapping::
TrajectoryBuilderInterface::InsertionResult>) {
std::shared_ptr<const TrajectoryState::LocalSlamData> local_slam_data =
std::make_shared<TrajectoryState::LocalSlamData>(
TrajectoryState::LocalSlamData{time, local_pose,
std::move(range_data_in_local)});
cartographer::common::MutexLocker lock(&mutex_);
trajectory_state_data_[trajectory_id] = std::move(local_slam_data);
}
OnLocalSlamResult() 這個方法大致就是確定當前的 localSlamData ,然後傳入到 TrajectoryState 集合中。
OnLocalSlamResult() 的使用如下所示:( 具體的處理函數還沒找到)
// cartographer/cartographer/mapping/trajectory_builder_interface.h
#include<functional>
using LocalSlamResultCallback =
std::function<void(int /* trajectory ID */, common::Time,
transform::Rigid3d /* local pose estimate */,
sensor::RangeData /* in local frame */,
std::unique_ptr<const InsertionResult>)>;
// cartographer/cartographer/mapping/map_builder_interface.h
using LocalSlamResultCallback =
TrajectoryBuilderInterface::LocalSlamResultCallback;
// Creates a new trajectory builder and returns its index.
virtual int AddTrajectoryBuilder(
const std::set<SensorId>& expected_sensor_ids,
const proto::TrajectoryBuilderOptions& trajectory_options,
LocalSlamResultCallback local_slam_result_callback) = 0;
// cartographer/cartographer/mapping/map_builder.h
int AddTrajectoryBuilder(
const std::set<SensorId> &expected_sensor_ids,
const proto::TrajectoryBuilderOptions &trajectory_options,
LocalSlamResultCallback local_slam_result_callback) override;
// cartographer/cartographer/mapping/map_builder.cc
int MapBuilder::AddTrajectoryBuilder(
const std::set<SensorId>& expected_sensor_ids,
const proto::TrajectoryBuilderOptions& trajectory_options,
LocalSlamResultCallback local_slam_result_callback) {
// ...
trajectory_builders_.push_back(
common::make_unique<CollatedTrajectoryBuilder>(
sensor_collator_.get(), trajectory_id, expected_sensor_ids,
CreateGlobalTrajectoryBuilder3D(
std::move(local_trajectory_builder), trajectory_id,
static_cast<PoseGraph3D*>(pose_graph_.get()),
local_slam_result_callback)));
}
下邊3個方法讀取 map_builder_->pose_graph() 進行 rviz 的可視化。
visualization_msgs::MarkerArray MapBuilderBridge::GetTrajectoryNodeList() {
visualization_msgs::MarkerArray MapBuilderBridge::GetLandmarkPosesList() {
visualization_msgs::MarkerArray MapBuilderBridge::GetConstraintList() {
剩下的方法都是調用 map_build.h中的方法,起到個 橋樑的作用。