cartographer 添加节点以及优化
程序员文章站
2022-06-06 11:21:13
...
程序入口
PoseGraph2D::AddNode()主要是在GlobalTrajectoryBuilder::AddSensorData调用,在计算完帧间匹配后,插入子图,然后再将帧间匹配和子图的结果加入到节点中。
GlobalTrajectoryBuilder::AddSensorData
void AddSensorData(
const std::string &sensor_id,
const sensor::TimedPointCloudData &timed_point_cloud_data) override {
CHECK(local_trajectory_builder_)
<< "Cannot add TimedPointCloudData without a LocalTrajectoryBuilder.";
// 输入range数据经过 scan_match 输出matchresult(包括位姿, 点云, 以及插入子图结果).
std::unique_ptr<typename LocalTrajectoryBuilder::MatchingResult>
matching_result = local_trajectory_builder_->AddRangeData(
sensor_id, timed_point_cloud_data);
if (matching_result == nullptr) {
// The range data has not been fully accumulated yet.
return;
}
kLocalSlamMatchingResults->Increment();
std::unique_ptr<InsertionResult> insertion_result;
if (matching_result->insertion_result != nullptr) {
kLocalSlamInsertionResults->Increment();
// matching_result加入到pose_graph
auto node_id = pose_graph_->AddNode(
matching_result->insertion_result->constant_data, trajectory_id_,
matching_result->insertion_result->insertion_submaps);
CHECK_EQ(node_id.trajectory_id, trajectory_id_);
// 计算插入子图结果
insertion_result = common::make_unique<InsertionResult>(InsertionResult{
node_id, matching_result->insertion_result->constant_data,
std::vector<std::shared_ptr<const Submap>>(
matching_result->insertion_result->insertion_submaps.begin(),
matching_result->insertion_result->insertion_submaps.end())});
}
if (local_slam_result_callback_) {
// 回调函数,将数据返回给ros
local_slam_result_callback_(
trajectory_id_, matching_result->time, matching_result->local_pose,
std::move(matching_result->range_data_in_local),
std::move(insertion_result));
}
}
PoseGraph2D::AddNode
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) {
// 得到最优pose,
// 将trajectory_id下的TrajectoryNode中局部位姿转换到世界坐标系下
const transform::Rigid3d optimized_pose(
GetLocalToGlobalTransform(trajectory_id) * constant_data->local_pose);
common::MutexLocker locker(&mutex_);
AddTrajectoryIfNeeded(trajectory_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.
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.
// 添加工作组 用于添加工作的队列work_queue_
const bool newly_finished_submap = insertion_submaps.front()->finished();
AddWorkItem([=]() REQUIRES(mutex_) {
ComputeConstraintsForNode(node_id, insertion_submaps,
newly_finished_submap);
});
return node_id;
}
ComputeConstraintsForNode
为激光数据帧scan计算约束 为这帧新激光添加约束并开始在后台进行扫描匹配。
主要调用PoseGraph2D::ComputeConstraint()进行计算。
void PoseGraph2D::ComputeConstraint(const NodeId &node_id,
const SubmapId &submap_id) {
// 首先确认该submap已经finish
CHECK(submap_data_.at(submap_id).state == SubmapState::kFinished);
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);
// 判断node_id和submap_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.
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());
}
}
PoseGraph2D::ComputeConstraint()
PoseGraph2D::RunOptimization
- 变量
MapById<NodeId, NodeSpec2D> node_data_;
MapById<SubmapId, SubmapSpec2D> submap_data_;
- 函数实现
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.
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);
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.
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();
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;
}
global_submap_poses_ = submap_data;
}
OptimizationProblem2D::Solve
void OptimizationProblem2D::Solve(
const std::vector<Constraint> &constraints,
const std::set<int> &frozen_trajectories,
const std::map<std::string, LandmarkNode> &landmark_nodes) {
if (node_data_.empty()) {
// Nothing to optimize.
return;
}
// ceres非线性优化变量
ceres::Problem::Options problem_options;
ceres::Problem problem(problem_options);
// Set the starting point.
// TODO(hrapp): Move ceres data into SubmapSpec.
MapById<SubmapId, std::array<double, 3>> C_submaps;
MapById<NodeId, std::array<double, 3>> C_nodes;
std::map<std::string, CeresPose> C_landmarks;
bool first_submap = true;
bool freeze_landmarks = !frozen_trajectories.empty();
// 设置起点,将ceres数据移动到SubmapSpec中
for (const auto &submap_id_data : submap_data_) {
// 首先在冻结轨迹中寻找该子图id是否存在,确定该子图是否为冻结子图
const bool frozen =
frozen_trajectories.count(submap_id_data.id.trajectory_id) != 0;
C_submaps.Insert(submap_id_data.id,
FromPose(submap_id_data.data.global_pose));
// 添加一个参数块
problem.AddParameterBlock(C_submaps.at(submap_id_data.id).data(), 3);
if (first_submap || frozen) {
first_submap = false;
// Fix the pose of the first submap or all submaps of a frozen
// trajectory.
// 如果为第一个子图或者是冻结子图,则固定子图的数据,也就是位姿
problem.SetParameterBlockConstant(C_submaps.at(submap_id_data.id).data());
}
}
for (const auto &node_id_data : node_data_) {
// 首先在冻结轨迹中寻找该node是否存在,确定该node_id_data是否为冻结node
const bool frozen =
frozen_trajectories.count(node_id_data.id.trajectory_id) != 0;
C_nodes.Insert(node_id_data.id, FromPose(node_id_data.data.global_pose_2d));
// 添加一个参数块
problem.AddParameterBlock(C_nodes.at(node_id_data.id).data(), 3);
if (frozen) {
// 如果为冻结node数据,则将该node_id_data数据设为固定值
problem.SetParameterBlockConstant(C_nodes.at(node_id_data.id).data());
}
}
// Add cost functions for intra- and inter-submap constraints.
// 为子图内和子图间约束添加costfunction
for (const Constraint &constraint : constraints) {
problem.AddResidualBlock(
CreateAutoDiffSpaCostFunction(constraint.pose),
// Only loop closure constraints should have a loss function.
constraint.tag == Constraint::INTER_SUBMAP
? new ceres::HuberLoss(options_.huber_scale())
: nullptr,
C_submaps.at(constraint.submap_id).data(),
C_nodes.at(constraint.node_id).data());
}
// Add cost functions for landmarks.
// 为landmark添加代价函数
AddLandmarkCostFunctions(landmark_nodes, freeze_landmarks, node_data_,
&C_nodes, &C_landmarks, &problem);
// Add penalties for violating odometry or changes between consecutive nodes
// if odometry is not available.
//
// 如果没有里程计,为违反里程计或连续节点之间的更改添加惩罚
for (auto node_it = node_data_.begin(); node_it != node_data_.end();) {
const int trajectory_id = node_it->id.trajectory_id;
const auto trajectory_end = node_data_.EndOfTrajectory(trajectory_id);
if (frozen_trajectories.count(trajectory_id) != 0) {
node_it = trajectory_end;
continue;
}
auto prev_node_it = node_it;
for (++node_it; node_it != trajectory_end; ++node_it) {
const NodeId first_node_id = prev_node_it->id;
const NodeSpec2D &first_node_data = prev_node_it->data;
prev_node_it = node_it;
const NodeId second_node_id = node_it->id;
const NodeSpec2D &second_node_data = node_it->data;
// 确保在同一个轨迹
if (second_node_id.node_index != first_node_id.node_index + 1) {
continue;
}
// Add a relative pose constraint based on the odometry (if available).
// 添加里程计约束
std::unique_ptr<transform::Rigid3d> relative_odometry =
CalculateOdometryBetweenNodes(trajectory_id, first_node_data,
second_node_data);
if (relative_odometry != nullptr) {
problem.AddResidualBlock(
CreateAutoDiffSpaCostFunction(Constraint::Pose{
*relative_odometry, options_.odometry_translation_weight(),
options_.odometry_rotation_weight()}),
nullptr /* loss function */, C_nodes.at(first_node_id).data(),
C_nodes.at(second_node_id).data());
}
// Add a relative pose constraint based on consecutive local SLAM poses.
// 添加localslam位姿约束
const transform::Rigid3d relative_local_slam_pose =
transform::Embed3D(first_node_data.local_pose_2d.inverse() *
second_node_data.local_pose_2d);
problem.AddResidualBlock(
CreateAutoDiffSpaCostFunction(
Constraint::Pose{relative_local_slam_pose,
options_.local_slam_pose_translation_weight(),
options_.local_slam_pose_rotation_weight()}),
nullptr /* loss function */, C_nodes.at(first_node_id).data(),
C_nodes.at(second_node_id).data());
}
}
// Solve.
ceres::Solver::Summary summary;
ceres::Solve(
common::CreateCeresSolverOptions(options_.ceres_solver_options()),
&problem, &summary);
if (options_.log_solver_summary()) {
LOG(INFO) << summary.FullReport();
}
// Store the result.
// 调整submap_data_, node_data_, landmark_dta_
for (const auto &C_submap_id_data : C_submaps) {
submap_data_.at(C_submap_id_data.id).global_pose =
ToPose(C_submap_id_data.data);
}
for (const auto &C_node_id_data : C_nodes) {
node_data_.at(C_node_id_data.id).global_pose_2d =
ToPose(C_node_id_data.data);
}
for (const auto &C_landmark : C_landmarks) {
landmark_data_[C_landmark.first] = C_landmark.second.ToRigid();
}
}
回环检测中残差计算
// 残差计算
template <typename T>
static std::array<T, 6> ComputeUnscaledError(
const transform::Rigid3d &relative_pose, const T *const start_rotation,
const T *const start_translation, const T *const end_rotation,
const T *const end_translation) {
// 四元素{w, x, y, z} 四元素的逆{w, -x, -y, -z}
const Eigen::Quaternion<T> R_i_inverse(start_rotation[0], -start_rotation[1],
-start_rotation[2],
-start_rotation[3]);
const Eigen::Matrix<T, 3, 1> delta(end_translation[0] - start_translation[0],
end_translation[1] - start_translation[1],
end_translation[2] - start_translation[2]);
// 从i到j的translatation <==> h^T = Ri^-1 * delta = Ri^T * delta
const Eigen::Matrix<T, 3, 1> h_translation = R_i_inverse * delta;
// 从i到j的rotation <==> Ri^-1 * Rj
// h_rotation_inversion = Rj^-1 * Ri
const Eigen::Quaternion<T> h_rotation_inverse =
Eigen::Quaternion<T>(end_rotation[0], -end_rotation[1], -end_rotation[2],
-end_rotation[3]) *
Eigen::Quaternion<T>(start_rotation[0], start_rotation[1],
start_rotation[2], start_rotation[3]);
// 轴角差
const Eigen::Matrix<T, 3, 1> angle_axis_difference =
transform::RotationQuaternionToAngleAxisVector(
h_rotation_inverse * relative_pose.rotation().cast<T>());
// 返回残差
return {{T(relative_pose.translation().x()) - h_translation[0],
T(relative_pose.translation().y()) - h_translation[1],
T(relative_pose.translation().z()) - h_translation[2],
angle_axis_difference[0], angle_axis_difference[1],
angle_axis_difference[2]}};
}
残差部分不知道注释对不?向大家请教。