您的当前位置:首页正文

同时定位与制图-SlamGMapping

2024-12-01 来源:个人技术集锦

Content:

机器人如小车如果需要在一个陌生的环境中导航,一个首要的前提就是得由一个地图。这个地图就需要小车利用自己的传感器来制作,这儿一般使用里程计和激光雷达,依赖一定的算法就可以完成。这儿以同时定位与制图-SlamGMapping为例解读。

节点的启动

class SlamGMappingNodelet : public nodelet::Nodelet
{
  public:
    SlamGMappingNodelet()  {}
 
    ~SlamGMappingNodelet() {}
 
    virtual void onInit()
    {
      NODELET_INFO_STREAM("Initialising Slam GMapping nodelet...");
      sg_.reset(new SlamGMapping(getNodeHandle(), getPrivateNodeHandle()));
      NODELET_INFO_STREAM("Starting live SLAM...");
//节点启动
      sg_->startLiveSlam();
    }
 
  private:  
    boost::shared_ptr sg_;
};
 
void SlamGMapping::startLiveSlam()
{
  entropy_publisher_ = private_nh_.advertise("entropy", 1, true);
  sst_ = node_.advertise("map", 1, true);
  sstm_ = node_.advertise("map_metadata", 1, true);
  ss_ = node_.advertiseService("dynamic_map", &SlamGMapping::mapCallback, this);
  scan_filter_sub_ = new message_filters::Subscriber(node_, "scan", 5);
  scan_filter_ = new tf::MessageFilter(*scan_filter_sub_, tf_, odom_frame_, 5);
//注册一个回调函数,每收到“scan”消息就调用“laserCallback”函数处理
  scan_filter_->registerCallback(boost::bind(&SlamGMapping::laserCallback, this, _1));
 
  transform_thread_ = new boost::thread(boost::bind(&SlamGMapping::publishLoop, this, transform_publish_period_));
}
 
void
SlamGMapping::laserCallback(const sensor_msgs::LaserScan::ConstPtr& scan)
{
  laser_count_++;
//限流,每throttle_scans_次扫描才处理一次
  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");
}

初始化Mapper

bool
SlamGMapping::initMapper(const sensor_msgs::LaserScan& scan)
{
  laser_frame_ = scan.header.frame_id;
  // Get the laser's pose, relative to base.
  tf::Stamped ident;
  tf::Stamped laser_pose;
  ident.setIdentity();
  ident.frame_id_ = laser_frame_;
  ident.stamp_ = scan.header.stamp;
  try
  {
//获取激光雷达相对小车的真实位置,这才是所有雷达信号的0点
    tf_.transformPose(base_frame_, ident, laser_pose);
  }
  catch(tf::TransformException e)
  {
    ROS_WARN("Failed to compute laser pose, aborting initialization (%s)",
             e.what());
    return false;
  }
 
//获取雷达上方1米的坐标,用于判断雷达是否安装在小车上
  // create a point 1m above the laser position and transform it into the laser-frame
  tf::Vector3 v;
  v.setValue(0, 0, 1 + laser_pose.getOrigin().z());
  tf::Stamped up(v, scan.header.stamp,
                                      base_frame_);
  try
  {
    tf_.transformPoint(laser_frame_, up, up);
    ROS_DEBUG("Z-Axis in sensor frame: %.3f", up.z());
  }
  catch(tf::TransformException& e)
  {
    ROS_WARN("Unable to determine orientation of laser: %s",
             e.what());
    return false;
  }
 
  // gmapping doesnt take roll or pitch into account. So check for correct sensor alignment.
  if (fabs(fabs(up.z()) - 1) > 0.001)
  {
    ROS_WARN("Laser has to be mounted planar! Z-coordinate has to be 1 or -1, but gave: %.5f",
                 up.z());
    return false;
  }
//扫描激光束数量
  gsp_laser_beam_count_ = scan.ranges.size();
//扫描激光束的中间角度
  double angle_center = (scan.angle_min + scan.angle_max)/2;
 
  if (up.z() > 0)
  {
//雷达正着装的时候,如果小角大于大角,说明扫描顺序是反的。
    do_reverse_range_ = scan.angle_min > scan.angle_max;
    centered_laser_pose_ = tf::Stamped(tf::Transform(tf::createQuaternionFromRPY(0,0,a
显示全文