00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 #include "cartographer/mapping/internal/3d/local_trajectory_builder_3d.h"
00018
00019 #include <memory>
00020 #include <random>
00021
00022 #include "Eigen/Core"
00023 #include "cartographer/common/lua_parameter_dictionary_test_helpers.h"
00024 #include "cartographer/common/time.h"
00025 #include "cartographer/mapping/3d/hybrid_grid.h"
00026 #include "cartographer/mapping/internal/3d/local_trajectory_builder_options_3d.h"
00027 #include "cartographer/sensor/range_data.h"
00028 #include "cartographer/transform/rigid_transform.h"
00029 #include "cartographer/transform/rigid_transform_test_helpers.h"
00030 #include "cartographer/transform/transform.h"
00031 #include "glog/logging.h"
00032 #include "gmock/gmock.h"
00033
00034 namespace cartographer {
00035 namespace mapping {
00036 namespace {
00037
00038 constexpr char kSensorId[] = "sensor_id";
00039
00040 class LocalTrajectoryBuilderTest : public ::testing::Test {
00041 protected:
00042 struct TrajectoryNode {
00043 common::Time time;
00044 transform::Rigid3d pose;
00045 };
00046
00047 void SetUp() override { GenerateBubbles(); }
00048
00049 mapping::proto::LocalTrajectoryBuilderOptions3D
00050 CreateTrajectoryBuilderOptions3D() {
00051 auto parameter_dictionary = common::MakeDictionary(R"text(
00052 return {
00053 min_range = 0.5,
00054 max_range = 50.,
00055 num_accumulated_range_data = 1,
00056 voxel_filter_size = 0.2,
00057
00058 high_resolution_adaptive_voxel_filter = {
00059 max_length = 0.7,
00060 min_num_points = 200,
00061 max_range = 50.,
00062 },
00063
00064 low_resolution_adaptive_voxel_filter = {
00065 max_length = 0.7,
00066 min_num_points = 200,
00067 max_range = 50.,
00068 },
00069
00070 use_online_correlative_scan_matching = false,
00071 real_time_correlative_scan_matcher = {
00072 linear_search_window = 0.2,
00073 angular_search_window = math.rad(1.),
00074 translation_delta_cost_weight = 1e-1,
00075 rotation_delta_cost_weight = 1.,
00076 },
00077
00078 ceres_scan_matcher = {
00079 occupied_space_weight_0 = 5.,
00080 occupied_space_weight_1 = 20.,
00081 translation_weight = 0.1,
00082 rotation_weight = 0.3,
00083 only_optimize_yaw = false,
00084 ceres_solver_options = {
00085 use_nonmonotonic_steps = true,
00086 max_num_iterations = 20,
00087 num_threads = 1,
00088 },
00089 },
00090
00091 motion_filter = {
00092 max_time_seconds = 0.2,
00093 max_distance_meters = 0.02,
00094 max_angle_radians = 0.001,
00095 },
00096
00097 imu_gravity_time_constant = 1.,
00098 rotational_histogram_size = 120,
00099
00100 submaps = {
00101 high_resolution = 0.2,
00102 high_resolution_max_range = 50.,
00103 low_resolution = 0.5,
00104 num_range_data = 45000,
00105 range_data_inserter = {
00106 hit_probability = 0.7,
00107 miss_probability = 0.4,
00108 num_free_space_voxels = 0,
00109 },
00110 },
00111 }
00112 )text");
00113 return mapping::CreateLocalTrajectoryBuilderOptions3D(
00114 parameter_dictionary.get());
00115 }
00116
00117 void GenerateBubbles() {
00118 std::mt19937 prng(42);
00119 std::uniform_real_distribution<float> distribution(-1.f, 1.f);
00120
00121 for (int i = 0; i != 100; ++i) {
00122 const float x = distribution(prng);
00123 const float y = distribution(prng);
00124 const float z = distribution(prng);
00125 bubbles_.push_back(10. * Eigen::Vector3f(x, y, z).normalized());
00126 }
00127 }
00128
00129
00130
00131
00132 Eigen::Vector3f CollideWithBox(const Eigen::Vector3f& from,
00133 const Eigen::Vector3f& to) {
00134 float first = 100.f;
00135 if (to.x() > from.x()) {
00136 first = std::min(first, (15.f - from.x()) / (to.x() - from.x()));
00137 } else if (to.x() < from.x()) {
00138 first = std::min(first, (-15.f - from.x()) / (to.x() - from.x()));
00139 }
00140 if (to.y() > from.y()) {
00141 first = std::min(first, (15.f - from.y()) / (to.y() - from.y()));
00142 } else if (to.y() < from.y()) {
00143 first = std::min(first, (-15.f - from.y()) / (to.y() - from.y()));
00144 }
00145 if (to.z() > from.z()) {
00146 first = std::min(first, (15.f - from.z()) / (to.z() - from.z()));
00147 } else if (to.z() < from.z()) {
00148 first = std::min(first, (-15.f - from.z()) / (to.z() - from.z()));
00149 }
00150 return first * (to - from) + from;
00151 }
00152
00153
00154
00155 Eigen::Vector3f CollideWithBubbles(const Eigen::Vector3f& from,
00156 const Eigen::Vector3f& to) {
00157 float first = 1.f;
00158 constexpr float kBubbleRadius = 0.5f;
00159 for (const Eigen::Vector3f& center : bubbles_) {
00160 const float a = (to - from).squaredNorm();
00161 const float beta = (to - from).dot(from - center);
00162 const float c =
00163 (from - center).squaredNorm() - kBubbleRadius * kBubbleRadius;
00164 const float discriminant = beta * beta - a * c;
00165 if (discriminant < 0.f) {
00166 continue;
00167 }
00168 const float solution = (-beta - std::sqrt(discriminant)) / a;
00169 if (solution < 0.f) {
00170 continue;
00171 }
00172 first = std::min(first, solution);
00173 }
00174 return first * (to - from) + from;
00175 }
00176
00177 sensor::TimedRangeData GenerateRangeData(const transform::Rigid3d& pose) {
00178
00179 sensor::TimedPointCloud directions_in_rangefinder_frame;
00180 for (int r = -8; r != 8; ++r) {
00181 for (int s = -250; s != 250; ++s) {
00182 const sensor::TimedRangefinderPoint first_point{
00183 Eigen::Vector3f{
00184 Eigen::AngleAxisf(M_PI * s / 250., Eigen::Vector3f::UnitZ()) *
00185 Eigen::AngleAxisf(M_PI / 12. * r / 8.,
00186 Eigen::Vector3f::UnitY()) *
00187 Eigen::Vector3f::UnitX()},
00188 0.};
00189 directions_in_rangefinder_frame.push_back(first_point);
00190
00191 const sensor::TimedRangefinderPoint second_point{
00192 Eigen::Vector3f{
00193 Eigen::AngleAxisf(M_PI / 2., Eigen::Vector3f::UnitX()) *
00194 Eigen::AngleAxisf(M_PI * s / 250., Eigen::Vector3f::UnitZ()) *
00195 Eigen::AngleAxisf(M_PI / 12. * r / 8.,
00196 Eigen::Vector3f::UnitY()) *
00197 Eigen::Vector3f::UnitX()},
00198 0.};
00199 directions_in_rangefinder_frame.push_back(second_point);
00200 }
00201 }
00202
00203
00204 sensor::TimedPointCloud returns_in_world_frame;
00205 for (const auto& direction_in_world_frame :
00206 sensor::TransformTimedPointCloud(directions_in_rangefinder_frame,
00207 pose.cast<float>())) {
00208 const Eigen::Vector3f origin =
00209 pose.cast<float>() * Eigen::Vector3f::Zero();
00210 const sensor::TimedRangefinderPoint return_point{
00211 CollideWithBubbles(
00212 origin,
00213 CollideWithBox(origin, direction_in_world_frame.position)),
00214 0.};
00215 returns_in_world_frame.push_back(return_point);
00216 }
00217 return {Eigen::Vector3f::Zero(),
00218 sensor::TransformTimedPointCloud(returns_in_world_frame,
00219 pose.inverse().cast<float>()),
00220 {}};
00221 }
00222
00223 void AddLinearOnlyImuObservation(const common::Time time,
00224 const transform::Rigid3d& expected_pose) {
00225 const Eigen::Vector3d gravity =
00226 expected_pose.rotation().inverse() * Eigen::Vector3d(0., 0., 9.81);
00227 local_trajectory_builder_->AddImuData(
00228 sensor::ImuData{time, gravity, Eigen::Vector3d::Zero()});
00229 }
00230
00231 std::vector<TrajectoryNode> GenerateCorkscrewTrajectory() {
00232 std::vector<TrajectoryNode> trajectory;
00233 common::Time current_time = common::FromUniversal(12345678);
00234
00235 for (int i = 0; i != 5; ++i) {
00236 current_time += common::FromSeconds(0.3);
00237 trajectory.push_back(
00238 TrajectoryNode{current_time, transform::Rigid3d::Identity()});
00239 }
00240
00241 for (double t = 0.; t <= 0.6; t += 0.05) {
00242 current_time += common::FromSeconds(0.3);
00243 trajectory.push_back(TrajectoryNode{
00244 current_time,
00245 transform::Rigid3d::Translation(Eigen::Vector3d(
00246 std::sin(4. * t), 1. - std::cos(4. * t), 1. * t)) *
00247 transform::Rigid3d::Rotation(Eigen::AngleAxisd(
00248 0.3 * t, Eigen::Vector3d(1., -1., 2.).normalized()))});
00249 }
00250 return trajectory;
00251 }
00252
00253 void VerifyAccuracy(const std::vector<TrajectoryNode>& expected_trajectory,
00254 double expected_accuracy) {
00255 int num_poses = 0;
00256 for (const TrajectoryNode& node : expected_trajectory) {
00257 AddLinearOnlyImuObservation(node.time, node.pose);
00258 const auto range_data = GenerateRangeData(node.pose);
00259 const std::unique_ptr<LocalTrajectoryBuilder3D::MatchingResult>
00260 matching_result = local_trajectory_builder_->AddRangeData(
00261 kSensorId, sensor::TimedPointCloudData{
00262 node.time, range_data.origin, range_data.returns});
00263 if (matching_result != nullptr) {
00264 EXPECT_THAT(matching_result->local_pose,
00265 transform::IsNearly(node.pose, 1e-1));
00266 ++num_poses;
00267 LOG(INFO) << "num_poses: " << num_poses;
00268 }
00269 }
00270 }
00271
00272 std::unique_ptr<LocalTrajectoryBuilder3D> local_trajectory_builder_;
00273 std::vector<Eigen::Vector3f> bubbles_;
00274 };
00275
00276 TEST_F(LocalTrajectoryBuilderTest, MoveInsideCubeUsingOnlyCeresScanMatcher) {
00277 local_trajectory_builder_.reset(new LocalTrajectoryBuilder3D(
00278 CreateTrajectoryBuilderOptions3D(), {kSensorId}));
00279 VerifyAccuracy(GenerateCorkscrewTrajectory(), 1e-1);
00280 }
00281
00282 }
00283 }
00284 }