local_trajectory_builder_3d_test.cc
Go to the documentation of this file.
1 /*
2  * Copyright 2016 The Cartographer Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
18 
19 #include <memory>
20 #include <random>
21 
22 #include "Eigen/Core"
31 #include "glog/logging.h"
32 #include "gmock/gmock.h"
33 
34 namespace cartographer {
35 namespace mapping {
36 namespace {
37 
38 constexpr char kSensorId[] = "sensor_id";
39 
40 class LocalTrajectoryBuilderTest : public ::testing::Test {
41  protected:
42  struct TrajectoryNode {
45  };
46 
47  void SetUp() override { GenerateBubbles(); }
48 
49  mapping::proto::LocalTrajectoryBuilderOptions3D
50  CreateTrajectoryBuilderOptions3D() {
51  auto parameter_dictionary = common::MakeDictionary(R"text(
52  return {
53  min_range = 0.5,
54  max_range = 50.,
55  num_accumulated_range_data = 1,
56  voxel_filter_size = 0.2,
57 
58  high_resolution_adaptive_voxel_filter = {
59  max_length = 0.7,
60  min_num_points = 200,
61  max_range = 50.,
62  },
63 
64  low_resolution_adaptive_voxel_filter = {
65  max_length = 0.7,
66  min_num_points = 200,
67  max_range = 50.,
68  },
69 
70  use_online_correlative_scan_matching = false,
71  real_time_correlative_scan_matcher = {
72  linear_search_window = 0.2,
73  angular_search_window = math.rad(1.),
74  translation_delta_cost_weight = 1e-1,
75  rotation_delta_cost_weight = 1.,
76  },
77 
78  ceres_scan_matcher = {
79  occupied_space_weight_0 = 5.,
80  occupied_space_weight_1 = 20.,
81  translation_weight = 0.1,
82  rotation_weight = 0.3,
83  only_optimize_yaw = false,
84  ceres_solver_options = {
85  use_nonmonotonic_steps = true,
86  max_num_iterations = 20,
87  num_threads = 1,
88  },
89  },
90 
91  motion_filter = {
92  max_time_seconds = 0.2,
93  max_distance_meters = 0.02,
94  max_angle_radians = 0.001,
95  },
96 
97  imu_gravity_time_constant = 1.,
98  rotational_histogram_size = 120,
99 
100  submaps = {
101  high_resolution = 0.2,
102  high_resolution_max_range = 50.,
103  low_resolution = 0.5,
104  num_range_data = 45000,
105  range_data_inserter = {
106  hit_probability = 0.7,
107  miss_probability = 0.4,
108  num_free_space_voxels = 0,
109  },
110  },
111  }
112  )text");
114  parameter_dictionary.get());
115  }
116 
117  void GenerateBubbles() {
118  std::mt19937 prng(42);
119  std::uniform_real_distribution<float> distribution(-1.f, 1.f);
120 
121  for (int i = 0; i != 100; ++i) {
122  const float x = distribution(prng);
123  const float y = distribution(prng);
124  const float z = distribution(prng);
125  bubbles_.push_back(10. * Eigen::Vector3f(x, y, z).normalized());
126  }
127  }
128 
129  // Computes the earliest intersection of the ray 'from' to 'to' with the
130  // axis-aligned cube with edge length 30 and center at the origin. Assumes
131  // that 'from' is inside the cube.
132  Eigen::Vector3f CollideWithBox(const Eigen::Vector3f& from,
133  const Eigen::Vector3f& to) {
134  float first = 100.f;
135  if (to.x() > from.x()) {
136  first = std::min(first, (15.f - from.x()) / (to.x() - from.x()));
137  } else if (to.x() < from.x()) {
138  first = std::min(first, (-15.f - from.x()) / (to.x() - from.x()));
139  }
140  if (to.y() > from.y()) {
141  first = std::min(first, (15.f - from.y()) / (to.y() - from.y()));
142  } else if (to.y() < from.y()) {
143  first = std::min(first, (-15.f - from.y()) / (to.y() - from.y()));
144  }
145  if (to.z() > from.z()) {
146  first = std::min(first, (15.f - from.z()) / (to.z() - from.z()));
147  } else if (to.z() < from.z()) {
148  first = std::min(first, (-15.f - from.z()) / (to.z() - from.z()));
149  }
150  return first * (to - from) + from;
151  }
152 
153  // Computes the earliest intersection of the ray 'from' to 'to' with all
154  // bubbles. Returns 'to' if no intersection exists.
155  Eigen::Vector3f CollideWithBubbles(const Eigen::Vector3f& from,
156  const Eigen::Vector3f& to) {
157  float first = 1.f;
158  constexpr float kBubbleRadius = 0.5f;
159  for (const Eigen::Vector3f& center : bubbles_) {
160  const float a = (to - from).squaredNorm();
161  const float beta = (to - from).dot(from - center);
162  const float c =
163  (from - center).squaredNorm() - kBubbleRadius * kBubbleRadius;
164  const float discriminant = beta * beta - a * c;
165  if (discriminant < 0.f) {
166  continue;
167  }
168  const float solution = (-beta - std::sqrt(discriminant)) / a;
169  if (solution < 0.f) {
170  continue;
171  }
172  first = std::min(first, solution);
173  }
174  return first * (to - from) + from;
175  }
176 
177  sensor::TimedRangeData GenerateRangeData(const transform::Rigid3d& pose) {
178  // 360 degree rays at 16 angles.
179  sensor::TimedPointCloud directions_in_rangefinder_frame;
180  for (int r = -8; r != 8; ++r) {
181  for (int s = -250; s != 250; ++s) {
182  Eigen::Vector4f first_point;
183  first_point << Eigen::AngleAxisf(M_PI * s / 250.,
184  Eigen::Vector3f::UnitZ()) *
185  Eigen::AngleAxisf(M_PI / 12. * r / 8.,
186  Eigen::Vector3f::UnitY()) *
187  Eigen::Vector3f::UnitX(),
188  0.;
189  directions_in_rangefinder_frame.push_back(first_point);
190  // Second orthogonal rangefinder.
191  Eigen::Vector4f second_point;
192  second_point << Eigen::AngleAxisf(M_PI / 2., Eigen::Vector3f::UnitX()) *
193  Eigen::AngleAxisf(M_PI * s / 250.,
194  Eigen::Vector3f::UnitZ()) *
195  Eigen::AngleAxisf(M_PI / 12. * r / 8.,
196  Eigen::Vector3f::UnitY()) *
197  Eigen::Vector3f::UnitX(),
198  0.;
199  directions_in_rangefinder_frame.push_back(second_point);
200  }
201  }
202  // We simulate a 30 m edge length box around the origin, also containing
203  // 50 cm radius spheres.
204  sensor::TimedPointCloud returns_in_world_frame;
205  for (const Eigen::Vector4f& direction_in_world_frame :
206  sensor::TransformTimedPointCloud(directions_in_rangefinder_frame,
207  pose.cast<float>())) {
208  const Eigen::Vector3f origin =
209  pose.cast<float>() * Eigen::Vector3f::Zero();
210  Eigen::Vector4f return_point;
211  return_point << CollideWithBubbles(
212  origin, CollideWithBox(origin, direction_in_world_frame.head<3>())),
213  0.;
214  returns_in_world_frame.push_back(return_point);
215  }
216  return {Eigen::Vector3f::Zero(),
217  sensor::TransformTimedPointCloud(returns_in_world_frame,
218  pose.inverse().cast<float>()),
219  {}};
220  }
221 
222  void AddLinearOnlyImuObservation(const common::Time time,
223  const transform::Rigid3d& expected_pose) {
224  const Eigen::Vector3d gravity =
225  expected_pose.rotation().inverse() * Eigen::Vector3d(0., 0., 9.81);
226  local_trajectory_builder_->AddImuData(
227  sensor::ImuData{time, gravity, Eigen::Vector3d::Zero()});
228  }
229 
230  std::vector<TrajectoryNode> GenerateCorkscrewTrajectory() {
231  std::vector<TrajectoryNode> trajectory;
232  common::Time current_time = common::FromUniversal(12345678);
233  // One second at zero velocity.
234  for (int i = 0; i != 5; ++i) {
235  current_time += common::FromSeconds(0.3);
236  trajectory.push_back(
237  TrajectoryNode{current_time, transform::Rigid3d::Identity()});
238  }
239  // Corkscrew translation and constant velocity rotation.
240  for (double t = 0.; t <= 0.6; t += 0.05) {
241  current_time += common::FromSeconds(0.3);
242  trajectory.push_back(TrajectoryNode{
243  current_time,
244  transform::Rigid3d::Translation(Eigen::Vector3d(
245  std::sin(4. * t), 1. - std::cos(4. * t), 1. * t)) *
246  transform::Rigid3d::Rotation(Eigen::AngleAxisd(
247  0.3 * t, Eigen::Vector3d(1., -1., 2.).normalized()))});
248  }
249  return trajectory;
250  }
251 
252  void VerifyAccuracy(const std::vector<TrajectoryNode>& expected_trajectory,
253  double expected_accuracy) {
254  int num_poses = 0;
255  for (const TrajectoryNode& node : expected_trajectory) {
256  AddLinearOnlyImuObservation(node.time, node.pose);
257  const auto range_data = GenerateRangeData(node.pose);
258  const std::unique_ptr<LocalTrajectoryBuilder3D::MatchingResult>
259  matching_result = local_trajectory_builder_->AddRangeData(
260  kSensorId, sensor::TimedPointCloudData{
261  node.time, range_data.origin, range_data.returns});
262  if (matching_result != nullptr) {
263  EXPECT_THAT(matching_result->local_pose,
264  transform::IsNearly(node.pose, 1e-1));
265  ++num_poses;
266  LOG(INFO) << "num_poses: " << num_poses;
267  }
268  }
269  }
270 
271  std::unique_ptr<LocalTrajectoryBuilder3D> local_trajectory_builder_;
272  std::vector<Eigen::Vector3f> bubbles_;
273 };
274 
275 TEST_F(LocalTrajectoryBuilderTest, MoveInsideCubeUsingOnlyCeresScanMatcher) {
276  local_trajectory_builder_.reset(new LocalTrajectoryBuilder3D(
277  CreateTrajectoryBuilderOptions3D(), {kSensorId}));
278  VerifyAccuracy(GenerateCorkscrewTrajectory(), 1e-1);
279 }
280 
281 } // namespace
282 } // namespace mapping
283 } // namespace cartographer
TimedPointCloud TransformTimedPointCloud(const TimedPointCloud &point_cloud, const transform::Rigid3f &transform)
Definition: point_cloud.cc:35
static Rigid3 Rotation(const AngleAxis &angle_axis)
Rigid3< double > Rigid3d
std::unique_ptr< LuaParameterDictionary > MakeDictionary(const std::string &code)
UniversalTimeScaleClock::time_point Time
Definition: time.h:44
common::Time time
Time FromUniversal(const int64 ticks)
Definition: time.cc:34
Duration FromSeconds(const double seconds)
Definition: time.cc:24
std::vector< Eigen::Vector4f > TimedPointCloud
Definition: point_cloud.h:39
std::vector< Eigen::Vector3f > bubbles_
transform::Rigid3d pose
static Rigid3 Translation(const Vector &vector)
std::unique_ptr< LocalTrajectoryBuilder3D > local_trajectory_builder_
proto::LocalTrajectoryBuilderOptions3D CreateLocalTrajectoryBuilderOptions3D(common::LuaParameterDictionary *parameter_dictionary)


cartographer
Author(s): The Cartographer Authors
autogenerated on Mon Feb 28 2022 22:00:58