• cartographer源码解析(2)


    上一节介绍了cartographer_ros对cartographer调用的流程与接口,本节开始重点关注cartographer源码前端部分,主要包括local_trajectoroy_builder中进行点云与地图的匹配,以及子图更新。

    我们从上节提到的MapBuilder类的实例开始,我们知道ros节点初始化时会调用AddTrajectory函数,源码位于cartographer/mapping/map_builder.cc中。

    // ros封装调用该函数初始化路径构建器
    int MapBuilder::AddTrajectoryBuilder(
        const std::set<SensorId>& expected_sensor_ids,
        const proto::TrajectoryBuilderOptions& trajectory_options,
        LocalSlamResultCallback local_slam_result_callback) {
      const int trajectory_id = trajectory_builders_.size();
      // 选择实际初始化的局部路径构造器对象,3d/2d
      if (options_.use_trajectory_builder_3d()) {
        std::unique_ptr<LocalTrajectoryBuilder3D> local_trajectory_builder;
        if (trajectory_options.has_trajectory_builder_3d_options()) {
          local_trajectory_builder = absl::make_unique<LocalTrajectoryBuilder3D>(
              trajectory_options.trajectory_builder_3d_options(),
              SelectRangeSensorIds(expected_sensor_ids));
        }
        DCHECK(dynamic_cast<PoseGraph3D*>(pose_graph_.get()));
        trajectory_builders_.push_back(absl::make_unique<CollatedTrajectoryBuilder>(
            trajectory_options, sensor_collator_.get(), trajectory_id,
            expected_sensor_ids,
            CreateGlobalTrajectoryBuilder3D(
                std::move(local_trajectory_builder), trajectory_id,
                static_cast<PoseGraph3D*>(pose_graph_.get()),
                local_slam_result_callback)));
      } else {
        std::unique_ptr<LocalTrajectoryBuilder2D> local_trajectory_builder;
        if (trajectory_options.has_trajectory_builder_2d_options()) {
          local_trajectory_builder = absl::make_unique<LocalTrajectoryBuilder2D>(
              trajectory_options.trajectory_builder_2d_options(),
              SelectRangeSensorIds(expected_sensor_ids));
        }
        DCHECK(dynamic_cast<PoseGraph2D*>(pose_graph_.get()));
        // 创建全局路径构造器对象
        trajectory_builders_.push_back(absl::make_unique<CollatedTrajectoryBuilder>(
            trajectory_options, sensor_collator_.get(), trajectory_id,
            expected_sensor_ids,
            CreateGlobalTrajectoryBuilder2D(
                std::move(local_trajectory_builder), trajectory_id,
                static_cast<PoseGraph2D*>(pose_graph_.get()),
                local_slam_result_callback)));
      }
      MaybeAddPureLocalizationTrimmer(trajectory_id, trajectory_options,
                                      pose_graph_.get());
    
      if (trajectory_options.has_initial_trajectory_pose()) {
        const auto& initial_trajectory_pose =
            trajectory_options.initial_trajectory_pose();
        pose_graph_->SetInitialTrajectoryPose(
            trajectory_id, initial_trajectory_pose.to_trajectory_id(),
            transform::ToRigid3(initial_trajectory_pose.relative_pose()),
            common::FromUniversal(initial_trajectory_pose.timestamp()));
      }
      proto::TrajectoryBuilderOptionsWithSensorIds options_with_sensor_ids_proto;
      for (const auto& sensor_id : expected_sensor_ids) {
        *options_with_sensor_ids_proto.add_sensor_id() = ToProto(sensor_id);
      }
      *options_with_sensor_ids_proto.mutable_trajectory_builder_options() =
          trajectory_options;
      all_trajectory_builder_options_.push_back(options_with_sensor_ids_proto);
      CHECK_EQ(trajectory_builders_.size(), all_trajectory_builder_options_.size());
      return trajectory_id;
    }

    该函数会根据配置参数创建2d或3d的local_trajectory_builder并以此为参数创建带校核(collated)的global_trajectory_builder,最终返回当前创建的路径编号。至此程序初始化基本完成,我们将关注点转移到上节末尾提到的SensorBridge::HandleRangefinder函数,每当监听到激光雷达数据,该函数会激活并最终调用trajectory_builder_->AddSensorData函数。

    void SensorBridge::HandleRangefinder(
        const std::string& sensor_id, const carto::common::Time time,
        const std::string& frame_id, const carto::sensor::TimedPointCloud& ranges) {
      if (!ranges.empty()) {
        CHECK_LE(ranges.back().time, 0.f);
      }
      // 获取两帧tf之间的平移变换
      const auto sensor_to_tracking =
          tf_bridge_.LookupToTracking(time, CheckNoLeadingSlash(frame_id));
      // 调用cartographer中global_trajectory_builder的对应函数
      if (sensor_to_tracking != nullptr) {
        trajectory_builder_->AddSensorData(
            sensor_id, carto::sensor::TimedPointCloudData{
                           time, sensor_to_tracking->translation().cast<float>(),
                           carto::sensor::TransformTimedPointCloud(
                               ranges, sensor_to_tracking->cast<float>())});
      }
    }

    由上文我们知道此处trajectory_builder实际上是CollatedTrajectoryBuilder类的实例,通过观察该类定义,我们大概知道这是一个对TrajectoryBuilder进行封装的校核器类,暂时忽略封装,直接将其当做对应的TrajectoryBuilder类的实例处理,那么此处trajectory_builder可看做GlobalTrajectoryBuilder的实例,通过该实例的AddSensorData函数,实现传感器数据从cartographer_ros节点到cartographer内部的传递。AddSensorData经过重构,可以接收并处理各传感器数据(点云、IMU、里程计等)。此处主要研究处理激光雷达点云的函数,具体实现如下(位于cartographer/mapping/internal/global_trajectory_builder.cc)。

      void AddSensorData(
          const std::string& sensor_id,
          const sensor::TimedPointCloudData& timed_point_cloud_data) override {
        CHECK(local_trajectory_builder_)
            << "Cannot add TimedPointCloudData without a LocalTrajectoryBuilder.";
        // 前端local_SLAM,将点云与地图匹配并返回更新的submaps
        std::unique_ptr<typename LocalTrajectoryBuilder::MatchingResult>
            matching_result = local_trajectory_builder_->AddRangeData(
                sensor_id, timed_point_cloud_data);
        if (matching_result == nullptr) {
          // The range data has not been fully accumulated yet.
          return;
        }
        // 这玩意儿没看懂在干嘛
        kLocalSlamMatchingResults->Increment();
        // 将前端产生的Insertion_result(时间戳、位姿、点云、子图等)放入pose_graph_中,供后端自行优化
        std::unique_ptr<InsertionResult> insertion_result;
        if (matching_result->insertion_result != nullptr) {
          kLocalSlamInsertionResults->Increment();
          auto node_id = pose_graph_->AddNode(
              matching_result->insertion_result->constant_data, trajectory_id_,
              matching_result->insertion_result->insertion_submaps);
          CHECK_EQ(node_id.trajectory_id, trajectory_id_);
          insertion_result = absl::make_unique<InsertionResult>(InsertionResult{
              node_id, matching_result->insertion_result->constant_data,
              std::vector<std::shared_ptr<const Submap>>(
                  matching_result->insertion_result->insertion_submaps.begin(),
                  matching_result->insertion_result->insertion_submaps.end())});
        }
        // 通过回调将结果传出
        if (local_slam_result_callback_) {
          local_slam_result_callback_(
              trajectory_id_, matching_result->time, matching_result->local_pose,
              std::move(matching_result->range_data_in_local),
              std::move(insertion_result));
        }
      }

    此处将数据传入前端进行位姿估计、点云与地图匹配并更新子图。跳转到local_trajectory_builder.cc中的AddRangeData函数的实现。

    // 由全局路径构造器调用,传入激光雷达数据
    std::unique_ptr<LocalTrajectoryBuilder2D::MatchingResult>
    LocalTrajectoryBuilder2D::AddRangeData(
        const std::string& sensor_id,
        const sensor::TimedPointCloudData& unsynchronized_data) {
      // LOG(INFO)<<"local trajectory add range data called.";
      auto synchronized_data =
          range_data_collator_.AddRangeData(sensor_id, unsynchronized_data);
      if (synchronized_data.ranges.empty()) {
        LOG(INFO) << "Range data collator filling buffer.";
        return nullptr;
      }
    
      const common::Time& time = synchronized_data.time;
      // Initialize extrapolator now if we do not ever use an IMU.
      if (!options_.use_imu_data()) {
        InitializeExtrapolator(time);
      }
    
      if (extrapolator_ == nullptr) {
        // Until we've initialized the extrapolator with our first IMU message, we
        // cannot compute the orientation of the rangefinder.
        LOG(INFO) << "Extrapolator not yet initialized.";
        return nullptr;
      }
    
      CHECK(!synchronized_data.ranges.empty());
      // TODO(gaschler): Check if this can strictly be 0.
      CHECK_LE(synchronized_data.ranges.back().point_time.time, 0.f);
      const common::Time time_first_point =
          time +
          common::FromSeconds(synchronized_data.ranges.front().point_time.time);
      if (time_first_point < extrapolator_->GetLastPoseTime()) {
        LOG(INFO) << "Extrapolator is still initializing.";
        return nullptr;
      }
    
      // 时间同步
      std::vector<transform::Rigid3f> range_data_poses;
      range_data_poses.reserve(synchronized_data.ranges.size());
      bool warned = false;
      for (const auto& range : synchronized_data.ranges) {
        common::Time time_point = time + common::FromSeconds(range.point_time.time);
        if (time_point < extrapolator_->GetLastExtrapolatedTime()) {
          if (!warned) {
            LOG(ERROR)
                << "Timestamp of individual range data point jumps backwards from "
                << extrapolator_->GetLastExtrapolatedTime() << " to " << time_point;
            warned = true;
          }
          time_point = extrapolator_->GetLastExtrapolatedTime();
        }
        range_data_poses.push_back(
            extrapolator_->ExtrapolatePose(time_point).cast<float>());
      }
    
      if (num_accumulated_ == 0) {
        // 'accumulated_range_data_.origin' is uninitialized until the last
        // accumulation.
        accumulated_range_data_ = sensor::RangeData{{}, {}, {}};
      }
    
      // 点云根据距离限制分类为"returns"和"messes"
      // Rays begin at 'origin'. 'returns' are the points where obstructions were
      // detected. 'misses' are points in the direction of rays for which no return
      // was detected, and were inserted at a configured distance. It is assumed that
      // between the 'origin' and 'misses' is free space.
    
      // Drop any returns below the minimum range and convert returns beyond the
      // maximum range into misses.
      for (size_t i = 0; i < synchronized_data.ranges.size(); ++i) {
        const sensor::TimedRangefinderPoint& hit =
            synchronized_data.ranges[i].point_time;
        const Eigen::Vector3f origin_in_local =
            range_data_poses[i] *
            synchronized_data.origins.at(synchronized_data.ranges[i].origin_index);
        sensor::RangefinderPoint hit_in_local =
            range_data_poses[i] * sensor::ToRangefinderPoint(hit);
        const Eigen::Vector3f delta = hit_in_local.position - origin_in_local;
        const float range = delta.norm();
        if (range >= options_.min_range()) {
          if (range <= options_.max_range()) {
            accumulated_range_data_.returns.push_back(hit_in_local);
          } else {
            hit_in_local.position =
                origin_in_local +
                options_.missing_data_ray_length() / range * delta;
            accumulated_range_data_.misses.push_back(hit_in_local);
          }
        }
      }
      ++num_accumulated_;
    
      if (num_accumulated_ >= options_.num_accumulated_range_data()) {
        const common::Time current_sensor_time = synchronized_data.time;
        absl::optional<common::Duration> sensor_duration;
        if (last_sensor_time_.has_value()) {
          sensor_duration = current_sensor_time - last_sensor_time_.value();
        }
        last_sensor_time_ = current_sensor_time;
        num_accumulated_ = 0;
        const transform::Rigid3d gravity_alignment = transform::Rigid3d::Rotation(
            extrapolator_->EstimateGravityOrientation(time));
        // TODO(gaschler): This assumes that 'range_data_poses.back()' is at time
        // 'time'.
        accumulated_range_data_.origin = range_data_poses.back().translation();
        return AddAccumulatedRangeData(
            time,
            TransformToGravityAlignedFrameAndFilter(
                gravity_alignment.cast<float>() * range_data_poses.back().inverse(),
                accumulated_range_data_),
            gravity_alignment, sensor_duration);
      }
      return nullptr;
    }

    该函数做了一些时间同步,计数与点云分类的操作,观察返回时调用的AddAccumulatedRangeData函数。

    std::unique_ptr<LocalTrajectoryBuilder2D::MatchingResult>
    LocalTrajectoryBuilder2D::AddAccumulatedRangeData(
        const common::Time time,
        const sensor::RangeData& gravity_aligned_range_data,
        const transform::Rigid3d& gravity_alignment,
        const absl::optional<common::Duration>& sensor_duration) {
      if (gravity_aligned_range_data.returns.empty()) {
        LOG(WARNING) << "Dropped empty horizontal range data.";
        return nullptr;
      }
    
      // Computes a gravity aligned pose prediction.
      const transform::Rigid3d non_gravity_aligned_pose_prediction =
          extrapolator_->ExtrapolatePose(time);
      const transform::Rigid2d pose_prediction = transform::Project2D(
          non_gravity_aligned_pose_prediction * gravity_alignment.inverse());
    
      // 体素滤波降低优化耗时
      const sensor::PointCloud& filtered_gravity_aligned_point_cloud =
          sensor::AdaptiveVoxelFilter(options_.adaptive_voxel_filter_options())
              .Filter(gravity_aligned_range_data.returns);
      if (filtered_gravity_aligned_point_cloud.empty()) {
        return nullptr;
      }
    
      // local map frame <- gravity-aligned frame
      // 调用ScanMatch,用ceres找到精定位信息
      std::unique_ptr<transform::Rigid2d> pose_estimate_2d =
          ScanMatch(time, pose_prediction, filtered_gravity_aligned_point_cloud);
      if (pose_estimate_2d == nullptr) {
        LOG(WARNING) << "Scan matching failed.";
        return nullptr;
      }
      const transform::Rigid3d pose_estimate =
          transform::Embed3D(*pose_estimate_2d) * gravity_alignment;
      extrapolator_->AddPose(time, pose_estimate);
    
      // submap更新制图
      sensor::RangeData range_data_in_local =
          TransformRangeData(gravity_aligned_range_data,
                             transform::Embed3D(pose_estimate_2d->cast<float>()));
      std::unique_ptr<InsertionResult> insertion_result = InsertIntoSubmap(
          time, range_data_in_local, filtered_gravity_aligned_point_cloud,
          pose_estimate, gravity_alignment.rotation());
    
      // 计算总时间
      const auto wall_time = std::chrono::steady_clock::now();
      if (last_wall_time_.has_value()) {
        const auto wall_time_duration = wall_time - last_wall_time_.value();
        kLocalSlamLatencyMetric->Set(common::ToSeconds(wall_time_duration));
        if (sensor_duration.has_value()) {
          kLocalSlamRealTimeRatio->Set(common::ToSeconds(sensor_duration.value()) /
                                       common::ToSeconds(wall_time_duration));
        }
      }
      // 计算该线程时间花销
      const double thread_cpu_time_seconds = common::GetThreadCpuTimeSeconds();
      if (last_thread_cpu_time_seconds_.has_value()) {
        const double thread_cpu_duration_seconds =
            thread_cpu_time_seconds - last_thread_cpu_time_seconds_.value();
        if (sensor_duration.has_value()) {
          kLocalSlamCpuRealTimeRatio->Set(
              common::ToSeconds(sensor_duration.value()) /
              thread_cpu_duration_seconds);
        }
      }
      last_wall_time_ = wall_time;
      last_thread_cpu_time_seconds_ = thread_cpu_time_seconds;
      return absl::make_unique<MatchingResult>(
          MatchingResult{time, pose_estimate, std::move(range_data_in_local),
                         std::move(insertion_result)});
    }

    至此,一帧激光雷达点云数据传入后的调用流程基本介绍完毕。下一节将从点云与地图的匹配(ScanMatch函数)开始介绍。

  • 相关阅读:
    微信小程序~事件绑定和冒泡
    为promise增加abort功能
    Object构造函数的方法 之 Object.freeze
    ES6新特性:JavaScript中内置的延迟对象Promise
    js 预编译
    什么是PWA?PWA的发展趋势
    CSS隐藏元素的方法及区别
    网页编码:UTF-8、GB2312
    Mixin 和 CSS Guards
    css自定义checkbox样式
  • 原文地址:https://www.cnblogs.com/carbo-T/p/12980704.html
Copyright © 2020-2023  润新知