Add support for multi-trajectory to 3D SLAM. (#146)
parent
318607ccd1
commit
51a0ec06a1
|
@ -168,7 +168,7 @@ LocalTrajectoryBuilder::AddHorizontalLaserFan(
|
||||||
pose_estimate_ * odometry_correction_;
|
pose_estimate_ * odometry_correction_;
|
||||||
const transform::Rigid3d model_prediction = pose_estimate_;
|
const transform::Rigid3d model_prediction = pose_estimate_;
|
||||||
// TODO(whess): Prefer IMU over odom orientation if configured?
|
// TODO(whess): Prefer IMU over odom orientation if configured?
|
||||||
const transform::Rigid3d pose_prediction = odometry_prediction;
|
const transform::Rigid3d& pose_prediction = odometry_prediction;
|
||||||
|
|
||||||
// Computes the rotation without yaw, as defined by GetYaw().
|
// Computes the rotation without yaw, as defined by GetYaw().
|
||||||
const transform::Rigid3d tracking_to_tracking_2d =
|
const transform::Rigid3d tracking_to_tracking_2d =
|
||||||
|
|
|
@ -38,7 +38,8 @@ void GlobalTrajectoryBuilder::AddImuData(
|
||||||
const Eigen::Vector3d& angular_velocity) {
|
const Eigen::Vector3d& angular_velocity) {
|
||||||
local_trajectory_builder_->AddImuData(time, linear_acceleration,
|
local_trajectory_builder_->AddImuData(time, linear_acceleration,
|
||||||
angular_velocity);
|
angular_velocity);
|
||||||
sparse_pose_graph_->AddImuData(time, linear_acceleration, angular_velocity);
|
sparse_pose_graph_->AddImuData(local_trajectory_builder_->submaps(), time,
|
||||||
|
linear_acceleration, angular_velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalTrajectoryBuilder::AddRangefinderData(
|
void GlobalTrajectoryBuilder::AddRangefinderData(
|
||||||
|
|
|
@ -136,12 +136,13 @@ void SparsePoseGraph::AddWorkItem(std::function<void()> work_item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SparsePoseGraph::AddImuData(common::Time time,
|
void SparsePoseGraph::AddImuData(const mapping::Submaps* trajectory,
|
||||||
|
common::Time time,
|
||||||
const Eigen::Vector3d& linear_acceleration,
|
const Eigen::Vector3d& linear_acceleration,
|
||||||
const Eigen::Vector3d& angular_velocity) {
|
const Eigen::Vector3d& angular_velocity) {
|
||||||
common::MutexLocker locker(&mutex_);
|
common::MutexLocker locker(&mutex_);
|
||||||
AddWorkItem([=]() REQUIRES(mutex_) {
|
AddWorkItem([=]() REQUIRES(mutex_) {
|
||||||
optimization_problem_.AddImuData(time, linear_acceleration,
|
optimization_problem_.AddImuData(trajectory, time, linear_acceleration,
|
||||||
angular_velocity);
|
angular_velocity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -202,7 +203,10 @@ void SparsePoseGraph::ComputeConstraintsForScan(
|
||||||
submap_transforms_[matching_index] *
|
submap_transforms_[matching_index] *
|
||||||
matching_submap->local_pose().inverse() * pose;
|
matching_submap->local_pose().inverse() * pose;
|
||||||
CHECK_EQ(scan_index, optimization_problem_.node_data().size());
|
CHECK_EQ(scan_index, optimization_problem_.node_data().size());
|
||||||
optimization_problem_.AddTrajectoryNode(time, optimized_pose);
|
const mapping::Submaps* const scan_trajectory =
|
||||||
|
trajectory_nodes_[scan_index].constant_data->trajectory;
|
||||||
|
optimization_problem_.AddTrajectoryNode(scan_trajectory, time,
|
||||||
|
optimized_pose);
|
||||||
for (const Submap* submap : insertion_submaps) {
|
for (const Submap* submap : insertion_submaps) {
|
||||||
const int submap_index = GetSubmapIndex(submap);
|
const int submap_index = GetSubmapIndex(submap);
|
||||||
CHECK(!submap_states_[submap_index].finished);
|
CHECK(!submap_states_[submap_index].finished);
|
||||||
|
@ -319,17 +323,13 @@ void SparsePoseGraph::RunFinalOptimization() {
|
||||||
void SparsePoseGraph::RunOptimization() {
|
void SparsePoseGraph::RunOptimization() {
|
||||||
if (!submap_transforms_.empty()) {
|
if (!submap_transforms_.empty()) {
|
||||||
transform::Rigid3d submap_0_pose;
|
transform::Rigid3d submap_0_pose;
|
||||||
std::vector<const mapping::Submaps*> trajectories;
|
|
||||||
{
|
{
|
||||||
common::MutexLocker locker(&mutex_);
|
common::MutexLocker locker(&mutex_);
|
||||||
CHECK(!submap_states_.empty());
|
CHECK(!submap_states_.empty());
|
||||||
submap_0_pose = submap_states_.front().submap->local_pose();
|
submap_0_pose = submap_states_.front().submap->local_pose();
|
||||||
for (const auto& trajectory_node : trajectory_nodes_) {
|
|
||||||
trajectories.push_back(trajectory_node.constant_data->trajectory);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
optimization_problem_.Solve(constraints_, submap_0_pose, trajectories,
|
optimization_problem_.Solve(constraints_, submap_0_pose,
|
||||||
&submap_transforms_);
|
&submap_transforms_);
|
||||||
common::MutexLocker locker(&mutex_);
|
common::MutexLocker locker(&mutex_);
|
||||||
has_new_optimized_poses_ = true;
|
has_new_optimized_poses_ = true;
|
||||||
|
|
|
@ -79,7 +79,8 @@ class SparsePoseGraph : public mapping::SparsePoseGraph {
|
||||||
EXCLUDES(mutex_);
|
EXCLUDES(mutex_);
|
||||||
|
|
||||||
// Adds new IMU data to be used in the optimization.
|
// Adds new IMU data to be used in the optimization.
|
||||||
void AddImuData(common::Time time, const Eigen::Vector3d& linear_acceleration,
|
void AddImuData(const mapping::Submaps* trajectory, common::Time time,
|
||||||
|
const Eigen::Vector3d& linear_acceleration,
|
||||||
const Eigen::Vector3d& angular_velocity);
|
const Eigen::Vector3d& angular_velocity);
|
||||||
|
|
||||||
void RunFinalOptimization() override;
|
void RunFinalOptimization() override;
|
||||||
|
|
|
@ -222,15 +222,24 @@ void ConstraintBuilder::ComputeConstraint(
|
||||||
float score = 0.;
|
float score = 0.;
|
||||||
transform::Rigid3d pose_estimate = transform::Rigid3d::Identity();
|
transform::Rigid3d pose_estimate = transform::Rigid3d::Identity();
|
||||||
|
|
||||||
CHECK(!match_full_submap) << "match_full_submap not supported for 3D.";
|
if (match_full_submap) {
|
||||||
|
if (submap_scan_matcher->fast_correlative_scan_matcher->MatchFullSubmap(
|
||||||
if (submap_scan_matcher->fast_correlative_scan_matcher->Match(
|
initial_pose.rotation(), filtered_point_cloud, point_cloud,
|
||||||
initial_pose, filtered_point_cloud, point_cloud, options_.min_score(),
|
options_.global_localization_min_score(), &score, &pose_estimate)) {
|
||||||
&score, &pose_estimate)) {
|
CHECK_GT(score, options_.global_localization_min_score());
|
||||||
// We've reported a successful local match.
|
trajectory_connectivity->Connect(scan_trajectory, submap_trajectory);
|
||||||
CHECK_GT(score, options_.min_score());
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
if (submap_scan_matcher->fast_correlative_scan_matcher->Match(
|
||||||
|
initial_pose, filtered_point_cloud, point_cloud,
|
||||||
|
options_.min_score(), &score, &pose_estimate)) {
|
||||||
|
// We've reported a successful local match.
|
||||||
|
CHECK_GT(score, options_.min_score());
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
common::MutexLocker locker(&mutex_);
|
common::MutexLocker locker(&mutex_);
|
||||||
|
|
|
@ -77,15 +77,18 @@ OptimizationProblem::OptimizationProblem(
|
||||||
|
|
||||||
OptimizationProblem::~OptimizationProblem() {}
|
OptimizationProblem::~OptimizationProblem() {}
|
||||||
|
|
||||||
void OptimizationProblem::AddImuData(common::Time time,
|
void OptimizationProblem::AddImuData(const mapping::Submaps* const trajectory,
|
||||||
|
const common::Time time,
|
||||||
const Eigen::Vector3d& linear_acceleration,
|
const Eigen::Vector3d& linear_acceleration,
|
||||||
const Eigen::Vector3d& angular_velocity) {
|
const Eigen::Vector3d& angular_velocity) {
|
||||||
imu_data_.push_back(ImuData{time, linear_acceleration, angular_velocity});
|
imu_data_[trajectory].push_back(
|
||||||
|
ImuData{time, linear_acceleration, angular_velocity});
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptimizationProblem::AddTrajectoryNode(
|
void OptimizationProblem::AddTrajectoryNode(
|
||||||
common::Time time, const transform::Rigid3d& point_cloud_pose) {
|
const mapping::Submaps* const trajectory, const common::Time time,
|
||||||
node_data_.push_back(NodeData{time, point_cloud_pose});
|
const transform::Rigid3d& point_cloud_pose) {
|
||||||
|
node_data_.push_back(NodeData{trajectory, time, point_cloud_pose});
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptimizationProblem::SetMaxNumIterations(const int32 max_num_iterations) {
|
void OptimizationProblem::SetMaxNumIterations(const int32 max_num_iterations) {
|
||||||
|
@ -96,13 +99,17 @@ void OptimizationProblem::SetMaxNumIterations(const int32 max_num_iterations) {
|
||||||
void OptimizationProblem::Solve(
|
void OptimizationProblem::Solve(
|
||||||
const std::vector<Constraint>& constraints,
|
const std::vector<Constraint>& constraints,
|
||||||
const transform::Rigid3d& submap_0_transform,
|
const transform::Rigid3d& submap_0_transform,
|
||||||
const std::vector<const mapping::Submaps*>& trajectories,
|
std::vector<transform::Rigid3d>* const submap_transforms) {
|
||||||
std::vector<transform::Rigid3d>* submap_transforms) {
|
|
||||||
if (node_data_.empty()) {
|
if (node_data_.empty()) {
|
||||||
// Nothing to optimize.
|
// Nothing to optimize.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CHECK(!imu_data_.empty());
|
|
||||||
|
// Compute the indices of consecutive nodes for each trajectory.
|
||||||
|
std::map<const mapping::Submaps*, std::vector<size_t>> nodes_per_trajectory;
|
||||||
|
for (size_t j = 0; j != node_data_.size(); ++j) {
|
||||||
|
nodes_per_trajectory[node_data_[j].trajectory].push_back(j);
|
||||||
|
}
|
||||||
|
|
||||||
ceres::Problem::Options problem_options;
|
ceres::Problem::Options problem_options;
|
||||||
ceres::Problem problem(problem_options);
|
ceres::Problem problem(problem_options);
|
||||||
|
@ -146,58 +153,68 @@ void OptimizationProblem::Solve(
|
||||||
C_point_clouds[constraint.j].translation());
|
C_point_clouds[constraint.j].translation());
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK(!node_data_.empty());
|
// Add constraints based on IMU observations of angular velocities and
|
||||||
CHECK_GE(trajectories.size(), node_data_.size());
|
// linear acceleration.
|
||||||
|
for (const auto& trajectory_nodes_pair : nodes_per_trajectory) {
|
||||||
|
const mapping::Submaps* const trajectory = trajectory_nodes_pair.first;
|
||||||
|
const std::vector<size_t>& node_indices = trajectory_nodes_pair.second;
|
||||||
|
const std::deque<ImuData>& imu_data = imu_data_.at(trajectory);
|
||||||
|
CHECK(!node_indices.empty());
|
||||||
|
CHECK(!imu_data.empty());
|
||||||
|
|
||||||
// Add constraints for IMU observed data: angular velocities and
|
// Skip IMU data before the first node of this trajectory.
|
||||||
// accelerations.
|
auto it = imu_data.cbegin();
|
||||||
auto it = imu_data_.cbegin();
|
while ((it + 1) != imu_data.cend() &&
|
||||||
while ((it + 1) != imu_data_.cend() && (it + 1)->time <= node_data_[0].time) {
|
(it + 1)->time <= node_data_[node_indices[0]].time) {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t j = 1; j < node_data_.size(); ++j) {
|
for (size_t j = 1; j < node_indices.size(); ++j) {
|
||||||
auto it2 = it;
|
auto it2 = it;
|
||||||
const IntegrateImuResult<double> result = IntegrateImu(
|
const IntegrateImuResult<double> result =
|
||||||
imu_data_, node_data_[j - 1].time, node_data_[j].time, &it);
|
IntegrateImu(imu_data, node_data_[node_indices[j - 1]].time,
|
||||||
if (j + 1 < node_data_.size()) {
|
node_data_[node_indices[j]].time, &it);
|
||||||
const common::Duration first_delta_time =
|
if (j + 1 < node_indices.size()) {
|
||||||
node_data_[j].time - node_data_[j - 1].time;
|
const common::Time first_time = node_data_[node_indices[j - 1]].time;
|
||||||
const common::Duration second_delta_time =
|
const common::Time second_time = node_data_[node_indices[j]].time;
|
||||||
node_data_[j + 1].time - node_data_[j].time;
|
const common::Time third_time = node_data_[node_indices[j + 1]].time;
|
||||||
const common::Time first_center =
|
const common::Duration first_duration = second_time - first_time;
|
||||||
node_data_[j - 1].time + first_delta_time / 2;
|
const common::Duration second_duration = third_time - second_time;
|
||||||
const common::Time second_center =
|
const common::Time first_center = first_time + first_duration / 2;
|
||||||
node_data_[j].time + second_delta_time / 2;
|
const common::Time second_center = second_time + second_duration / 2;
|
||||||
const IntegrateImuResult<double> result_to_first_center =
|
const IntegrateImuResult<double> result_to_first_center =
|
||||||
IntegrateImu(imu_data_, node_data_[j - 1].time, first_center, &it2);
|
IntegrateImu(imu_data, first_time, first_center, &it2);
|
||||||
const IntegrateImuResult<double> result_center_to_center =
|
const IntegrateImuResult<double> result_center_to_center =
|
||||||
IntegrateImu(imu_data_, first_center, second_center, &it2);
|
IntegrateImu(imu_data, first_center, second_center, &it2);
|
||||||
// 'delta_velocity' is the change in velocity from the point in time
|
// 'delta_velocity' is the change in velocity from the point in time
|
||||||
// halfway between the first and second poses to halfway between second
|
// halfway between the first and second poses to halfway between second
|
||||||
// and third pose. It is computed from IMU data and still contains a
|
// and third pose. It is computed from IMU data and still contains a
|
||||||
// delta due to gravity. The orientation of this vector is in the IMU
|
// delta due to gravity. The orientation of this vector is in the IMU
|
||||||
// frame at the second pose.
|
// frame at the second pose.
|
||||||
const Eigen::Vector3d delta_velocity =
|
const Eigen::Vector3d delta_velocity =
|
||||||
(result.delta_rotation.inverse() *
|
(result.delta_rotation.inverse() *
|
||||||
result_to_first_center.delta_rotation) *
|
result_to_first_center.delta_rotation) *
|
||||||
result_center_to_center.delta_velocity;
|
result_center_to_center.delta_velocity;
|
||||||
problem.AddResidualBlock(
|
problem.AddResidualBlock(
|
||||||
new ceres::AutoDiffCostFunction<AccelerationCostFunction, 3, 4, 3, 3,
|
new ceres::AutoDiffCostFunction<AccelerationCostFunction, 3, 4, 3,
|
||||||
3, 1>(new AccelerationCostFunction(
|
3, 3, 1>(
|
||||||
options_.acceleration_weight(), delta_velocity,
|
new AccelerationCostFunction(
|
||||||
common::ToSeconds(first_delta_time),
|
options_.acceleration_weight(), delta_velocity,
|
||||||
common::ToSeconds(second_delta_time))),
|
common::ToSeconds(first_duration),
|
||||||
nullptr, C_point_clouds[j].rotation(),
|
common::ToSeconds(second_duration))),
|
||||||
C_point_clouds[j - 1].translation(), C_point_clouds[j].translation(),
|
nullptr, C_point_clouds[node_indices[j]].rotation(),
|
||||||
C_point_clouds[j + 1].translation(), &gravity_constant_);
|
C_point_clouds[node_indices[j - 1]].translation(),
|
||||||
|
C_point_clouds[node_indices[j]].translation(),
|
||||||
|
C_point_clouds[node_indices[j + 1]].translation(),
|
||||||
|
&gravity_constant_);
|
||||||
|
}
|
||||||
|
problem.AddResidualBlock(
|
||||||
|
new ceres::AutoDiffCostFunction<RotationCostFunction, 3, 4, 4>(
|
||||||
|
new RotationCostFunction(options_.rotation_weight(),
|
||||||
|
result.delta_rotation)),
|
||||||
|
nullptr, C_point_clouds[node_indices[j - 1]].rotation(),
|
||||||
|
C_point_clouds[node_indices[j]].rotation());
|
||||||
}
|
}
|
||||||
problem.AddResidualBlock(
|
|
||||||
new ceres::AutoDiffCostFunction<RotationCostFunction, 3, 4, 4>(
|
|
||||||
new RotationCostFunction(options_.rotation_weight(),
|
|
||||||
result.delta_rotation)),
|
|
||||||
nullptr, C_point_clouds[j - 1].rotation(),
|
|
||||||
C_point_clouds[j].rotation());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve.
|
// Solve.
|
||||||
|
|
|
@ -36,6 +36,8 @@ namespace mapping_3d {
|
||||||
namespace sparse_pose_graph {
|
namespace sparse_pose_graph {
|
||||||
|
|
||||||
struct NodeData {
|
struct NodeData {
|
||||||
|
// TODO(whess): Keep nodes per trajectory instead.
|
||||||
|
const mapping::Submaps* trajectory;
|
||||||
common::Time time;
|
common::Time time;
|
||||||
transform::Rigid3d point_cloud_pose;
|
transform::Rigid3d point_cloud_pose;
|
||||||
};
|
};
|
||||||
|
@ -53,26 +55,24 @@ class OptimizationProblem {
|
||||||
OptimizationProblem(const OptimizationProblem&) = delete;
|
OptimizationProblem(const OptimizationProblem&) = delete;
|
||||||
OptimizationProblem& operator=(const OptimizationProblem&) = delete;
|
OptimizationProblem& operator=(const OptimizationProblem&) = delete;
|
||||||
|
|
||||||
void AddImuData(common::Time time, const Eigen::Vector3d& linear_acceleration,
|
void AddImuData(const mapping::Submaps* trajectory, common::Time time,
|
||||||
|
const Eigen::Vector3d& linear_acceleration,
|
||||||
const Eigen::Vector3d& angular_velocity);
|
const Eigen::Vector3d& angular_velocity);
|
||||||
void AddTrajectoryNode(common::Time time,
|
void AddTrajectoryNode(const mapping::Submaps* trajectory, common::Time time,
|
||||||
const transform::Rigid3d& point_cloud_pose);
|
const transform::Rigid3d& point_cloud_pose);
|
||||||
|
|
||||||
void SetMaxNumIterations(int32 max_num_iterations);
|
void SetMaxNumIterations(int32 max_num_iterations);
|
||||||
|
|
||||||
// Computes the optimized poses. The point cloud at 'point_cloud_poses[i]'
|
// Computes the optimized poses.
|
||||||
// belongs to 'trajectories[i]'. Within a given trajectory, scans are expected
|
|
||||||
// to be contiguous.
|
|
||||||
void Solve(const std::vector<Constraint>& constraints,
|
void Solve(const std::vector<Constraint>& constraints,
|
||||||
const transform::Rigid3d& submap_0_transform,
|
const transform::Rigid3d& submap_0_transform,
|
||||||
const std::vector<const mapping::Submaps*>& trajectories,
|
|
||||||
std::vector<transform::Rigid3d>* submap_transforms);
|
std::vector<transform::Rigid3d>* submap_transforms);
|
||||||
|
|
||||||
const std::vector<NodeData>& node_data() const;
|
const std::vector<NodeData>& node_data() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mapping::sparse_pose_graph::proto::OptimizationProblemOptions options_;
|
mapping::sparse_pose_graph::proto::OptimizationProblemOptions options_;
|
||||||
std::deque<ImuData> imu_data_;
|
std::map<const mapping::Submaps*, std::deque<ImuData>> imu_data_;
|
||||||
std::vector<NodeData> node_data_;
|
std::vector<NodeData> node_data_;
|
||||||
double gravity_constant_ = 9.8;
|
double gravity_constant_ = 9.8;
|
||||||
};
|
};
|
||||||
|
|
|
@ -118,15 +118,14 @@ TEST_F(OptimizationProblemTest, ReducesNoise) {
|
||||||
NoisyNode{RandomTransform(10., 3.), RandomYawOnlyTransform(0.2, 0.3)});
|
NoisyNode{RandomTransform(10., 3.), RandomYawOnlyTransform(0.2, 0.3)});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const mapping::Submaps*> trajectories;
|
|
||||||
common::Time now = common::FromUniversal(0);
|
common::Time now = common::FromUniversal(0);
|
||||||
for (const NoisyNode& node : test_data) {
|
for (const NoisyNode& node : test_data) {
|
||||||
trajectories.push_back(kTrajectory);
|
|
||||||
const transform::Rigid3d pose =
|
const transform::Rigid3d pose =
|
||||||
AddNoise(node.ground_truth_pose, node.noise);
|
AddNoise(node.ground_truth_pose, node.noise);
|
||||||
optimization_problem_.AddImuData(now, Eigen::Vector3d::UnitZ() * 9.81,
|
optimization_problem_.AddImuData(kTrajectory, now,
|
||||||
|
Eigen::Vector3d::UnitZ() * 9.81,
|
||||||
Eigen::Vector3d::Zero());
|
Eigen::Vector3d::Zero());
|
||||||
optimization_problem_.AddTrajectoryNode(now, pose);
|
optimization_problem_.AddTrajectoryNode(kTrajectory, now, pose);
|
||||||
now += common::FromSeconds(0.01);
|
now += common::FromSeconds(0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +165,7 @@ TEST_F(OptimizationProblemTest, ReducesNoise) {
|
||||||
node_data[j].point_cloud_pose);
|
node_data[j].point_cloud_pose);
|
||||||
}
|
}
|
||||||
|
|
||||||
optimization_problem_.Solve(constraints, kSubmap0Transform, trajectories,
|
optimization_problem_.Solve(constraints, kSubmap0Transform,
|
||||||
&submap_transforms);
|
&submap_transforms);
|
||||||
|
|
||||||
double translation_error_after = 0.;
|
double translation_error_after = 0.;
|
||||||
|
|
Loading…
Reference in New Issue