目录
Cartographer源码阅读-2D后端-PoseGraph2D
后端PoseGraph2D
后端管理着轨迹及其相关的Node及Submap的位姿,并对各个位姿进行优化,也是分支定界的接口。
struct TrajectoryNodePose {
// Node的数据
struct ConstantPoseData {
// 时间
common::Time time;
// 在Local SLAM坐标系下的位姿
transform::Rigid3d local_pose;
};
// The node pose in the global SLAM frame.
transform::Rigid3d global_pose;
common::optional<ConstantPoseData> constant_pose_data;
};
struct TrajectoryNode {
struct Data {
// node时间
common::Time time;
// Transform to approximately gravity align the tracking frame as
// determined by local SLAM.
Eigen::Quaterniond gravity_alignment; // 重力方向
// Used for loop closure in 2D: voxel filtered returns in the
// 'gravity_alignment' frame.
sensor::PointCloud filtered_gravity_aligned_point_cloud; // 重力对齐后的点云数据
// Used for loop closure in 3D.
sensor::PointCloud high_resolution_point_cloud; // 3D中用的高分辨率点云
sensor::PointCloud low_resolution_point_cloud; // 3D中用的低分辨率点云
Eigen::VectorXf rotational_scan_matcher_histogram; // 3D中用的直方图
// The node pose in the local SLAM frame.
transform::Rigid3d local_pose;
};
common::Time time() const { return constant_data->time; }
// This must be a shared_ptr. If the data is used for visualization while the
// node is being trimmed, it must survive until all use finishes.
std::shared_ptr<const Data> constant_data;
// The node pose in the global SLAM frame.
transform::Rigid3d global_pose;
};
class PoseGraphInterface {
public:
// A "constraint" as in the paper by Konolige, Kurt, et al. "Efficient sparse
// pose adjustment for 2d mapping." Intelligent Robots and Systems (IROS),
// 2010 IEEE/RSJ International Conference on (pp. 22--29). IEEE, 2010.
struct Constraint {
struct Pose {
// 约束之间的相对位姿,Node和submap之间
transform::Rigid3d zbar_ij;
double translation_weight;
double rotation_weight;
};
SubmapId submap_id; // 'i' in the paper.
NodeId node_id; // 'j' in the paper.
// Pose of the node 'j' relative to submap 'i'.
Pose pose;
// Differentiates between intra-submap (where node 'j' was inserted into
// submap 'i') and inter-submap constraints (where node 'j' was not inserted
// into submap 'i').
// 全局回环或者sequence边
enum Tag { INTRA_SUBMAP, INTER_SUBMAP } tag;
};
// Landmark
struct LandmarkNode {
struct LandmarkObservation {
int trajectory_id;
common::Time time;
transform::Rigid3d landmark_to_tracking_transform;
double translation_weight;
double rotation_weight;
};
std::vector<LandmarkObservation> landmark_observations;
common::optional<transform::Rigid3d> global_landmark_pose;
};
struct SubmapPose {
int version;
// 在global SLAM坐标系下的位姿
transform::Rigid3d pose;
};
struct SubmapData {
std::shared_ptr<const Submap> submap;
transform::Rigid3d pose;
};
struct TrajectoryData {
double gravity_constant = 9.8;
std::array<double, 4> imu_calibration{{1., 0., 0., 0.}};
common::optional<transform::Rigid3d> fixed_frame_origin_in_map;
};
using GlobalSlamOptimizationCallback =
std::function<void(const std::map<int /* trajectory_id */, SubmapId>&,
const std::map<int /* trajectory_id */, NodeId>&)>;
PoseGraphInterface() {}
virtual ~PoseGraphInterface() {}
PoseGraphInterface(const PoseGraphInterface&) = delete;
PoseGraphInterface& operator=(const PoseGraphInterface&) = delete;
// Waits for all computations to finish and computes optimized poses.
virtual void RunFinalOptimization() = 0;
// Returns data for all submaps.
virtual MapById<SubmapId, SubmapData> GetAllSubmapData() const = 0;
// Returns the global poses for all submaps.
virtual MapById<SubmapId, SubmapPose> GetAllSubmapPoses() const = 0;
// Returns the transform converting data in the local map frame (i.e. the
// continuous, non-loop-closed frame) into the global map frame (i.e. the
// discontinuous, loop-closed frame).
virtual transform::Rigid3d GetLocalToGlobalTransform(
int trajectory_id) const = 0;
// Returns the current optimized trajectories.
virtual MapById<NodeId, TrajectoryNode> GetTrajectoryNodes() const = 0;
// Returns the current optimized trajectory poses.
virtual MapById<NodeId, TrajectoryNodePose> GetTrajectoryNodePoses()
const = 0;
// Returns the current optimized landmark poses.
virtual std::map<std::string, transform::Rigid3d> GetLandmarkPoses()
const = 0;
// Sets global pose of landmark 'landmark_id' to given 'global_pose'.
virtual void SetLandmarkPose(const std::string& landmark_id,
const transform::Rigid3d& global_pose) = 0;
// Checks if the given trajectory is finished.
virtual bool IsTrajectoryFinished(int trajectory_id) const = 0;
// Checks if the given trajectory is frozen.
virtual bool IsTrajectoryFrozen(int trajectory_id) const = 0;
// Returns the trajectory data.
virtual std::map<int, TrajectoryData> GetTrajectoryData() const = 0;
// Returns the collection of constraints.
virtual std::vector<Constraint> constraints() const = 0;
// Serializes the constraints and trajectories.
virtual proto::PoseGraph ToProto() const = 0;
// Sets the callback function that is invoked whenever the global optimization
// problem is solved.
virtual void SetGlobalSlamOptimizationCallback(
GlobalSlamOptimizationCallback callback) = 0;
};
class PoseGraph : public PoseGraphInterface {
public:
struct InitialTrajectoryPose {
int to_trajectory_id;
transform::Rigid3d relative_pose;
common::Time time;
};
PoseGraph() {}
~PoseGraph() override {}
PoseGraph(const PoseGraph&) = delete;
PoseGraph& operator=(const PoseGraph&) = delete;
// Inserts an IMU measurement.
virtual void AddImuData(int trajectory_id,
const sensor::ImuData& imu_data) = 0;
// Inserts an odometry measurement.
virtual void AddOdometryData(int trajectory_id,
const sensor::OdometryData& odometry_data) = 0;
// Inserts a fixed frame pose measurement.
virtual void AddFixedFramePoseData(
int trajectory_id,
const sensor::FixedFramePoseData& fixed_frame_pose_data) = 0;
// Inserts landmarks observations.
virtual void AddLandmarkData(int trajectory_id,
const sensor::LandmarkData& landmark_data) = 0;
// Finishes the given trajectory.
virtual void FinishTrajectory(int trajectory_id) = 0;
// Freezes a trajectory. Poses in this trajectory will not be optimized.
virtual void FreezeTrajectory(int trajectory_id) = 0;
// Adds a 'submap' from a proto with the given 'global_pose' to the
// appropriate trajectory.
virtual void AddSubmapFromProto(const transform::Rigid3d& global_pose,
const proto::Submap& submap) = 0;
// Adds a 'node' from a proto with the given 'global_pose' to the
// appropriate trajectory.
virtual void AddNodeFromProto(const transform::Rigid3d& global_pose,
const proto::Node& node) = 0;
// Sets the trajectory data from a proto.
virtual void SetTrajectoryDataFromProto(
const mapping::proto::TrajectoryData& data) = 0;
// Adds information that 'node_id' was inserted into 'submap_id'. The submap
// has to be deserialized first.
virtual void AddNodeToSubmap(const NodeId& node_id,
const SubmapId& submap_id) = 0;
// Adds serialized constraints. The corresponding trajectory nodes and submaps
// have to be deserialized before calling this function.
virtual void AddSerializedConstraints(
const std::vector<Constraint>& constraints) = 0;
// Adds a 'trimmer'. It will be used after all data added before it has been
// included in the pose graph.
virtual void AddTrimmer(std::unique_ptr<PoseGraphTrimmer> trimmer) = 0;
// Gets the current trajectory clusters.
virtual std::vector<std::vector<int>> GetConnectedTrajectories() const = 0;
// Returns the current optimized transform and submap itself for the given
// 'submap_id'. Returns 'nullptr' for the 'submap' member if the submap does
// not exist (anymore).
virtual SubmapData GetSubmapData(const SubmapId& submap_id) const = 0;
proto::PoseGraph ToProto() const override;
// Returns the IMU data.
virtual sensor::MapByTime<sensor::ImuData> GetImuData() const = 0;
// Returns the odometry data.
virtual sensor::MapByTime<sensor::OdometryData> GetOdometryData() const = 0;
// Returns the fixed frame pose data.
virtual sensor::MapByTime<sensor::FixedFramePoseData> GetFixedFramePoseData()
const = 0;
// Returns the landmark data.
virtual std::map<std::string /* landmark ID */, PoseGraph::LandmarkNode>
GetLandmarkNodes() const = 0;
// Sets a relative initial pose 'relative_pose' for 'from_trajectory_id' with
// respect to 'to_trajectory_id' at time 'time'.
virtual void SetInitialTrajectoryPose(int from_trajectory_id,
int to_trajectory_id,
const transform::Rigid3d& pose,
const common::Time time) = 0;
};
class PoseGraph2D : public PoseGraph {
public:
PoseGraph2D(
const proto::PoseGraphOptions& options,
std::unique_ptr<optimization::OptimizationProblem2D> optimization_problem,
common::ThreadPool* thread_pool);
~PoseGraph2D() override;
PoseGraph2D(const PoseGraph2D&) = delete;
PoseGraph2D& operator=(const PoseGraph2D&) = delete;
// 在后端添加数据
// Adds a new node with 'constant_data'. Its 'constant_data->local_pose' was
// determined by scan matching against 'insertion_submaps.front()' and the
// node data was inserted into the 'insertion_submaps'. If
// 'insertion_submaps.front().finished()' is 'true', data was inserted into
// this submap for the last time.
NodeId AddNode(
std::shared_ptr<const TrajectoryNode::Data> constant_data,
int trajectory_id,
const std::vector<std::shared_ptr<const Submap2D>>& insertion_submaps)
EXCLUDES(mutex_);
void AddImuData(int trajectory_id, const sensor::ImuData& imu_data) override
EXCLUDES(mutex_);
void AddOdometryData(int trajectory_id,
const sensor::OdometryData& odometry_data) override
EXCLUDES(mutex_);
void AddFixedFramePoseData(
int trajectory_id,
const sensor::FixedFramePoseData& fixed_frame_pose_data) override
EXCLUDES(mutex_);
void AddLandmarkData(int trajectory_id,
const sensor::LandmarkData& landmark_data) override
EXCLUDES(mutex_);
// 轨迹相关操作
void FinishTrajectory(int trajectory_id) override;
bool IsTrajectoryFinished(int trajectory_id) const override REQUIRES(mutex_);
void FreezeTrajectory(int trajectory_id) override;
bool IsTrajectoryFrozen(int trajectory_id) const override REQUIRES(mutex_);
// 从proto中读取submap和Node的数据
void AddSubmapFromProto(const transform::Rigid3d& global_submap_pose,
const proto::Submap& submap) override;
void AddNodeFromProto(const transform::Rigid3d& global_pose,
const proto::Node& node) override;
void SetTrajectoryDataFromProto(const proto::TrajectoryData& data) override;
void AddNodeToSubmap(const NodeId& node_id,
const SubmapId& submap_id) override;
void AddSerializedConstraints(
const std::vector<Constraint>& constraints) override;
// 添加删除机制
void AddTrimmer(std::unique_ptr<PoseGraphTrimmer> trimmer) override;
// 执行最后一次优化
void RunFinalOptimization() override;
// 获取后端数据
std::vector<std::vector<int>> GetConnectedTrajectories() const override;
PoseGraphInterface::SubmapData GetSubmapData(const SubmapId& submap_id) const
EXCLUDES(mutex_) override;
MapById<SubmapId, PoseGraphInterface::SubmapData> GetAllSubmapData() const
EXCLUDES(mutex_) override;
MapById<SubmapId, SubmapPose> GetAllSubmapPoses() const
EXCLUDES(mutex_) override;
transform::Rigid3d GetLocalToGlobalTransform(int trajectory_id) const
EXCLUDES(mutex_) override;
MapById<NodeId, TrajectoryNode> GetTrajectoryNodes() const override
EXCLUDES(mutex_);
MapById<NodeId, TrajectoryNodePose> GetTrajectoryNodePoses() const override
EXCLUDES(mutex_);
std::map<std::string, transform::Rigid3d> GetLandmarkPoses() const override
EXCLUDES(mutex_);
void SetLandmarkPose(const std::string& landmark_id,
const transform::Rigid3d& global_pose) override
EXCLUDES(mutex_);
sensor::MapByTime<sensor::ImuData> GetImuData() const override
EXCLUDES(mutex_);
sensor::MapByTime<sensor::OdometryData> GetOdometryData() const override
EXCLUDES(mutex_);
sensor::MapByTime<sensor::FixedFramePoseData> GetFixedFramePoseData()
const override EXCLUDES(mutex_);
std::map<std::string /* landmark ID */, PoseGraph::LandmarkNode>
GetLandmarkNodes() const override EXCLUDES(mutex_);
std::map<int, TrajectoryData> GetTrajectoryData() const override
EXCLUDES(mutex_);
std::vector<Constraint> constraints() const override EXCLUDES(mutex_);
void SetInitialTrajectoryPose(int from_trajectory_id, int to_trajectory_id,
const transform::Rigid3d& pose,
const common::Time time) override
EXCLUDES(mutex_);
void SetGlobalSlamOptimizationCallback(
PoseGraphInterface::GlobalSlamOptimizationCallback callback) override;
transform::Rigid3d GetInterpolatedGlobalTrajectoryPose(
int trajectory_id, const common::Time time) const REQUIRES(mutex_);
private:
// The current state of the submap in the background threads. When this
// transitions to kFinished, all nodes are tried to match against this submap.
// Likewise, all new nodes are matched against submaps which are finished.
enum class SubmapState { kActive, kFinished };
struct InternalSubmapData {
// 该submap的数据
std::shared_ptr<const Submap2D> submap;
// IDs of the nodes that were inserted into this map together with
// constraints for them. They are not to be matched again when this submap
// becomes 'finished'.
// 该submap中关联的Node的ID
std::set<NodeId> node_ids;
// submap状态
SubmapState state = SubmapState::kActive;
};
MapById<SubmapId, PoseGraphInterface::SubmapData> GetSubmapDataUnderLock()
const REQUIRES(mutex_);
// Handles a new work item.
void AddWorkItem(const std::function<void()>& work_item) REQUIRES(mutex_);
// Adds connectivity and sampler for a trajectory if it does not exist.
void AddTrajectoryIfNeeded(int trajectory_id) REQUIRES(mutex_);
// Grows the optimization problem to have an entry for every element of
// 'insertion_submaps'. Returns the IDs for the 'insertion_submaps'.
std::vector<SubmapId> InitializeGlobalSubmapPoses(
int trajectory_id, const common::Time time,
const std::vector<std::shared_ptr<const Submap2D>>& insertion_submaps)
REQUIRES(mutex_);
// Adds constraints for a node, and starts scan matching in the background.
void ComputeConstraintsForNode(
const NodeId& node_id,
std::vector<std::shared_ptr<const Submap2D>> insertion_submaps,
bool newly_finished_submap) REQUIRES(mutex_);
// Computes constraints for a node and submap pair.
void ComputeConstraint(const NodeId& node_id, const SubmapId& submap_id)
REQUIRES(mutex_);
// Adds constraints for older nodes whenever a new submap is finished.
void ComputeConstraintsForOldNodes(const SubmapId& submap_id)
REQUIRES(mutex_);
// Runs the optimization, executes the trimmers and processes the work queue.
void HandleWorkQueue(const constraints::ConstraintBuilder2D::Result& result)
REQUIRES(mutex_);
// Waits until we caught up (i.e. nothing is waiting to be scheduled), and
// all computations have finished.
void WaitForAllComputations() EXCLUDES(mutex_);
// Runs the optimization. Callers have to make sure, that there is only one
// optimization being run at a time.
void RunOptimization() EXCLUDES(mutex_);
// Computes the local to global map frame transform based on the given
// 'global_submap_poses'.
transform::Rigid3d ComputeLocalToGlobalTransform(
const MapById<SubmapId, optimization::SubmapSpec2D>& global_submap_poses,
int trajectory_id) const REQUIRES(mutex_);
SubmapData GetSubmapDataUnderLock(const SubmapId& submap_id) const
REQUIRES(mutex_);
common::Time GetLatestNodeTime(const NodeId& node_id,
const SubmapId& submap_id) const
REQUIRES(mutex_);
// Updates the trajectory connectivity structure with a new constraint.
void UpdateTrajectoryConnectivity(const Constraint& constraint)
REQUIRES(mutex_);
const proto::PoseGraphOptions options_;
GlobalSlamOptimizationCallback global_slam_optimization_callback_;
mutable common::Mutex mutex_;
// If it exists, further work items must be added to this queue, and will be
// considered later.
std::unique_ptr<std::deque<std::function<void()>>> work_queue_
GUARDED_BY(mutex_);
// How our various trajectories are related.
TrajectoryConnectivityState trajectory_connectivity_state_;
// We globally localize a fraction of the nodes from each trajectory.
std::unordered_map<int, std::unique_ptr<common::FixedRatioSampler>>
global_localization_samplers_ GUARDED_BY(mutex_);
// Number of nodes added since last loop closure.
int num_nodes_since_last_loop_closure_ GUARDED_BY(mutex_) = 0;
// Whether the optimization has to be run before more data is added.
bool run_loop_closure_ GUARDED_BY(mutex_) = false;
// Schedules optimization (i.e. loop closure) to run.
void DispatchOptimization() REQUIRES(mutex_);
// Current optimization problem.
std::unique_ptr<optimization::OptimizationProblem2D> optimization_problem_;
constraints::ConstraintBuilder2D constraint_builder_ GUARDED_BY(mutex_);
std::vector<Constraint> constraints_ GUARDED_BY(mutex_);
// Submaps get assigned an ID and state as soon as they are seen, even
// before they take part in the background computations.
MapById<SubmapId, InternalSubmapData> submap_data_ GUARDED_BY(mutex_);
// Data that are currently being shown.
MapById<NodeId, TrajectoryNode> trajectory_nodes_ GUARDED_BY(mutex_);
int num_trajectory_nodes_ GUARDED_BY(mutex_) = 0;
// Global submap poses currently used for displaying data.
MapById<SubmapId, optimization::SubmapSpec2D> global_submap_poses_
GUARDED_BY(mutex_);
// Global landmark poses with all observations.
std::map<std::string /* landmark ID */, PoseGraph::LandmarkNode>
landmark_nodes_ GUARDED_BY(mutex_);
// List of all trimmers to consult when optimizations finish.
std::vector<std::unique_ptr<PoseGraphTrimmer>> trimmers_ GUARDED_BY(mutex_);
// Set of all frozen trajectories not being optimized.
std::set<int> frozen_trajectories_ GUARDED_BY(mutex_);
// Set of all finished trajectories.
std::set<int> finished_trajectories_ GUARDED_BY(mutex_);
// Set of all initial trajectory poses.
std::map<int, InitialTrajectoryPose> initial_trajectory_poses_
GUARDED_BY(mutex_);
// Allows querying and manipulating the pose graph by the 'trimmers_'. The
// 'mutex_' of the pose graph is held while this class is used.
// 删除机制接口类
class TrimmingHandle : public Trimmable {
public:
TrimmingHandle(PoseGraph2D* parent);
~TrimmingHandle() override {}
int num_submaps(int trajectory_id) const override;
std::vector<SubmapId> GetSubmapIds(int trajectory_id) const override;
MapById<SubmapId, SubmapData> GetOptimizedSubmapData() const override
REQUIRES(parent_->mutex_);
const MapById<NodeId, TrajectoryNode>& GetTrajectoryNodes() const override
REQUIRES(parent_->mutex_);
const std::vector<Constraint>& GetConstraints() const override
REQUIRES(parent_->mutex_);
// 删除submap及submap相关的数据
void MarkSubmapAsTrimmed(const SubmapId& submap_id)
REQUIRES(parent_->mutex_) override;
bool IsFinished(int trajectory_id) const override REQUIRES(parent_->mutex_);
private:
PoseGraph2D* const parent_;
};
};
添加Node
NodeId PoseGraph2D::AddNode(
std::shared_ptr<const TrajectoryNode::Data> constant_data,
const int trajectory_id,
const std::vector<std::shared_ptr<const Submap2D>>& insertion_submaps) {
// 直接获取该Node在globalSLAM坐标系下的位姿
const transform::Rigid3d optimized_pose(
GetLocalToGlobalTransform(trajectory_id) * constant_data->local_pose);
common::MutexLocker locker(&mutex_);
// 如果没有该trajectory,则添加该trajectory
AddTrajectoryIfNeeded(trajectory_id);
// 为该Node添加Node_id
const NodeId node_id = trajectory_nodes_.Append(
trajectory_id, TrajectoryNode{constant_data, optimized_pose});
++num_trajectory_nodes_;
// Test if the 'insertion_submap.back()' is one we never saw before.
// 如果没有该条轨迹或该轨迹中没有该submap,则为该submap添加submap_id
if (submap_data_.SizeOfTrajectoryOrZero(trajectory_id) == 0 ||
std::prev(submap_data_.EndOfTrajectory(trajectory_id))->data.submap !=
insertion_submaps.back()) {
// We grow 'submap_data_' as needed. This code assumes that the first
// time we see a new submap is as 'insertion_submaps.back()'.
const SubmapId submap_id =
submap_data_.Append(trajectory_id, InternalSubmapData());
submap_data_.at(submap_id).submap = insertion_submaps.back();
}
// We have to check this here, because it might have changed by the time we
// execute the lambda.
// 判断该activesubmap是否插入结束,并根据情况,计算约束
const bool newly_finished_submap = insertion_submaps.front()->finished();
AddWorkItem([=]() REQUIRES(mutex_) {
ComputeConstraintsForNode(node_id, insertion_submaps,
newly_finished_submap);
});
return node_id;
}
获取当前轨迹(Local SLAM坐标系)在Global SLAM坐标系下的位姿:GetLocalToGlobalTransform()
transform::Rigid3d PoseGraph2D::GetLocalToGlobalTransform(
const int trajectory_id) const {
common::MutexLocker locker(&mutex_);
return ComputeLocalToGlobalTransform(global_submap_poses_, trajectory_id);
}
transform::Rigid3d PoseGraph2D::ComputeLocalToGlobalTransform(
const MapById<SubmapId, optimization::SubmapSpec2D>& global_submap_poses,
const int trajectory_id) const {
// 获取该条轨迹的最前和最后两个submap
auto begin_it = global_submap_poses.BeginOfTrajectory(trajectory_id);
auto end_it = global_submap_poses.EndOfTrajectory(trajectory_id);
// 如果该条轨迹没有submap数据
if (begin_it == end_it) {
// 查看该条轨迹是否存在后端
const auto it = initial_trajectory_poses_.find(trajectory_id);
// 若存在则进行轨迹位姿插值
if (it != initial_trajectory_poses_.end()) {
return GetInterpolatedGlobalTrajectoryPose(it->second.to_trajectory_id,
it->second.time) *
it->second.relative_pose;
} else {
// 若不存在,则为单位阵
return transform::Rigid3d::Identity();
}
}
// 若存在submap数据,则找到次新的sunmap
const SubmapId last_optimized_submap_id = std::prev(end_it)->id;
// 计算轨迹的local to global pose
// Accessing 'local_pose' in Submap is okay, since the member is const.
return transform::Embed3D(
global_submap_poses.at(last_optimized_submap_id).global_pose) *
submap_data_.at(last_optimized_submap_id)
.submap->local_pose()
.inverse();
}
// 轨迹插值
transform::Rigid3d PoseGraph2D::GetInterpolatedGlobalTrajectoryPose(
const int trajectory_id, const common::Time time) const {
CHECK_GT(trajectory_nodes_.SizeOfTrajectoryOrZero(trajectory_id), 0);
// 若该条轨迹不在一个轨迹的区间范围内,则直接用边界轨迹的global pose赋值
const auto it = trajectory_nodes_.lower_bound(trajectory_id, time);
if (it == trajectory_nodes_.BeginOfTrajectory(trajectory_id)) {
return trajectory_nodes_.BeginOfTrajectory(trajectory_id)->data.global_pose;
}
if (it == trajectory_nodes_.EndOfTrajectory(trajectory_id)) {
return std::prev(trajectory_nodes_.EndOfTrajectory(trajectory_id))
->data.global_pose;
}
// 若该条轨迹在一个轨迹的区间范围内,则通过插值的方法计算轨迹的 global pose
return transform::Interpolate(
transform::TimestampedTransform{std::prev(it)->data.time(),
std::prev(it)->data.global_pose},
transform::TimestampedTransform{it->data.time(),
it->data.global_pose},
time)
.transform;
}
// An highlighted block
var foo = 'bar';
回环计算接口:ComputeConstraintsForNode()
void PoseGraph2D::ComputeConstraintsForNode(
const NodeId& node_id,
std::vector<std::shared_ptr<const Submap2D>> insertion_submaps,
const bool newly_finished_submap) {
// 获取该Node的数据
const auto& constant_data = trajectory_nodes_.at(node_id).constant_data;
// 获取该Node所在的submaps,如果是第一个submap,则只有一个,如果不是第一个submap,则该submap_ids应该有两个,并且在该函数中,将submap加入优化问题
const std::vector<SubmapId> submap_ids = InitializeGlobalSubmapPoses(
node_id.trajectory_id, constant_data->time, insertion_submaps);
CHECK_EQ(submap_ids.size(), insertion_submaps.size());
// 前一个submap
const SubmapId matching_id = submap_ids.front();
// 计算该node在localSLAM坐标系下的去掉重力对齐的位姿local_pose_2d:其中,constant_data中的Local pose是重力对齐的
const transform::Rigid2d local_pose_2d = transform::Project2D(
constant_data->local_pose *
transform::Rigid3d::Rotation(constant_data->gravity_alignment.inverse()));
// 通过其所在的submap的global pose 和Local pose,计算node在global SLAM坐标系下的去掉重力对齐的位姿global_pose_2d,并放入优化问题
const transform::Rigid2d global_pose_2d =
optimization_problem_->submap_data().at(matching_id).global_pose *
constraints::ComputeSubmapPose(*insertion_submaps.front()).inverse() *
local_pose_2d;
optimization_problem_->AddTrajectoryNode(
matching_id.trajectory_id,
optimization::NodeSpec2D{constant_data->time, local_pose_2d,
global_pose_2d,
constant_data->gravity_alignment});
// 逐个添加sequence边
for (size_t i = 0; i < insertion_submaps.size(); ++i) {
const SubmapId submap_id = submap_ids[i];
// Even if this was the last node added to 'submap_id', the submap will
// only be marked as finished in 'submap_data_' further below.
CHECK(submap_data_.at(submap_id).state == SubmapState::kActive);
submap_data_.at(submap_id).node_ids.emplace(node_id);
// 计算sequence边:疑惑:为什么不再乘以重力呢?因为
// constraint = submap.global_pose_3d(global_pose_2d * grivaty).inverse() * node.global_pose_2d也就是
// constraint = submap.local_pose_3d(local_pose_2d*grivaty).inverse() * local_pose_2d
const transform::Rigid2d constraint_transform =
constraints::ComputeSubmapPose(*insertion_submaps[i]).inverse() *
local_pose_2d;
constraints_.push_back(Constraint{submap_id,
node_id,
{transform::Embed3D(constraint_transform),
options_.matcher_translation_weight(),
options_.matcher_rotation_weight()},
Constraint::INTRA_SUBMAP});
}
// 为当前的node和所有的finished的submap计算约束
for (const auto& submap_id_data : submap_data_) {
if (submap_id_data.data.state == SubmapState::kFinished) {
CHECK_EQ(submap_id_data.data.node_ids.count(node_id), 0);
ComputeConstraint(node_id, submap_id_data.id);
}
}
// 如果insertion_submaps的第一个submap是刚结束的submap
if (newly_finished_submap) {
// 取出ID
const SubmapId finished_submap_id = submap_ids.front();
InternalSubmapData& finished_submap_data =
submap_data_.at(finished_submap_id);
CHECK(finished_submap_data.state == SubmapState::kActive);
// 将其状态赋值为kFinished
finished_submap_data.state = SubmapState::kFinished;
// We have a new completed submap, so we look into adding constraints for
// old nodes.
// 并将新结束的submap和历史上所有的node做回环检测
ComputeConstraintsForOldNodes(finished_submap_id);
}
constraint_builder_.NotifyEndOfNode();
++num_nodes_since_last_loop_closure_;
CHECK(!run_loop_closure_);
// 如果当前的待优化的Node数量达到阈值,则开始执行优化任务
if (options_.optimize_every_n_nodes() > 0 &&
num_nodes_since_last_loop_closure_ > options_.optimize_every_n_nodes()) {
DispatchOptimization();
}
}
// 计算新生成的Node和所有submap之间的回环
void PoseGraph2D::ComputeConstraint(const NodeId& node_id,
const SubmapId& submap_id) {
CHECK(submap_data_.at(submap_id).state == SubmapState::kFinished);
// 获取当前submap中最新node的时间和当前node的时间的最大值
const common::Time node_time = GetLatestNodeTime(node_id, submap_id);
const common::Time last_connection_time =
trajectory_connectivity_state_.LastConnectionTime(
node_id.trajectory_id, submap_id.trajectory_id);
// 如果是同一条轨迹,且时间差不大,则将优化后的相对值当做初值传入
if (node_id.trajectory_id == submap_id.trajectory_id ||
node_time <
last_connection_time +
common::FromSeconds(
options_.global_constraint_search_after_n_seconds())) {
// If the node and the submap belong to the same trajectory or if there
// has been a recent global constraint that ties that node's trajectory to
// the submap's trajectory, it suffices to do a match constrained to a
// local search window.
// constraint = submap.global_pose_3d(global_pose_2d * grivaty).inverse() * node.global_pose_2d
const transform::Rigid2d initial_relative_pose =
optimization_problem_->submap_data()
.at(submap_id)
.global_pose.inverse() *
optimization_problem_->node_data().at(node_id).global_pose_2d;
constraint_builder_.MaybeAddConstraint(
submap_id, submap_data_.at(submap_id).submap.get(), node_id,
trajectory_nodes_.at(node_id).constant_data.get(),
initial_relative_pose);
} else if (global_localization_samplers_[node_id.trajectory_id]->Pulse()) {
// 没有初始值
constraint_builder_.MaybeAddGlobalConstraint(
submap_id, submap_data_.at(submap_id).submap.get(), node_id,
trajectory_nodes_.at(node_id).constant_data.get());
}
}
// 计算新生成的submap和所有老的Node之间的回环
void PoseGraph2D::ComputeConstraintsForOldNodes(const SubmapId& submap_id) {
const auto& submap_data = submap_data_.at(submap_id);
// 遍历所有的node,和submap计算回环
for (const auto& node_id_data : optimization_problem_->node_data()) {
const NodeId& node_id = node_id_data.id;
if (submap_data.node_ids.count(node_id) == 0) {
ComputeConstraint(node_id, submap_id);
}
}
}
将submap加入优化问题:InitializeGlobalSubmapPoses()
// 在这一步不仅仅是获取Node所在的两个submap,也是将submap数据加入优化的入口
std::vector<SubmapId> PoseGraph2D::InitializeGlobalSubmapPoses(
const int trajectory_id, const common::Time time,
const std::vector<std::shared_ptr<const Submap2D>>& insertion_submaps) {
CHECK(!insertion_submaps.empty());
const auto& submap_data = optimization_problem_->submap_data();
// 如果是该条轨迹的第一个submap
if (insertion_submaps.size() == 1) {
// If we don't already have an entry for the first submap, add one.
if (submap_data.SizeOfTrajectoryOrZero(trajectory_id) == 0) {
if (initial_trajectory_poses_.count(trajectory_id) > 0) {
trajectory_connectivity_state_.Connect(
trajectory_id,
initial_trajectory_poses_.at(trajectory_id).to_trajectory_id, time);
}
optimization_problem_->AddSubmap(
trajectory_id,
// 计算该submap在global SLAM坐标系下的位姿
transform::Project2D(ComputeLocalToGlobalTransform(
global_submap_poses_, trajectory_id) *
insertion_submaps[0]->local_pose()));
}
CHECK_EQ(1, submap_data.SizeOfTrajectoryOrZero(trajectory_id));
// 保证submap的submap_index必须从0开始
const SubmapId submap_id{trajectory_id, 0};
CHECK(submap_data_.at(submap_id).submap == insertion_submaps.front());
return {submap_id};
}
CHECK_EQ(2, insertion_submaps.size());
const auto end_it = submap_data.EndOfTrajectory(trajectory_id);
CHECK(submap_data.BeginOfTrajectory(trajectory_id) != end_it);
// 获取submap_data中最后一个submap的id
const SubmapId last_submap_id = std::prev(end_it)->id;
// 如果submap_data中最后一个submap的id为insertion_submaps的第一个submap,则证明insertion_submaps的第二个submap是新生成的submap,需要将insertion_submaps的第二个submap加入submap_data_
if (submap_data_.at(last_submap_id).submap == insertion_submaps.front()) {
// In this case, 'last_submap_id' is the ID of
// 'insertions_submaps.front()' and 'insertions_submaps.back()' is new.
// 获取优化后的insertion_submaps的第一个submap的全局位姿
const auto& first_submap_pose = submap_data.at(last_submap_id).global_pose;
optimization_problem_->AddSubmap(
trajectory_id,
// 计算优化后的insertion_submaps的第二个submap的全局位姿
first_submap_pose *
constraints::ComputeSubmapPose(*insertion_submaps[0]).inverse() *
constraints::ComputeSubmapPose(*insertion_submaps[1]));
return {last_submap_id,
SubmapId{trajectory_id, last_submap_id.submap_index + 1}};
}
CHECK(submap_data_.at(last_submap_id).submap == insertion_submaps.back());
const SubmapId front_submap_id{trajectory_id,
last_submap_id.submap_index - 1};
CHECK(submap_data_.at(front_submap_id).submap == insertion_submaps.front());
return {front_submap_id, last_submap_id};
}
执行优化:DispatchOptimization()
void PoseGraph2D::DispatchOptimization() {
// 执行线程池
run_loop_closure_ = true;
// If there is a 'work_queue_' already, some other thread will take care.
if (work_queue_ == nullptr) {
work_queue_ = common::make_unique<std::deque<std::function<void()>>>();
constraint_builder_.WhenDone(
std::bind(&PoseGraph2D::HandleWorkQueue, this, std::placeholders::_1));
}
}
// 线程池执行
void PoseGraph2D::HandleWorkQueue(
const constraints::ConstraintBuilder2D::Result& result) {
{
common::MutexLocker locker(&mutex_);
constraints_.insert(constraints_.end(), result.begin(), result.end());
}
// 执行优化
RunOptimization();
if (global_slam_optimization_callback_) {
std::map<int, NodeId> trajectory_id_to_last_optimized_node_id;
std::map<int, SubmapId> trajectory_id_to_last_optimized_submap_id;
{
common::MutexLocker locker(&mutex_);
// 取出优化数据
const auto& submap_data = optimization_problem_->submap_data();
const auto& node_data = optimization_problem_->node_data();
for (const int trajectory_id : node_data.trajectory_ids()) {
trajectory_id_to_last_optimized_node_id[trajectory_id] =
std::prev(node_data.EndOfTrajectory(trajectory_id))->id;
trajectory_id_to_last_optimized_submap_id[trajectory_id] =
std::prev(submap_data.EndOfTrajectory(trajectory_id))->id;
}
}
global_slam_optimization_callback_(
trajectory_id_to_last_optimized_submap_id,
trajectory_id_to_last_optimized_node_id);
}
// 更新各条轨迹之间的连接
common::MutexLocker locker(&mutex_);
for (const Constraint& constraint : result) {
UpdateTrajectoryConnectivity(constraint);
}
// 执行删除submap任务
TrimmingHandle trimming_handle(this);
for (auto& trimmer : trimmers_) {
trimmer->Trim(&trimming_handle);
}
trimmers_.erase(
std::remove_if(trimmers_.begin(), trimmers_.end(),
[](std::unique_ptr<PoseGraphTrimmer>& trimmer) {
return trimmer->IsFinished();
}),
trimmers_.end());
num_nodes_since_last_loop_closure_ = 0;
run_loop_closure_ = false;
while (!run_loop_closure_) {
if (work_queue_->empty()) {
work_queue_.reset();
return;
}
work_queue_->front()();
work_queue_->pop_front();
}
LOG(INFO) << "Remaining work items in queue: " << work_queue_->size();
// We have to optimize again.
constraint_builder_.WhenDone(
std::bind(&PoseGraph2D::HandleWorkQueue, this, std::placeholders::_1));
}
void PoseGraph2D::RunOptimization() {
if (optimization_problem_->submap_data().empty()) {
return;
}
// No other thread is accessing the optimization_problem_, constraints_,
// frozen_trajectories_ and landmark_nodes_ when executing the Solve. Solve is
// time consuming, so not taking the mutex before Solve to avoid blocking
// foreground processing.
// SPA优化
optimization_problem_->Solve(constraints_, frozen_trajectories_,
landmark_nodes_);
common::MutexLocker locker(&mutex_);
// 取出优化后的数据
const auto& submap_data = optimization_problem_->submap_data();
const auto& node_data = optimization_problem_->node_data();
for (const int trajectory_id : node_data.trajectory_ids()) {
for (const auto& node : node_data.trajectory(trajectory_id)) {
auto& mutable_trajectory_node = trajectory_nodes_.at(node.id);
// node的global pose 为 global_pose_2d * grivitay
mutable_trajectory_node.global_pose =
transform::Embed3D(node.data.global_pose_2d) *
transform::Rigid3d::Rotation(
mutable_trajectory_node.constant_data->gravity_alignment);
}
// Extrapolate all point cloud poses that were not included in the
// 'optimization_problem_' yet.
// 计算old_global_to_new_global相对转换
const auto local_to_new_global =
ComputeLocalToGlobalTransform(submap_data, trajectory_id);
const auto local_to_old_global =
ComputeLocalToGlobalTransform(global_submap_poses_, trajectory_id);
const transform::Rigid3d old_global_to_new_global =
local_to_new_global * local_to_old_global.inverse();
// 更新node的global_pose
const NodeId last_optimized_node_id =
std::prev(node_data.EndOfTrajectory(trajectory_id))->id;
auto node_it = std::next(trajectory_nodes_.find(last_optimized_node_id));
for (; node_it != trajectory_nodes_.EndOfTrajectory(trajectory_id);
++node_it) {
auto& mutable_trajectory_node = trajectory_nodes_.at(node_it->id);
mutable_trajectory_node.global_pose =
old_global_to_new_global * mutable_trajectory_node.global_pose;
}
}
for (const auto& landmark : optimization_problem_->landmark_data()) {
landmark_nodes_[landmark.first].global_landmark_pose = landmark.second;
}
// 更新submap的global_pose
global_submap_poses_ = submap_data;
}
接下来:
(1)回环检测:主要是分支定界算法
(2)后端优化:SPA算法
(3)删除机制:两种:OverlappingSubmapsTrimmer2D和PureLocalizationTrimmer
参考链接: https://blog.csdn.net/yeluohanchan/article/details/108885896.