local_trajectory_builder_3d.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 
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 // TODO(spielawa): Adjust metrics for multi-trajectory. So far we assume a
00034 // single trajectory.
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     // We take a copy since we use 'initial_ceres_pose' as an output argument.
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   // We derive velocities from poses which are at least 1 ms apart for numerical
00112   // stability. Usually poses known to the extrapolator will be further apart
00113   // in time and thus the last two are used.
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     // Until we've initialized the extrapolator with our first IMU message, we
00134     // cannot compute the orientation of the rangefinder.
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     // 'accumulated_range_data_.origin' is not used.
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         // We insert a ray cropped to 'max_range' as a miss for hits beyond the
00190         // maximum range. This way the free space up to the maximum range will
00191         // be updated.
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     // Until we've initialized the extrapolator we cannot add odometry data.
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                               {},  // 'filtered_point_cloud' is only used in 2D.
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 }  // namespace mapping
00435 }  // namespace cartographer


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