c++mobile-robot-toolkit

MRPT Graph Slam Minimal Example


I am trying to come up with a "minimal" way of running a graph slam application using MRPT. The sensor data (LaserScan / Odometry) will be provided by a custom middleware similiar to ROS. After reading docs and source codes (both for the MRPT and the ROS bridge) extensively, I came up with the following snippet:

std::string config_file = "../../../laser_odometry.ini";   
std::string rawlog_fname = "";   
std::string fname_GT = "";   
auto node_reg = mrpt::graphslam::deciders::CICPCriteriaNRD<mrpt::graphs::CNetworkOfPoses2DInf>{}; 
auto edge_reg = mrpt::graphslam::deciders::CICPCriteriaERD<mrpt::graphs::CNetworkOfPoses2DInf>{}; 
auto optimizer = mrpt::graphslam::optimizers::CLevMarqGSO<mrpt::graphs::CNetworkOfPoses2DInf>{};

auto win3d = mrpt::gui::CDisplayWindow3D{"Slam", 800, 600};
auto win_observer = mrpt::graphslam::CWindowObserver{};
auto win_manager = mrpt::graphslam::CWindowManager{&win3d, &win_observer};

auto engine = mrpt::graphslam::CGraphSlamEngine<mrpt::graphs::CNetworkOfPoses2DInf>{
  config_file, rawlog_fname, fname_GT, &win_manager, &node_reg, &edge_reg, &optimizer};

for (size_t measurement_count = 0;;) {
   // grab laser scan from the network, then fill it (hardcoded values for now), e.g:
   auto scan_ptr = mrpt::obs::CObservation2DRangeScan::Create();
   scan_ptr->timestamp = std::chrono::system_clock::now().time_since_epoch().count();
   scan_ptr->rightToLeft = true;
   scan_ptr->sensorLabel = "";
   scan_ptr->aperture = 3.14;  // rad (max-min)
   scan_ptr->maxRange = 3.0;   // m
   scan_ptr->sensorPose = mrpt::poses::CPose3D{};

   scan_ptr->resizeScan(30);
   for (int i = 0; i < 30; ++i) {
     scan_ptr->setScanRange(i, 0.5);
     scan_ptr->setScanRangeValidity(i, true);
   }

   { // Send LaserScan measurement to the slam engine
     auto obs_ptr = std::dynamic_pointer_cast<mrpt::obs::CObservation>(scan_ptr);
     engine.execGraphSlamStep(obs_ptr, measurement_count);
     ++measurement_count;
   }


   // grab odometry from the network, then fill it (hardcoded values for now), e.g:
   auto odometry_ptr = mrpt::obs::CObservationOdometry::Create();
   odometry_ptr->timestamp = std::chrono::system_clock::now().time_since_epoch().count();
   odometry_ptr->hasVelocities = false;
   odometry_ptr->odometry.x(0);
   odometry_ptr->odometry.y(0);
   odometry_ptr->odometry.phi(0);

   { // Send Odometry measurement to the slam engine
     auto obs_ptr = std::dynamic_pointer_cast<mrpt::obs::CObservation>(odometry_ptr);
     engine.execGraphSlamStep(obs_ptr, measurement_count);
     ++measurement_count;
   }

   // Get pose estimation from the engine
   auto pose = engine.getCurrentRobotPosEstimation();

}

Am I in the right direction here? Did I miss something?


Solution

  • Hmm, at a first look the script seems fine, you are providing odometry and the laser scan in two different steps and in Observation form.

    If you want to run with Odometry + laser scans use CFixedIntervalsNRD instead. It's much better tested and actually makes use of those measurements.

    There is no minimal graphslam-engine example at present in MRPT but here's here's the main method for running graph-slam with datasets:

    https://github.com/MRPT/mrpt/blob/26ee0f2d3a9366c50faa5f78d0388476ae886808/libs/graphslam/include/mrpt/graphslam/apps_related/CGraphSlamHandler_impl.h#L395

    template <class GRAPH_T>
    void CGraphSlamHandler<GRAPH_T>::execute()
    {
        using namespace mrpt::obs;
        ASSERTDEB_(m_engine);
    
        // Variables initialization
        mrpt::io::CFileGZInputStream rawlog_stream(m_rawlog_fname);
        CActionCollection::Ptr action;
        CSensoryFrame::Ptr observations;
        CObservation::Ptr observation;
        size_t curr_rawlog_entry;
        auto arch = mrpt::serialization::archiveFrom(rawlog_stream);
    
        // Read the dataset and pass the measurements to CGraphSlamEngine
        bool cont_exec = true;
        while (CRawlog::getActionObservationPairOrObservation(
                   arch, action, observations, observation, curr_rawlog_entry) &&
               cont_exec)
        {
            // actual call to the graphSLAM execution method
            // Exit if user pressed C-c
            cont_exec = m_engine->_execGraphSlamStep(
                action, observations, observation, curr_rawlog_entry);
        }
        m_logger->logFmt(mrpt::system::LVL_WARN, "Finished graphslam execution.");
    }
    

    You basically grab the data and then continuously feed them to CGraphSlamEngine via either execGraphSlamStep or _execGraphSlamStep methods.

    Here's also the relevant snippet for processing measurements in the corresponding ROS wrapper that operates with measurements from ROS topics:

    https://github.com/mrpt-ros-pkg/mrpt_slam/blob/8b32136e2a381b1759eb12458b4adba65e2335da/mrpt_graphslam_2d/include/mrpt_graphslam_2d/CGraphSlamHandler_ROS_impl.h#L719

    template<class GRAPH_T>
    void CGraphSlamHandler_ROS<GRAPH_T>::processObservation(
            mrpt::obs::CObservation::Ptr& observ) {
      this->_process(observ);
    }
    
    template<class GRAPH_T>
    void CGraphSlamHandler_ROS<GRAPH_T>::_process(
            mrpt::obs::CObservation::Ptr& observ) {
      using namespace mrpt::utils;
      if (!this->m_engine->isPaused()) {
            this->m_engine->execGraphSlamStep(observ, m_measurement_cnt);
            m_measurement_cnt++;
      }
    }