地图处理函数handleMapMessage

之前看AMCL只是关注粒子滤波的流程,对于地图数据怎么转换没仔细看。现在有空就简单理理。

1、处理地图数据的入口是函数:handleMapMessage。ROS地图数据转换、粒子滤波器的初始化、创建传感器对象及其模型参数的初始化,均在处理地图数据的时候进行。

void
AmclNode::handleMapMessage(const nav_msgs::OccupancyGrid& msg)
{
  boost::recursive_mutex::scoped_lock cfl(configuration_mutex_);

  ROS_INFO("Received a %d X %d map @ %.3f m/pix
",
           msg.info.width,
           msg.info.height,
           msg.info.resolution);
  
  if(msg.header.frame_id != global_frame_id_)
    ROS_WARN("Frame_id of map received:'%s' doesn't match global_frame_id:'%s'. This could cause issues with reading published topics",
             msg.header.frame_id.c_str(),
             global_frame_id_.c_str());

  freeMapDependentMemory();
  // Clear queued laser objects because they hold pointers to the existing
  // map, #5202.
  lasers_.clear();
  lasers_update_.clear();
  frame_to_laser_.clear();

  map_ = convertMap(msg);

#if NEW_UNIFORM_SAMPLING  //默认值为1,把地图上的空的点存起来
  // Index of free space
  free_space_indices.resize(0);
  for(int i = 0; i < map_->size_x; i++)
    for(int j = 0; j < map_->size_y; j++)
      if(map_->cells[MAP_INDEX(map_,i,j)].occ_state == -1)
        free_space_indices.push_back(std::make_pair(i,j));
#endif
  // Create the particle filter
  pf_ = pf_alloc(min_particles_, max_particles_,
                 alpha_slow_, alpha_fast_,
                 (pf_init_model_fn_t)AmclNode::uniformPoseGenerator,//函数指针
                 (void *)map_);  //这里得到的是一个均匀分布采样的向量//void *是无类型指针,可以指向任何数据
  pf_->pop_err = pf_err_;
  pf_->pop_z = pf_z_;

  // Initialize the filter
  updatePoseFromServer();
  pf_vector_t pf_init_pose_mean = pf_vector_zero();
  pf_init_pose_mean.v[0] = init_pose_[0];
  pf_init_pose_mean.v[1] = init_pose_[1];
  pf_init_pose_mean.v[2] = init_pose_[2];
  pf_matrix_t pf_init_pose_cov = pf_matrix_zero();
  pf_init_pose_cov.m[0][0] = init_cov_[0];
  pf_init_pose_cov.m[1][1] = init_cov_[1];
  pf_init_pose_cov.m[2][2] = init_cov_[2];
  pf_init(pf_, pf_init_pose_mean, pf_init_pose_cov);
  pf_init_ = false;

  // Instantiate the sensor objects
  // Odometry
  delete odom_;
  odom_ = new AMCLOdom();
  ROS_ASSERT(odom_);
  odom_->SetModel( odom_model_type_, alpha1_, alpha2_, alpha3_, alpha4_, alpha5_ );
  // Laser
  delete laser_;
  laser_ = new AMCLLaser(max_beams_, map_);
  ROS_ASSERT(laser_);
  if(laser_model_type_ == LASER_MODEL_BEAM)
    laser_->SetModelBeam(z_hit_, z_short_, z_max_, z_rand_,
                         sigma_hit_, lambda_short_, 0.0);
  else if(laser_model_type_ == LASER_MODEL_LIKELIHOOD_FIELD_PROB){
    ROS_INFO("Initializing likelihood field model; this can take some time on large maps...");
    laser_->SetModelLikelihoodFieldProb(z_hit_, z_rand_, sigma_hit_,
                    laser_likelihood_max_dist_, 
                    do_beamskip_, beam_skip_distance_, 
                    beam_skip_threshold_, beam_skip_error_threshold_);
    ROS_INFO("Done initializing likelihood field model.");
  }
  else
  {
    ROS_INFO("Initializing likelihood field model; this can take some time on large maps...");
    laser_->SetModelLikelihoodField(z_hit_, z_rand_, sigma_hit_,
                                    laser_likelihood_max_dist_);
    ROS_INFO("Done initializing likelihood field model.");
  }

  // In case the initial pose message arrived before the first map,
  // try to apply the initial pose now that the map has arrived.
  applyInitialPose();

}

2、ROS下的地图数据:nav_msgs/OccupancyGrid Message

看官方解释:http://docs.ros.org/en/kinetic/api/nav_msgs/html/msg/OccupancyGrid.html

std_msgs/Header header
nav_msgs/MapMetaData info
int8[] data

注意:data是一维数组,存储方式是一行一行存储。按序号取对应像素点的值。左下角是地图序号的起点(0,0),别问怎么知道,自己去写一个map的数据调用测试一下便知。

AMCL中地图的数据格式

typedef struct
{
  // Map origin; the map is a viewport onto a conceptual larger map. 地图起点在世界坐标下的值,地图起点位于地图的左下角
  double origin_x, origin_y;
  
  // Map scale (m/cell) 尺度
  double scale;

  // Map dimensions (number of cells)  栅格数目
  int size_x, size_y;
  
  // The map data, stored as a grid 像素点数据
  map_cell_t *cells;

  // Max distance at which we care about obstacles, for constructing
  // likelihood field  障碍物的最大距离
  double max_occ_dist;
  
} map_t;

3、convertMap(msg)地图数据的转换,要知道占用的表示:其中100表示障碍物occupy,0表示未占用free,-1表示未知状态unknown

map_t*
AmclNode::convertMap( const nav_msgs::OccupancyGrid& map_msg )
{
  map_t* map = map_alloc();
  ROS_ASSERT(map);

  map->size_x = map_msg.info.width;
  map->size_y = map_msg.info.height;
  map->scale = map_msg.info.resolution;
  map->origin_x = map_msg.info.origin.position.x + (map->size_x / 2) * map->scale;  
  map->origin_y = map_msg.info.origin.position.y + (map->size_y / 2) * map->scale;
  // Convert to player format
  map->cells = (map_cell_t*)malloc(sizeof(map_cell_t)*map->size_x*map->size_y);
  ROS_ASSERT(map->cells);
  for(int i=0;i<map->size_x * map->size_y;i++)
  {
      // 将0,free 转成 -1
    if(map_msg.data[i] == 0)
      map->cells[i].occ_state = -1;
        // 将100,占用 转成 +1
    else if(map_msg.data[i] == 100)
      map->cells[i].occ_state = +1;
        // 将其他情况 转成 0
    else
      map->cells[i].occ_state = 0;
  }

  return map;
}

地图点的格式,用结构体表示。转换后的map数据存在一块内存中,也是map->cells数组来存储。

// Description for a single map cell.
typedef struct
{
  // Occupancy state (-1 = free, 0 = unknown, +1 = occ)
  int occ_state;

  // Distance to the nearest occupied cell  记录的是该栅格与最近的被占据栅格的距离值
  double occ_dist;

  // Wifi levels
  //int wifi_levels[MAP_WIFI_MAX_LEVELS];

} map_cell_t;

但是在地图数据转换的时候,并没有看到occ_dist的数据的初始化。这就要看另一个函数map_update_cspace了。

4、地图坐标系的像素点和世界坐标系下的空间点的转换关系

// Convert from map index to world coords
#define MAP_WXGX(map, i) (map->origin_x + ((i) - map->size_x / 2) * map->scale)
#define MAP_WYGY(map, j) (map->origin_y + ((j) - map->size_y / 2) * map->scale)

// Convert from world coords to map coords  世界坐标系转换成地图坐标系
#define MAP_GXWX(map, x) (floor((x - map->origin_x) / map->scale + 0.5) + map->size_x / 2)  //floor取最大的整数,+0.5后是四舍五入
#define MAP_GYWY(map, y) (floor((y - map->origin_y) / map->scale + 0.5) + map->size_y / 2)

// Test to see if the given map coords lie within the absolute map bounds.是否在地图范围内的点
#define MAP_VALID(map, i, j) ((i >= 0) && (i < map->size_x) && (j >= 0) && (j < map->size_y))

// Compute the cell index for the given map coords. 计算(i,j)像素点以int[]数组存储,下面是数组的索引计算方式,索引是从左下角为起始点
#define MAP_INDEX(map, i, j) ((i) + (j) * map->size_x)

从MAP_INDEX就可以看出索引的方式是一行一行遍历的。

5、地图转换好了,用途是什么呢?再看pf_alloc初始化滤波器的时候,调用的uniformPoseGenerator,用于在地图的空白处随机的生成一个的粒子位姿。

pf_vector_t
AmclNode::uniformPoseGenerator(void* arg)  //做的事情是按均匀分布采样,根据地图获取一个随机的位姿
{
  map_t* map = (map_t*)arg;
#if NEW_UNIFORM_SAMPLING//如果是新的均匀采样
  unsigned int rand_index = drand48() * free_space_indices.size();  //均匀采样drand48(),随机找到地图上空白的某个像素点
  std::pair<int,int> free_point = free_space_indices[rand_index];
  pf_vector_t p;
  p.v[0] = MAP_WXGX(map, free_point.first);   //地图像素点转换成世界坐标系下的位姿
  p.v[1] = MAP_WYGY(map, free_point.second);  // Convert from map index to world coords
  p.v[2] = drand48() * 2 * M_PI - M_PI;   //0-180角度均匀采样
#else
 /***省略else部分***/
  return p;
}  //这里是地图上free区域上随机的采点,满足均匀分布

5、最后执行applyInitialPose。有种情况是,当机器人给定了一个初始值时,此时的初始Pose值就会覆盖上面随机生成的Pose值,调用pf_init,用设定的Pose值来初始化滤波器。

void
AmclNode::applyInitialPose()
{
  boost::recursive_mutex::scoped_lock cfl(configuration_mutex_);
  if( initial_pose_hyp_ != NULL && map_ != NULL ) {
    pf_init(pf_, initial_pose_hyp_->pf_pose_mean, initial_pose_hyp_->pf_pose_cov);
    pf_init_ = false;

    delete initial_pose_hyp_;
    initial_pose_hyp_ = NULL;
  }
}

 需要注意的是,另开一个线程的意义:是否能支持,只要新来一个Pose初值,是不是就可以重新生成一个滤波器呢?待思考和验证。

6、讲讲pf_init初始化,和随机初始化的区别是什么?

先看一下假设的初始值的数据结构

// Pose hypothesis
typedef struct
{
  // Total weight (weights sum to 1)
  double weight;

  // Mean of pose esimate
  pf_vector_t pf_pose_mean;

  // Covariance of pose estimate
  pf_matrix_t pf_pose_cov;

} amcl_hyp_t;

这里的weight是什么?为什么是0-1?

然后看pf_init,这部分以前没仔细看,所以没有注释。因为涉及到更多粒子滤波器的函数,后面看完了之后再补充与地图相关的内容。

// Initialize the filter using a guassian  每次位姿估计后,用高斯分布初始化一次的粒子
void pf_init(pf_t *pf, pf_vector_t mean, pf_matrix_t cov)
{
  int i;
  pf_sample_set_t *set;
  pf_sample_t *sample;
  pf_pdf_gaussian_t *pdf;
  
  set = pf->sets + pf->current_set;
  
  // Create the kd tree for adaptive sampling
  pf_kdtree_clear(set->kdtree);

  set->sample_count = pf->max_samples;

  pdf = pf_pdf_gaussian_alloc(mean, cov);
    
  // Compute the new sample poses
  for (i = 0; i < set->sample_count; i++)
  {
    sample = set->samples + i;
    sample->weight = 1.0 / pf->max_samples;
    sample->pose = pf_pdf_gaussian_sample(pdf);

    // Add sample to histogram
    pf_kdtree_insert(set->kdtree, sample->pose, sample->weight);
  }

  pf->w_slow = pf->w_fast = 0.0;

  pf_pdf_gaussian_free(pdf);
    
  // Re-compute cluster statistics
  pf_cluster_stats(pf, set); 

  //set converged to 0
  pf_init_converged(pf);

  return;
}

7、初始化完成后,还需设置传感器模型。其中重点分析SetModelLikelihoodField函数,因为涉及到map中max_occ_dist的更新。

void 
AMCLLaser::SetModelLikelihoodField(double z_hit,
                                   double z_rand,
                                   double sigma_hit,
                                   double max_occ_dist)
{
  this->model_type = LASER_MODEL_LIKELIHOOD_FIELD;
  this->z_hit = z_hit;
  this->z_rand = z_rand;
  this->sigma_hit = sigma_hit;

  map_update_cspace(this->map, max_occ_dist);
}

其中map_update_cspace函数:主要是计算每个occ_dist的值,计算每个障碍点周围4个点的障碍点的距离来更新occ_dist

// Update the cspace distance values
void map_update_cspace(map_t *map, double max_occ_dist)
{
  unsigned char* marked;
  std::priority_queue<CellData> Q;

  marked = new unsigned char[map->size_x*map->size_y];
  memset(marked, 0, sizeof(unsigned char) * map->size_x*map->size_y);

  map->max_occ_dist = max_occ_dist;

  CachedDistanceMap* cdm = get_distance_map(map->scale, map->max_occ_dist);

  // Enqueue all the obstacle cells
  CellData cell;
  cell.map_ = map;
  for(int i=0; i<map->size_x; i++)
  {
    cell.src_i_ = cell.i_ = i;
    for(int j=0; j<map->size_y; j++)
    {
      if(map->cells[MAP_INDEX(map, i, j)].occ_state == +1)
      {
    map->cells[MAP_INDEX(map, i, j)].occ_dist = 0.0;
    cell.src_j_ = cell.j_ = j;
    marked[MAP_INDEX(map, i, j)] = 1;
    Q.push(cell);
      }
      else
    map->cells[MAP_INDEX(map, i, j)].occ_dist = max_occ_dist;
    }
  }

  while(!Q.empty())
  {
    CellData current_cell = Q.top();
    if(current_cell.i_ > 0)
      enqueue(map, current_cell.i_-1, current_cell.j_, 
          current_cell.src_i_, current_cell.src_j_,
          Q, cdm, marked);
    if(current_cell.j_ > 0)
      enqueue(map, current_cell.i_, current_cell.j_-1, 
          current_cell.src_i_, current_cell.src_j_,
          Q, cdm, marked);
    if((int)current_cell.i_ < map->size_x - 1)
      enqueue(map, current_cell.i_+1, current_cell.j_, 
          current_cell.src_i_, current_cell.src_j_,
          Q, cdm, marked);
    if((int)current_cell.j_ < map->size_y - 1)
      enqueue(map, current_cell.i_, current_cell.j_+1, 
          current_cell.src_i_, current_cell.src_j_,
          Q, cdm, marked);

    Q.pop();
  }

  delete[] marked;
}

目前还不清楚这个变量的用途。 max_occ_dist通过参数设置的,表示障碍物点的最大膨胀距离。

原文地址:https://www.cnblogs.com/havain/p/15010783.html