local_trajectory_builder_3d_test.cc
Go to the documentation of this file.
00001 /*
00002  * Copyright 2016 The Cartographer Authors
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *      http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
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   // Computes the earliest intersection of the ray 'from' to 'to' with the
00130   // axis-aligned cube with edge length 30 and center at the origin. Assumes
00131   // that 'from' is inside the cube.
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   // Computes the earliest intersection of the ray 'from' to 'to' with all
00154   // bubbles. Returns 'to' if no intersection exists.
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     // 360 degree rays at 16 angles.
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         // Second orthogonal rangefinder.
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     // We simulate a 30 m edge length box around the origin, also containing
00203     // 50 cm radius spheres.
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     // One second at zero velocity.
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     // Corkscrew translation and constant velocity rotation.
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 }  // namespace
00283 }  // namespace mapping
00284 }  // namespace cartographer


cartographer
Author(s): The Cartographer Authors
autogenerated on Thu May 9 2019 02:27:35