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
00021 #include "absl/memory/memory.h"
00022 #include "cartographer/common/time.h"
00023 #include "cartographer/mapping/internal/3d/scan_matching/rotational_scan_matcher.h"
00024 #include "cartographer/mapping/proto/3d/local_trajectory_builder_options_3d.pb.h"
00025 #include "cartographer/mapping/proto/3d/submaps_options_3d.pb.h"
00026 #include "cartographer/mapping/proto/scan_matching//ceres_scan_matcher_options_3d.pb.h"
00027 #include "cartographer/mapping/proto/scan_matching//real_time_correlative_scan_matcher_options.pb.h"
00028 #include "glog/logging.h"
00029
00030 namespace cartographer {
00031 namespace mapping {
00032
00033
00034
00035 static auto* kLocalSlamLatencyMetric = metrics::Gauge::Null();
00036 static auto* kLocalSlamVoxelFilterFraction = metrics::Gauge::Null();
00037 static auto* kLocalSlamScanMatcherFraction = metrics::Gauge::Null();
00038 static auto* kLocalSlamInsertIntoSubmapFraction = metrics::Gauge::Null();
00039 static auto* kLocalSlamRealTimeRatio = metrics::Gauge::Null();
00040 static auto* kLocalSlamCpuRealTimeRatio = metrics::Gauge::Null();
00041 static auto* kRealTimeCorrelativeScanMatcherScoreMetric =
00042 metrics::Histogram::Null();
00043 static auto* kCeresScanMatcherCostMetric = metrics::Histogram::Null();
00044 static auto* kScanMatcherResidualDistanceMetric = metrics::Histogram::Null();
00045 static auto* kScanMatcherResidualAngleMetric = metrics::Histogram::Null();
00046
00047 LocalTrajectoryBuilder3D::LocalTrajectoryBuilder3D(
00048 const mapping::proto::LocalTrajectoryBuilderOptions3D& options,
00049 const std::vector<std::string>& expected_range_sensor_ids)
00050 : options_(options),
00051 active_submaps_(options.submaps_options()),
00052 motion_filter_(options.motion_filter_options()),
00053 real_time_correlative_scan_matcher_(
00054 absl::make_unique<scan_matching::RealTimeCorrelativeScanMatcher3D>(
00055 options_.real_time_correlative_scan_matcher_options())),
00056 ceres_scan_matcher_(absl::make_unique<scan_matching::CeresScanMatcher3D>(
00057 options_.ceres_scan_matcher_options())),
00058 accumulated_range_data_{Eigen::Vector3f::Zero(), {}, {}},
00059 range_data_collator_(expected_range_sensor_ids) {}
00060
00061 LocalTrajectoryBuilder3D::~LocalTrajectoryBuilder3D() {}
00062
00063 std::unique_ptr<transform::Rigid3d> LocalTrajectoryBuilder3D::ScanMatch(
00064 const transform::Rigid3d& pose_prediction,
00065 const sensor::PointCloud& low_resolution_point_cloud_in_tracking,
00066 const sensor::PointCloud& high_resolution_point_cloud_in_tracking) {
00067 if (active_submaps_.submaps().empty()) {
00068 return absl::make_unique<transform::Rigid3d>(pose_prediction);
00069 }
00070 std::shared_ptr<const mapping::Submap3D> matching_submap =
00071 active_submaps_.submaps().front();
00072 transform::Rigid3d initial_ceres_pose =
00073 matching_submap->local_pose().inverse() * pose_prediction;
00074 if (options_.use_online_correlative_scan_matching()) {
00075
00076 const transform::Rigid3d initial_pose = initial_ceres_pose;
00077 const double score = real_time_correlative_scan_matcher_->Match(
00078 initial_pose, high_resolution_point_cloud_in_tracking,
00079 matching_submap->high_resolution_hybrid_grid(), &initial_ceres_pose);
00080 kRealTimeCorrelativeScanMatcherScoreMetric->Observe(score);
00081 }
00082
00083 transform::Rigid3d pose_observation_in_submap;
00084 ceres::Solver::Summary summary;
00085 ceres_scan_matcher_->Match(
00086 (matching_submap->local_pose().inverse() * pose_prediction).translation(),
00087 initial_ceres_pose,
00088 {{&high_resolution_point_cloud_in_tracking,
00089 &matching_submap->high_resolution_hybrid_grid()},
00090 {&low_resolution_point_cloud_in_tracking,
00091 &matching_submap->low_resolution_hybrid_grid()}},
00092 &pose_observation_in_submap, &summary);
00093 kCeresScanMatcherCostMetric->Observe(summary.final_cost);
00094 const double residual_distance = (pose_observation_in_submap.translation() -
00095 initial_ceres_pose.translation())
00096 .norm();
00097 kScanMatcherResidualDistanceMetric->Observe(residual_distance);
00098 const double residual_angle =
00099 pose_observation_in_submap.rotation().angularDistance(
00100 initial_ceres_pose.rotation());
00101 kScanMatcherResidualAngleMetric->Observe(residual_angle);
00102 return absl::make_unique<transform::Rigid3d>(matching_submap->local_pose() *
00103 pose_observation_in_submap);
00104 }
00105
00106 void LocalTrajectoryBuilder3D::AddImuData(const sensor::ImuData& imu_data) {
00107 if (extrapolator_ != nullptr) {
00108 extrapolator_->AddImuData(imu_data);
00109 return;
00110 }
00111
00112
00113
00114 constexpr double kExtrapolationEstimationTimeSec = 0.001;
00115 extrapolator_ = mapping::PoseExtrapolator::InitializeWithImu(
00116 ::cartographer::common::FromSeconds(kExtrapolationEstimationTimeSec),
00117 options_.imu_gravity_time_constant(), imu_data);
00118 }
00119
00120 std::unique_ptr<LocalTrajectoryBuilder3D::MatchingResult>
00121 LocalTrajectoryBuilder3D::AddRangeData(
00122 const std::string& sensor_id,
00123 const sensor::TimedPointCloudData& unsynchronized_data) {
00124 const auto synchronized_data =
00125 range_data_collator_.AddRangeData(sensor_id, unsynchronized_data);
00126 if (synchronized_data.ranges.empty()) {
00127 LOG(INFO) << "Range data collator filling buffer.";
00128 return nullptr;
00129 }
00130
00131 const common::Time& current_sensor_time = synchronized_data.time;
00132 if (extrapolator_ == nullptr) {
00133
00134
00135 LOG(INFO) << "IMU not yet initialized.";
00136 return nullptr;
00137 }
00138
00139 CHECK(!synchronized_data.ranges.empty());
00140 CHECK_LE(synchronized_data.ranges.back().point_time.time, 0.f);
00141 const common::Time time_first_point =
00142 current_sensor_time +
00143 common::FromSeconds(synchronized_data.ranges.front().point_time.time);
00144 if (time_first_point < extrapolator_->GetLastPoseTime()) {
00145 LOG(INFO) << "Extrapolator is still initializing.";
00146 return nullptr;
00147 }
00148
00149 std::vector<sensor::TimedPointCloudOriginData::RangeMeasurement> hits =
00150 sensor::VoxelFilter(0.5f * options_.voxel_filter_size())
00151 .Filter(synchronized_data.ranges);
00152
00153 std::vector<transform::Rigid3f> hits_poses;
00154 hits_poses.reserve(hits.size());
00155 bool warned = false;
00156
00157 for (const auto& hit : hits) {
00158 common::Time time_point =
00159 current_sensor_time + common::FromSeconds(hit.point_time.time);
00160 if (time_point < extrapolator_->GetLastExtrapolatedTime()) {
00161 if (!warned) {
00162 LOG(ERROR)
00163 << "Timestamp of individual range data point jumps backwards from "
00164 << extrapolator_->GetLastExtrapolatedTime() << " to " << time_point;
00165 warned = true;
00166 }
00167 time_point = extrapolator_->GetLastExtrapolatedTime();
00168 }
00169 hits_poses.push_back(
00170 extrapolator_->ExtrapolatePose(time_point).cast<float>());
00171 }
00172
00173 if (num_accumulated_ == 0) {
00174
00175 accumulated_range_data_ = sensor::RangeData{{}, {}, {}};
00176 }
00177
00178 for (size_t i = 0; i < hits.size(); ++i) {
00179 sensor::RangefinderPoint hit_in_local =
00180 hits_poses[i] * sensor::ToRangefinderPoint(hits[i].point_time);
00181 const Eigen::Vector3f origin_in_local =
00182 hits_poses[i] * synchronized_data.origins.at(hits[i].origin_index);
00183 const Eigen::Vector3f delta = hit_in_local.position - origin_in_local;
00184 const float range = delta.norm();
00185 if (range >= options_.min_range()) {
00186 if (range <= options_.max_range()) {
00187 accumulated_range_data_.returns.push_back(hit_in_local);
00188 } else {
00189
00190
00191
00192 hit_in_local.position =
00193 origin_in_local + options_.max_range() / range * delta;
00194 accumulated_range_data_.misses.push_back(hit_in_local);
00195 }
00196 }
00197 }
00198 ++num_accumulated_;
00199
00200 if (num_accumulated_ >= options_.num_accumulated_range_data()) {
00201 absl::optional<common::Duration> sensor_duration;
00202 if (last_sensor_time_.has_value()) {
00203 sensor_duration = current_sensor_time - last_sensor_time_.value();
00204 }
00205 last_sensor_time_ = current_sensor_time;
00206 num_accumulated_ = 0;
00207
00208 transform::Rigid3f current_pose =
00209 extrapolator_->ExtrapolatePose(current_sensor_time).cast<float>();
00210
00211 const auto voxel_filter_start = std::chrono::steady_clock::now();
00212 const sensor::RangeData filtered_range_data = {
00213 current_pose.translation(),
00214 sensor::VoxelFilter(options_.voxel_filter_size())
00215 .Filter(accumulated_range_data_.returns),
00216 sensor::VoxelFilter(options_.voxel_filter_size())
00217 .Filter(accumulated_range_data_.misses)};
00218 const auto voxel_filter_stop = std::chrono::steady_clock::now();
00219 const auto voxel_filter_duration = voxel_filter_stop - voxel_filter_start;
00220
00221 if (sensor_duration.has_value()) {
00222 const double voxel_filter_fraction =
00223 common::ToSeconds(voxel_filter_duration) /
00224 common::ToSeconds(sensor_duration.value());
00225 kLocalSlamVoxelFilterFraction->Set(voxel_filter_fraction);
00226 }
00227
00228 return AddAccumulatedRangeData(
00229 current_sensor_time,
00230 sensor::TransformRangeData(filtered_range_data, current_pose.inverse()),
00231 sensor_duration);
00232 }
00233 return nullptr;
00234 }
00235
00236 std::unique_ptr<LocalTrajectoryBuilder3D::MatchingResult>
00237 LocalTrajectoryBuilder3D::AddAccumulatedRangeData(
00238 const common::Time time,
00239 const sensor::RangeData& filtered_range_data_in_tracking,
00240 const absl::optional<common::Duration>& sensor_duration) {
00241 if (filtered_range_data_in_tracking.returns.empty()) {
00242 LOG(WARNING) << "Dropped empty range data.";
00243 return nullptr;
00244 }
00245 const transform::Rigid3d pose_prediction =
00246 extrapolator_->ExtrapolatePose(time);
00247
00248 const auto scan_matcher_start = std::chrono::steady_clock::now();
00249
00250 sensor::AdaptiveVoxelFilter adaptive_voxel_filter(
00251 options_.high_resolution_adaptive_voxel_filter_options());
00252 const sensor::PointCloud high_resolution_point_cloud_in_tracking =
00253 adaptive_voxel_filter.Filter(filtered_range_data_in_tracking.returns);
00254 if (high_resolution_point_cloud_in_tracking.empty()) {
00255 LOG(WARNING) << "Dropped empty high resolution point cloud data.";
00256 return nullptr;
00257 }
00258 sensor::AdaptiveVoxelFilter low_resolution_adaptive_voxel_filter(
00259 options_.low_resolution_adaptive_voxel_filter_options());
00260 const sensor::PointCloud low_resolution_point_cloud_in_tracking =
00261 low_resolution_adaptive_voxel_filter.Filter(
00262 filtered_range_data_in_tracking.returns);
00263 if (low_resolution_point_cloud_in_tracking.empty()) {
00264 LOG(WARNING) << "Dropped empty low resolution point cloud data.";
00265 return nullptr;
00266 }
00267
00268 std::unique_ptr<transform::Rigid3d> pose_estimate =
00269 ScanMatch(pose_prediction, low_resolution_point_cloud_in_tracking,
00270 high_resolution_point_cloud_in_tracking);
00271 if (pose_estimate == nullptr) {
00272 LOG(WARNING) << "Scan matching failed.";
00273 return nullptr;
00274 }
00275 extrapolator_->AddPose(time, *pose_estimate);
00276
00277 const auto scan_matcher_stop = std::chrono::steady_clock::now();
00278 const auto scan_matcher_duration = scan_matcher_stop - scan_matcher_start;
00279 if (sensor_duration.has_value()) {
00280 const double scan_matcher_fraction =
00281 common::ToSeconds(scan_matcher_duration) /
00282 common::ToSeconds(sensor_duration.value());
00283 kLocalSlamScanMatcherFraction->Set(scan_matcher_fraction);
00284 }
00285
00286 const Eigen::Quaterniond gravity_alignment =
00287 extrapolator_->EstimateGravityOrientation(time);
00288 sensor::RangeData filtered_range_data_in_local = sensor::TransformRangeData(
00289 filtered_range_data_in_tracking, pose_estimate->cast<float>());
00290
00291 const auto insert_into_submap_start = std::chrono::steady_clock::now();
00292 std::unique_ptr<InsertionResult> insertion_result = InsertIntoSubmap(
00293 time, filtered_range_data_in_local, filtered_range_data_in_tracking,
00294 high_resolution_point_cloud_in_tracking,
00295 low_resolution_point_cloud_in_tracking, *pose_estimate,
00296 gravity_alignment);
00297 const auto insert_into_submap_stop = std::chrono::steady_clock::now();
00298
00299 const auto insert_into_submap_duration =
00300 insert_into_submap_stop - insert_into_submap_start;
00301 if (sensor_duration.has_value()) {
00302 const double insert_into_submap_fraction =
00303 common::ToSeconds(insert_into_submap_duration) /
00304 common::ToSeconds(sensor_duration.value());
00305 kLocalSlamInsertIntoSubmapFraction->Set(insert_into_submap_fraction);
00306 }
00307 const auto wall_time = std::chrono::steady_clock::now();
00308 if (last_wall_time_.has_value()) {
00309 const auto wall_time_duration = wall_time - last_wall_time_.value();
00310 kLocalSlamLatencyMetric->Set(common::ToSeconds(wall_time_duration));
00311 if (sensor_duration.has_value()) {
00312 kLocalSlamRealTimeRatio->Set(common::ToSeconds(sensor_duration.value()) /
00313 common::ToSeconds(wall_time_duration));
00314 }
00315 }
00316 const double thread_cpu_time_seconds = common::GetThreadCpuTimeSeconds();
00317 if (last_thread_cpu_time_seconds_.has_value()) {
00318 const double thread_cpu_duration_seconds =
00319 thread_cpu_time_seconds - last_thread_cpu_time_seconds_.value();
00320 if (sensor_duration.has_value()) {
00321 kLocalSlamCpuRealTimeRatio->Set(
00322 common::ToSeconds(sensor_duration.value()) /
00323 thread_cpu_duration_seconds);
00324 }
00325 }
00326 last_wall_time_ = wall_time;
00327 last_thread_cpu_time_seconds_ = thread_cpu_time_seconds;
00328 return absl::make_unique<MatchingResult>(MatchingResult{
00329 time, *pose_estimate, std::move(filtered_range_data_in_local),
00330 std::move(insertion_result)});
00331 }
00332
00333 void LocalTrajectoryBuilder3D::AddOdometryData(
00334 const sensor::OdometryData& odometry_data) {
00335 if (extrapolator_ == nullptr) {
00336
00337 LOG(INFO) << "Extrapolator not yet initialized.";
00338 return;
00339 }
00340 extrapolator_->AddOdometryData(odometry_data);
00341 }
00342
00343 std::unique_ptr<LocalTrajectoryBuilder3D::InsertionResult>
00344 LocalTrajectoryBuilder3D::InsertIntoSubmap(
00345 const common::Time time,
00346 const sensor::RangeData& filtered_range_data_in_local,
00347 const sensor::RangeData& filtered_range_data_in_tracking,
00348 const sensor::PointCloud& high_resolution_point_cloud_in_tracking,
00349 const sensor::PointCloud& low_resolution_point_cloud_in_tracking,
00350 const transform::Rigid3d& pose_estimate,
00351 const Eigen::Quaterniond& gravity_alignment) {
00352 if (motion_filter_.IsSimilar(time, pose_estimate)) {
00353 return nullptr;
00354 }
00355 const Eigen::VectorXf rotational_scan_matcher_histogram_in_gravity =
00356 scan_matching::RotationalScanMatcher::ComputeHistogram(
00357 sensor::TransformPointCloud(
00358 filtered_range_data_in_tracking.returns,
00359 transform::Rigid3f::Rotation(gravity_alignment.cast<float>())),
00360 options_.rotational_histogram_size());
00361
00362 const Eigen::Quaterniond local_from_gravity_aligned =
00363 pose_estimate.rotation() * gravity_alignment.inverse();
00364 std::vector<std::shared_ptr<const mapping::Submap3D>> insertion_submaps =
00365 active_submaps_.InsertData(filtered_range_data_in_local,
00366 local_from_gravity_aligned,
00367 rotational_scan_matcher_histogram_in_gravity);
00368 return absl::make_unique<InsertionResult>(
00369 InsertionResult{std::make_shared<const mapping::TrajectoryNode::Data>(
00370 mapping::TrajectoryNode::Data{
00371 time,
00372 gravity_alignment,
00373 {},
00374 high_resolution_point_cloud_in_tracking,
00375 low_resolution_point_cloud_in_tracking,
00376 rotational_scan_matcher_histogram_in_gravity,
00377 pose_estimate}),
00378 std::move(insertion_submaps)});
00379 }
00380
00381 void LocalTrajectoryBuilder3D::RegisterMetrics(
00382 metrics::FamilyFactory* family_factory) {
00383 auto* latency = family_factory->NewGaugeFamily(
00384 "mapping_3d_local_trajectory_builder_latency",
00385 "Duration from first incoming point cloud in accumulation to local slam "
00386 "result");
00387 kLocalSlamLatencyMetric = latency->Add({});
00388
00389 auto* voxel_filter_fraction = family_factory->NewGaugeFamily(
00390 "mapping_3d_local_trajectory_builder_voxel_filter_fraction",
00391 "Fraction of total sensor time taken up by voxel filter.");
00392 kLocalSlamVoxelFilterFraction = voxel_filter_fraction->Add({});
00393
00394 auto* scan_matcher_fraction = family_factory->NewGaugeFamily(
00395 "mapping_3d_local_trajectory_builder_scan_matcher_fraction",
00396 "Fraction of total sensor time taken up by scan matcher.");
00397 kLocalSlamScanMatcherFraction = scan_matcher_fraction->Add({});
00398
00399 auto* insert_into_submap_fraction = family_factory->NewGaugeFamily(
00400 "mapping_3d_local_trajectory_builder_insert_into_submap_fraction",
00401 "Fraction of total sensor time taken up by inserting into submap.");
00402 kLocalSlamInsertIntoSubmapFraction = insert_into_submap_fraction->Add({});
00403
00404 auto* real_time_ratio = family_factory->NewGaugeFamily(
00405 "mapping_3d_local_trajectory_builder_real_time_ratio",
00406 "sensor duration / wall clock duration.");
00407 kLocalSlamRealTimeRatio = real_time_ratio->Add({});
00408
00409 auto* cpu_real_time_ratio = family_factory->NewGaugeFamily(
00410 "mapping_3d_local_trajectory_builder_cpu_real_time_ratio",
00411 "sensor duration / cpu duration.");
00412 kLocalSlamCpuRealTimeRatio = cpu_real_time_ratio->Add({});
00413
00414 auto score_boundaries = metrics::Histogram::FixedWidth(0.05, 20);
00415 auto* scores = family_factory->NewHistogramFamily(
00416 "mapping_3d_local_trajectory_builder_scores", "Local scan matcher scores",
00417 score_boundaries);
00418 kRealTimeCorrelativeScanMatcherScoreMetric =
00419 scores->Add({{"scan_matcher", "real_time_correlative"}});
00420 auto cost_boundaries = metrics::Histogram::ScaledPowersOf(2, 0.01, 100);
00421 auto* costs = family_factory->NewHistogramFamily(
00422 "mapping_3d_local_trajectory_builder_costs", "Local scan matcher costs",
00423 cost_boundaries);
00424 kCeresScanMatcherCostMetric = costs->Add({{"scan_matcher", "ceres"}});
00425 auto distance_boundaries = metrics::Histogram::ScaledPowersOf(2, 0.01, 10);
00426 auto* residuals = family_factory->NewHistogramFamily(
00427 "mapping_3d_local_trajectory_builder_residuals",
00428 "Local scan matcher residuals", distance_boundaries);
00429 kScanMatcherResidualDistanceMetric =
00430 residuals->Add({{"component", "distance"}});
00431 kScanMatcherResidualAngleMetric = residuals->Add({{"component", "angle"}});
00432 }
00433
00434 }
00435 }