Sematic-Cartographer/cartographer-master/cartographer/mapping/internal/2d/pose_graph_2d_test.cc

319 lines
12 KiB
C++
Raw Normal View History

2022-06-23 19:58:36 +08:00
/*
* Copyright 2016 The Cartographer Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cartographer/mapping/internal/2d/pose_graph_2d.h"
#include <cmath>
#include <memory>
#include <random>
#include "absl/memory/memory.h"
#include "cartographer/common/internal/testing/lua_parameter_dictionary_test_helpers.h"
#include "cartographer/common/thread_pool.h"
#include "cartographer/common/time.h"
#include "cartographer/mapping/2d/probability_grid_range_data_inserter_2d.h"
#include "cartographer/mapping/2d/submap_2d.h"
#include "cartographer/transform/rigid_transform.h"
#include "cartographer/transform/rigid_transform_test_helpers.h"
#include "cartographer/transform/transform.h"
#include "gmock/gmock.h"
namespace cartographer {
namespace mapping {
namespace {
class PoseGraph2DTest : public ::testing::Test {
protected:
PoseGraph2DTest() : thread_pool_(1) {
// Builds a wavy, irregularly circular point cloud that is unique
// rotationally. This gives us good rotational texture and avoids any
// possibility of the CeresScanMatcher2D preferring free space (>
// kMinProbability) to unknown space (== kMinProbability).
for (float t = 0.f; t < 2.f * M_PI; t += 0.005f) {
const float r = (std::sin(20.f * t) + 2.f) * std::sin(t + 2.f);
point_cloud_.push_back(
{Eigen::Vector3f{r * std::sin(t), r * std::cos(t), 0.f}});
}
{
auto parameter_dictionary = common::MakeDictionary(R"text(
return {
num_range_data = 1,
grid_options_2d = {
grid_type = "PROBABILITY_GRID",
resolution = 0.05,
},
range_data_inserter = {
range_data_inserter_type = "PROBABILITY_GRID_INSERTER_2D",
probability_grid_range_data_inserter = {
insert_free_space = true,
hit_probability = 0.53,
miss_probability = 0.495,
},
tsdf_range_data_inserter = {
truncation_distance = 0.3,
maximum_weight = 10.,
update_free_space = false,
normal_estimation_options = {
num_normal_samples = 4,
sample_radius = 0.5,
},
project_sdf_distance_to_scan_normal = false,
update_weight_range_exponent = 0,
update_weight_angle_scan_normal_to_ray_kernel_bandwidth = 0,
update_weight_distance_cell_to_hit_kernel_bandwidth = 0,
},
},
})text");
active_submaps_ = absl::make_unique<ActiveSubmaps2D>(
mapping::CreateSubmapsOptions2D(parameter_dictionary.get()));
}
{
auto parameter_dictionary = common::MakeDictionary(R"text(
return {
optimize_every_n_nodes = 1000,
constraint_builder = {
sampling_ratio = 1.,
max_constraint_distance = 6.,
min_score = 0.5,
global_localization_min_score = 0.6,
loop_closure_translation_weight = 1.,
loop_closure_rotation_weight = 1.,
log_matches = true,
fast_correlative_scan_matcher = {
linear_search_window = 3.,
angular_search_window = 0.1,
branch_and_bound_depth = 3,
},
ceres_scan_matcher = {
occupied_space_weight = 20.,
translation_weight = 10.,
rotation_weight = 1.,
ceres_solver_options = {
use_nonmonotonic_steps = true,
max_num_iterations = 50,
num_threads = 1,
},
},
fast_correlative_scan_matcher_3d = {
branch_and_bound_depth = 3,
full_resolution_depth = 3,
min_rotational_score = 0.1,
min_low_resolution_score = 0.5,
linear_xy_search_window = 4.,
linear_z_search_window = 4.,
angular_search_window = 0.1,
},
ceres_scan_matcher_3d = {
occupied_space_weight_0 = 20.,
translation_weight = 10.,
rotation_weight = 1.,
only_optimize_yaw = true,
ceres_solver_options = {
use_nonmonotonic_steps = true,
max_num_iterations = 50,
num_threads = 1,
},
},
},
matcher_translation_weight = 1.,
matcher_rotation_weight = 1.,
optimization_problem = {
acceleration_weight = 1.,
rotation_weight = 1e2,
huber_scale = 1.,
local_slam_pose_translation_weight = 0.,
local_slam_pose_rotation_weight = 0.,
odometry_translation_weight = 0.,
odometry_rotation_weight = 0.,
fixed_frame_pose_translation_weight = 1e1,
fixed_frame_pose_rotation_weight = 1e2,
fixed_frame_pose_use_tolerant_loss = false,
fixed_frame_pose_tolerant_loss_param_a = 1,
fixed_frame_pose_tolerant_loss_param_b = 1,
log_solver_summary = true,
use_online_imu_extrinsics_in_3d = true,
fix_z_in_3d = false,
ceres_solver_options = {
use_nonmonotonic_steps = false,
max_num_iterations = 200,
num_threads = 1,
},
},
max_num_final_iterations = 200,
global_sampling_ratio = 0.01,
log_residual_histograms = true,
global_constraint_search_after_n_seconds = 10.0,
})text");
auto options = CreatePoseGraphOptions(parameter_dictionary.get());
pose_graph_ = absl::make_unique<PoseGraph2D>(
options,
absl::make_unique<optimization::OptimizationProblem2D>(
options.optimization_problem_options()),
&thread_pool_);
}
current_pose_ = transform::Rigid2d::Identity();
}
void MoveRelativeWithNoise(const transform::Rigid2d& movement,
const transform::Rigid2d& noise) {
current_pose_ = current_pose_ * movement;
const sensor::PointCloud new_point_cloud = sensor::TransformPointCloud(
point_cloud_,
transform::Embed3D(current_pose_.inverse().cast<float>()));
const sensor::RangeData range_data{
Eigen::Vector3f::Zero(), new_point_cloud, {}};
const transform::Rigid2d pose_estimate = noise * current_pose_;
constexpr int kTrajectoryId = 0;
active_submaps_->InsertRangeData(TransformRangeData(
range_data, transform::Embed3D(pose_estimate.cast<float>())));
std::vector<std::shared_ptr<const Submap2D>> insertion_submaps;
for (const auto& submap : active_submaps_->submaps()) {
insertion_submaps.push_back(submap);
}
pose_graph_->AddNode(
std::make_shared<const TrajectoryNode::Data>(
TrajectoryNode::Data{common::FromUniversal(0),
Eigen::Quaterniond::Identity(),
range_data.returns,
{},
{},
{},
transform::Embed3D(pose_estimate)}),
kTrajectoryId, insertion_submaps);
}
void MoveRelative(const transform::Rigid2d& movement) {
MoveRelativeWithNoise(movement, transform::Rigid2d::Identity());
}
template <typename Range>
std::vector<int> ToVectorInt(const Range& range) {
return std::vector<int>(range.begin(), range.end());
}
sensor::PointCloud point_cloud_;
std::unique_ptr<ActiveSubmaps2D> active_submaps_;
common::ThreadPool thread_pool_;
std::unique_ptr<PoseGraph2D> pose_graph_;
transform::Rigid2d current_pose_;
};
TEST_F(PoseGraph2DTest, EmptyMap) {
pose_graph_->RunFinalOptimization();
const auto nodes = pose_graph_->GetTrajectoryNodes();
EXPECT_TRUE(nodes.empty());
}
TEST_F(PoseGraph2DTest, NoMovement) {
MoveRelative(transform::Rigid2d::Identity());
MoveRelative(transform::Rigid2d::Identity());
MoveRelative(transform::Rigid2d::Identity());
pose_graph_->RunFinalOptimization();
const auto nodes = pose_graph_->GetTrajectoryNodes();
ASSERT_THAT(ToVectorInt(nodes.trajectory_ids()),
::testing::ContainerEq(std::vector<int>{0}));
EXPECT_THAT(nodes.SizeOfTrajectoryOrZero(0), ::testing::Eq(3u));
EXPECT_THAT(nodes.at(NodeId{0, 0}).global_pose,
transform::IsNearly(transform::Rigid3d::Identity(), 1e-2));
EXPECT_THAT(nodes.at(NodeId{0, 1}).global_pose,
transform::IsNearly(transform::Rigid3d::Identity(), 1e-2));
EXPECT_THAT(nodes.at(NodeId{0, 2}).global_pose,
transform::IsNearly(transform::Rigid3d::Identity(), 1e-2));
}
TEST_F(PoseGraph2DTest, NoOverlappingNodes) {
std::mt19937 rng(0);
std::uniform_real_distribution<double> distribution(-1., 1.);
std::vector<transform::Rigid2d> poses;
for (int i = 0; i != 4; ++i) {
MoveRelative(transform::Rigid2d({0.25 * distribution(rng), 5.}, 0.));
poses.emplace_back(current_pose_);
}
pose_graph_->RunFinalOptimization();
const auto nodes = pose_graph_->GetTrajectoryNodes();
ASSERT_THAT(ToVectorInt(nodes.trajectory_ids()),
::testing::ContainerEq(std::vector<int>{0}));
for (int i = 0; i != 4; ++i) {
EXPECT_THAT(
poses[i],
IsNearly(transform::Project2D(nodes.at(NodeId{0, i}).global_pose),
1e-2))
<< i;
}
}
TEST_F(PoseGraph2DTest, ConsecutivelyOverlappingNodes) {
std::mt19937 rng(0);
std::uniform_real_distribution<double> distribution(-1., 1.);
std::vector<transform::Rigid2d> poses;
for (int i = 0; i != 5; ++i) {
MoveRelative(transform::Rigid2d({0.25 * distribution(rng), 2.}, 0.));
poses.emplace_back(current_pose_);
}
pose_graph_->RunFinalOptimization();
const auto nodes = pose_graph_->GetTrajectoryNodes();
ASSERT_THAT(ToVectorInt(nodes.trajectory_ids()),
::testing::ContainerEq(std::vector<int>{0}));
for (int i = 0; i != 5; ++i) {
EXPECT_THAT(
poses[i],
IsNearly(transform::Project2D(nodes.at(NodeId{0, i}).global_pose),
1e-2))
<< i;
}
}
TEST_F(PoseGraph2DTest, OverlappingNodes) {
std::mt19937 rng(0);
std::uniform_real_distribution<double> distribution(-1., 1.);
std::vector<transform::Rigid2d> ground_truth;
std::vector<transform::Rigid2d> poses;
for (int i = 0; i != 5; ++i) {
const double noise_x = 0.1 * distribution(rng);
const double noise_y = 0.1 * distribution(rng);
const double noise_orientation = 0.1 * distribution(rng);
transform::Rigid2d noise({noise_x, noise_y}, noise_orientation);
MoveRelativeWithNoise(
transform::Rigid2d({0.15 * distribution(rng), 0.4}, 0.), noise);
ground_truth.emplace_back(current_pose_);
poses.emplace_back(noise * current_pose_);
}
pose_graph_->RunFinalOptimization();
const auto nodes = pose_graph_->GetTrajectoryNodes();
ASSERT_THAT(ToVectorInt(nodes.trajectory_ids()),
::testing::ContainerEq(std::vector<int>{0}));
transform::Rigid2d true_movement =
ground_truth.front().inverse() * ground_truth.back();
transform::Rigid2d movement_before = poses.front().inverse() * poses.back();
transform::Rigid2d error_before = movement_before.inverse() * true_movement;
transform::Rigid3d optimized_movement =
nodes.BeginOfTrajectory(0)->data.global_pose.inverse() *
std::prev(nodes.EndOfTrajectory(0))->data.global_pose;
transform::Rigid2d optimized_error =
transform::Project2D(optimized_movement).inverse() * true_movement;
EXPECT_THAT(std::abs(optimized_error.normalized_angle()),
::testing::Lt(std::abs(error_before.normalized_angle())));
EXPECT_THAT(optimized_error.translation().norm(),
::testing::Lt(error_before.translation().norm()));
}
} // namespace
} // namespace mapping
} // namespace cartographer