gmapping原理及代码解析(一)

本文介绍了Gmapping的学习过程,详细解析了main.cpp中的startLiveSlam()函数及其调用的回调函数。主要内容包括激光数据回调函数SlamGMapping::laserCallback中的addscan()和processScan()函数,涉及里程计更新、扫描匹配、粒子权重计算和重采样等关键步骤。文章还提及了scanmatch()、updateTreeWeights()、resample()和updateMap()等核心功能的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

此次记录Gmapping学习的过程,笔者能力尚缺,欢迎大家一起交流啊~

一、gmapping代码解析

我们先来看看Gmapping里的main.cpp

#include <ros/ros.h>

#include "slam_gmapping.h"

int
main(int argc, char** argv)
{
  ros::init(argc, argv, "slam_gmapping");

  SlamGMapping gn;
  gn.startLiveSlam();
  ros::spin();

  return(0);
}

里面涉及到startLiveSlam()函数,转到此函数,此函数在slam_gmapping.h中定义,在slam_gmapping.cpp中实现:

void SlamGMapping::startLiveSlam()
{
  entropy_publisher_ = private_nh_.advertise<std_msgs::Float64>("entropy", 1, true);
  sst_ = node_.advertise<nav_msgs::OccupancyGrid>("map", 1, true);
  sstm_ = node_.advertise<nav_msgs::MapMetaData>("map_metadata", 1, true);
  ss_ = node_.advertiseService("dynamic_map", &SlamGMapping::mapCallback, this);
  scan_filter_sub_ = new message_filters::Subscriber<sensor_msgs::LaserScan>(node_, "scan", 5);
  scan_filter_ = new tf::MessageFilter<sensor_msgs::LaserScan>(*scan_filter_sub_, tf_, odom_frame_, 5);
  scan_filter_->registerCallback(boost::bind(&SlamGMapping::laserCallback, this, _1));

  transform_thread_ = new boost::thread(boost::bind(&SlamGMapping::publishLoop, this, transform_publish_period_));
}

可以看出,此函数注册了相关的回调函数和发布者,我们进入雷达数据的回调函数SlamGMapping::laserCallback,也是在slam_gmapping.cpp中:

void
SlamGMapping::laserCallback(const sensor_msgs::LaserScan::ConstPtr& scan)
{
  laser_count_++;
  if ((laser_count_ % throttle_scans_) != 0)
    return;

  static ros::Time last_map_update(0,0);

  //第一帧激光数据时不初始化mapper
  // We can't initialize the mapper until we've got the first scan
  if(!got_first_scan_)
  {
    if(!initMapper(*scan))
      return;
    got_first_scan_ = true;
  }

  GMapping::OrientedPoint odom_pose;

  if(addScan(*scan, odom_pose))
  {
    ROS_DEBUG("scan processed");

    GMapping::OrientedPoint mpose = gsp_->getParticles()[gsp_->getBestParticleIndex()].pose;
    ROS_DEBUG("new best pose: %.3f %.3f %.3f", mpose.x, mpose.y, mpose.theta);
    ROS_DEBUG("odom pose: %.3f %.3f %.3f", odom_pose.x, odom_pose.y, odom_pose.theta);
    ROS_DEBUG("correction: %.3f %.3f %.3f", mpose.x - odom_pose.x, mpose.y - odom_pose.y, mpose.theta - odom_pose.theta);

    tf::Transform laser_to_map = tf::Transform(tf::createQuaternionFromRPY(0, 0, mpose.theta), tf::Vector3(mpose.x, mpose.y, 0.0)).inverse();
    tf::Transform odom_to_laser = tf::Transform(tf::createQuaternionFromRPY(0, 0, odom_pose.theta), tf::Vector3(odom_pose.x, odom_pose.y, 0.0));

    map_to_odom_mutex_.lock();
    map_to_odom_ = (odom_to_laser * laser_to_map).inverse();
    map_to_odom_mutex_.unlock();

    if(!got_map_ || (scan->header.stamp - last_map_update) > map_update_interval_)
    {
      updateMap(*scan);
      last_map_update = scan->header.stamp;
      ROS_DEBUG("Updated the map");
    }
  } else
    ROS_DEBUG("cannot process scan");
}

在此函数中,突出重要的是addscan()函数,这个函数需要scan(激光数据)和odom_pose(里程计位姿)两个参数:

bool
SlamGMapping::addScan(const sensor_msgs::LaserScan& scan, GMapping::OrientedPoint& gmap_pose)
{
  if(!getOdomPose(gmap_pose, scan.header.stamp))
     return false;
  
  if(scan.ranges.size() != gsp_laser_beam_count_)
    return false;

  // GMapping wants an array of doubles...
  double* ranges_double = new double[scan.ranges.size()];   //dynamic array
  // If the angle increment is negative, we have to invert the order of the readings.
  if (do_reverse_range_)
  {
    ROS_DEBUG("Inverting scan");
    int num_ranges = scan.ranges.size();
    for(int i=0; i < num_ranges; i++)
    {
      // Must filter out short readings, because the mapper won't  ?过滤掉短读数,i he i-1这样关系是因为雷达reverse
      if(scan.ranges[num_ranges - i - 1] < scan.range_min)
        ranges_double[i] = (double)scan.range_max;
      else
        ranges_double[i] = (double)scan.ranges[num_ranges - i - 1];
    }
  } else 
  {
    for(unsigned int i=0; i < scan.ranges.size(); i++)
    {
      // Must filter out short readings, because the mapper won't  //可是为什么要等于最大值呢
      if(scan.ranges[i] < scan.range_min)
        ranges_double[i] = (double)scan.range_max;
      else
        ranges_double[i] = (double)scan.ranges[i];
    }
  }

  GMapping::RangeReading reading(scan.ranges.size(),
                                 ranges_double,
                                 gsp_laser_,
                                 scan.header.stamp.toSec());

//但是它在rangereading构造函数中深度复制它们,因此我们不需要保留数组。
  // ...but it deep copies them in RangeReading constructor, so we don't
  // need to keep our array around.
  delete[] ranges_double;

  reading.setPose(gmap_pose);   //inline void setPose(const OrientedPoint& pose) {m_pose=pose;}

  /*
  ROS_DEBUG("scanpose (%.3f): %.3f %.3f %.3f\n",
            scan.header.stamp.toSec(),
            gmap_pose.x,
            gmap_pose.y,
            gmap_pose.theta);
            */
  ROS_DEBUG("processing scan");

  return gsp_->processScan(reading);
}

addscan()函数只是将获取到的scan消息作一下处理,过滤掉无效值,将处理过的数据传入processScan()函数,这个函数如果在ros上安装了gmapping包,可以在ros的安装路径下找到,如笔者的就在/opt/ros/kinetic/include/gmapping/gridfastslam中找到此函数的相关的头文件,或者可以之间去openslam下载源代码。;

 bool GridSlamProcessor::processScan(const RangeReading & reading, int adaptParticles)
  {
    /**retireve the position from the reading, and compute the odometry*/
    /*得到当前的里程计的位置*/
	OrientedPoint relPose=reading.getPose();
    //relPose.y = m_odoPose.y;
    
	/*m_count表示这个函数被调用的次数 如果是第0次调用,则所有的位姿都是一样的*/
	if (!m_count)
	{
      m_lastPartPose=m_odoPose=relPose;
    }
    
    //write the state of the reading and update all the particles using the motion model
	/*对于每一个粒子,都从里程计运动模型中采样,得到车子的初步估计位置  这一步对应于   里程计的更新 */
    int tmp_size = m_particles.size();

//这个for循环显然可以用OpenMP进行并行化
//#pragma omp parallel for
    for(int i = 0; i < tmp_size;i++)
    {
        OrientedPoint& pose(m_particles[i].pose);
        pose = m_motionModel.drawFromMotion(m_particles[i],relPose,m_odoPose);
    }

    //invoke the callback
    /*回调函数  实际上什么都没做*/
    onOdometryUpdate();
    
    // accumulate the robot translation and rotation
    /*根据两次里程计的数据 计算出来机器人的线性位移和角度位移的累积值 m_odoPose表示上一次的里程计位姿  relPose表示新的里程计的位姿*/
    OrientedPoint move=relPose-m_odoPose;
    move.theta=atan2(sin(move.theta), cos(move.theta));

    //统计机器人在进行激光雷达更新之前 走了多远的距离 以及 平移了多少的角度
    m_linearDistance+=sqrt(move*move);
    m_angularDistance+=fabs(move.theta);

//    cerr <<"linear Distance:"<<m_linearDistance<<endl;
//    cerr <<"angular Distance:"<<m_angularDistance<<endl;
    
    // if the robot jumps throw a warning
    /*
     * 如果机器人在走了m_distanceThresholdCheck这么远的距离都没有进行激光雷达的更新
     * 则需要进行报警。这个误差很可能是里程计或者激光雷达的BUG造成的。
     * 例如里程计数据出错 或者 激光雷达很久没有数据等等
     * 每次进行激光雷达的更新之后 m_linearDistance这个参数就会清零
     */
    if (m_linearDistance>m_distanceThresholdCheck)
    {
      cerr << "***********************************************************************" << endl;
      cerr << "********** Error: m_distanceThresholdCheck overridden!!!! *************" << endl;
      cer
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值