ROS Navigation之amcl源码解析(完全详解)

柔情只为你懂 2023-06-13 11:14 107阅读 0赞

0. 写在最前面

本文持续更新地址:https://haoqchen.site/2018/05/06/amcl-code/

这篇文章记录下自己在阅读amcl源码过程中的一些理解,如有不妥,欢迎评论或私信。

本文中所有代码因为篇幅等问题,都只给出主要部分,详细的自己下载下来对照着看。

如果觉得写得还不错,就请收藏一下啦~~~也可以找一下我写的其他包的源码解读来看一下。关注一下我的专栏什么的。

帮我github page点个 Star呗~~~

1. amcl是干什么的

Amcl is a probabilistic localization system for a robot moving in 2D. It implements the adaptive (or KLD-sampling) Monte Carlo localization approach (as described by Dieter Fox), which uses a particle filter to track the pose of a robot against a known map.

以上是官网的介绍,说白了就是2D的概率定位系统,输入激光雷达数据、里程计数据,输出机器人在地图中的位姿。用的是自适应蒙特卡洛定位方法,这个方法是在已知地图中使用粒子滤波方法得到位姿的。

如下图所示,如果里程计没有误差,完美的情况下,我们可以直接使用里程计信息(上半图)推算出机器人(base_frame)相对里程计坐标系的位置。但现实情况,里程计存在漂移以及无法忽略的累计误差,所以AMCL采用下半图的方法,即先根据里程计信息初步定位base_frame,然后通过测量模型得到base_frame相对于map_frame(全局地图坐标系),也就知道了机器人在地图中的位姿。(注意,这里虽然估计的是base到map的转换,但最后发布的是map到odom的转换,可以理解为里程计的漂移。)
frame\_relation

2. 总体情况

2.1 CMakeLists

研究一个ROS包,肯定要先看CMakeLists。我们可以看到,这个包会生成

三个库:

  • amcl_pf
  • amcl_map
  • amcl_sensors
    一个节点:
  • amcl

2.2 其中amcl的订阅与发布:

发布话题:

  1. amcl_pose: geometry_msgs::PoseWithCovarianceStamped,后验位姿+一个6*6的协方差矩阵(xyz+三个转角)
  2. particlecloud:geometry_msgs::PoseArray,粒子位姿的数组
  3. 一个15秒的定时器:AmclNode::checkLaserReceived,检查上一次收到激光雷达数据至今是否超过15秒,如超过则报错。

发布服务:

  1. global_localization:&AmclNode::globalLocalizationCallback,这里是没有给定初始位姿的情况下在全局范围内初始化粒子位姿,该Callback调用pf_init_model,然后调用AmclNode::uniformPoseGenerator在地图的free点随机生成pf->max_samples个粒子
  2. request_nomotion_update:&AmclNode::nomotionUpdateCallback没运动模型更新的情况下也暂时更新粒子群
  3. set_map:&AmclNode::setMapCallback
  4. dynamic_reconfigure::Server动态参数配置器。

订阅话题:

  1. scan_topic_:sensor_msgs::LaserScan,AmclNode::laserReceived,这里是tf订阅,转换到odom_frame_id_
  2. initialpose:AmclNode::initialPoseReceived,这个应该就是订阅rviz中给的初始化位姿,调用AmclNode::handleInitialPoseMessage,只接受global_frame_id_(一般为map)的坐标,并重新生成粒子
  3. map:AmclNode::mapReceived这个在use_map_topic_的时候才订阅,否则requestMap();我这里也没有订阅,因为只使用了一个固定的地图。

3. amcl_node.cpp

这个文件实现了上述的amcl节点功能。

3.1 main

  1. int main(int argc, char** argv)
  2. {
  3. ros::init(argc, argv, "amcl");
  4. ros::NodeHandle nh;
  5. // Override default sigint handler
  6. signal(SIGINT, sigintHandler);
  7. // Make our node available to sigintHandler
  8. amcl_node_ptr.reset(new AmclNode());
  9. if (argc == 1)
  10. {
  11. // run using ROS input
  12. ros::spin();
  13. }
  14. }

没啥好说的,主要就是定义了amcl节点,初始化了一个AmclNode的类对象,最关键的中断函数配置都在该类的构造函数中实现。

3.2 AmclNode

  1. AmclNode::AmclNode()
  2. {
  3. boost::recursive_mutex::scoped_lock l(configuration_mutex_);
  4. //测量模型选择,具体参考《概率机器人》第6章内容,这里默认的likelihood_field是6.4节的似然域模型
  5. std::string tmp_model_type;
  6. private_nh_.param("laser_model_type", tmp_model_type, std::string("likelihood_field"));
  7. //机器人模型选择,具体参考《概率机器人》5.4节里程计运动模型内容,这里默认的diff应该是差分型(两轮驱动)的机器人?,另外一个是全向机器人?
  8. private_nh_.param("odom_model_type", tmp_model_type, std::string("diff"));
  9. //从参数服务器中获取初始位姿及初始分布
  10. updatePoseFromServer();
  11. //定义话题及订阅等,具体在本文第2章有讲
  12. pose_pub_ = nh_.advertise<geometry_msgs::PoseWithCovarianceStamped>("amcl_pose", 2, true);
  13. }

3.3 requestMap

这里请求服务static_server提供map,然后调用handleMapMessage处理地图信息。这里的地图类型是nav_msgs::OccupancyGrid,这里不介绍,有兴趣自己去看数据类型。然后获取初始位姿、初始化粒子、里程计信息等。

  1. AmclNode::requestMap()
  2. {
  3. //一直请求服务static_map直到成功,该服务在map_server这个包的map_server节点中进行定义
  4. while(!ros::service::call("static_map", req, resp))
  5. {
  6. ROS_WARN("Request for map failed; trying again...");
  7. ros::Duration d(0.5);
  8. d.sleep();
  9. }
  10. handleMapMessage( resp.map );
  11. }
  12. AmclNode::handleMapMessage(const nav_msgs::OccupancyGrid& msg)
  13. {
  14. //free相应的指针
  15. freeMapDependentMemory();
  16. //转换成标准地图,0->-1(不是障碍);100->+1(障碍);else->0(不明)
  17. map_ = convertMap(msg);
  18. //将不是障碍的点的坐标保存下来
  19. #if NEW_UNIFORM_SAMPLING
  20. // Index of free space
  21. free_space_indices.resize(0);
  22. for(int i = 0; i < map_->size_x; i++)
  23. for(int j = 0; j < map_->size_y; j++)
  24. if(map_->cells[MAP_INDEX(map_,i,j)].occ_state == -1)
  25. free_space_indices.push_back(std::make_pair(i,j));
  26. #endif
  27. // Create the particle filter,定义了一个回调,尚未清除干啥
  28. pf_ = pf_alloc(min_particles_, max_particles_,
  29. alpha_slow_, alpha_fast_,
  30. (pf_init_model_fn_t)AmclNode::uniformPoseGenerator,
  31. (void *)map_);
  32. // 从参数服务器获取初始位姿及方差放到pf中
  33. updatePoseFromServer();
  34. // 定义里程计与激光雷达并初始化数据
  35. // Odometry
  36. delete odom_;
  37. odom_ = new AMCLOdom();
  38. ROS_ASSERT(odom_);
  39. odom_->SetModel( odom_model_type_, alpha1_, alpha2_, alpha3_, alpha4_, alpha5_ );
  40. // Laser
  41. delete laser_;
  42. laser_ = new AMCLLaser(max_beams_, map_);
  43. // In case the initial pose message arrived before the first map,
  44. // try to apply the initial pose now that the map has arrived.
  45. applyInitialPose();
  46. }

3.4 laserReceived

其中变量pose是base相对于odom的位姿;pf_odom_pose_则是上一时刻base相对于odom的位姿,用于后续得到机器人的相对运动程度。

  1. AmclNode::laserReceived(const sensor_msgs::LaserScanConstPtr& laser_scan)
  2. {
  3. // Do we have the base->base_laser Tx yet?感觉这里是支持多个激光雷达的,找到当前响应的激光雷达之前存储的信息,
  4. //如相对于base的转换,是否更新等,使用map结构直接通过id来找到对应序号即可
  5. //如果之前没有备案则在map结构中备案,然后存到frame_to_laser_及lasers_中下次备用
  6. if(frame_to_laser_.find(laser_scan->header.frame_id) == frame_to_laser_.end())
  7. {
  8. frame_to_laser_[laser_scan->header.frame_id] = laser_index;
  9. } else {
  10. // we have the laser pose, retrieve laser index
  11. laser_index = frame_to_laser_[laser_scan->header.frame_id];
  12. }
  13. // Where was the robot when this scan was taken?获得base在激光雷达扫描时候相对于odom的相对位姿
  14. pf_vector_t pose;
  15. if(!getOdomPose(latest_odom_pose_, pose.v[0], pose.v[1], pose.v[2],
  16. laser_scan->header.stamp, base_frame_id_))
  17. {
  18. ROS_ERROR("Couldn't determine robot's pose associated with laser scan");
  19. return;
  20. }
  21. pf_vector_t delta = pf_vector_zero();
  22. //如果不是第一帧,看运动幅度是否超过设定值需要更新(第一帧是指更新了地图或者更新初始位姿)
  23. if(pf_init_)
  24. {
  25. // Compute change in pose
  26. //delta = pf_vector_coord_sub(pose, pf_odom_pose_);
  27. delta.v[0] = pose.v[0] - pf_odom_pose_.v[0];
  28. delta.v[1] = pose.v[1] - pf_odom_pose_.v[1];
  29. delta.v[2] = angle_diff(pose.v[2], pf_odom_pose_.v[2]);
  30. // See if we should update the filter
  31. bool update = fabs(delta.v[0]) > d_thresh_ ||
  32. fabs(delta.v[1]) > d_thresh_ ||
  33. fabs(delta.v[2]) > a_thresh_;
  34. update = update || m_force_update;
  35. m_force_update=false;
  36. // Set the laser update flags
  37. if(update)
  38. for(unsigned int i=0; i < lasers_update_.size(); i++)
  39. lasers_update_[i] = true;
  40. }
  41. //第一帧则初始化一些值
  42. bool force_publication = false;
  43. if(!pf_init_)
  44. {
  45. // Pose at last filter update
  46. pf_odom_pose_ = pose;
  47. // Filter is now initialized
  48. pf_init_ = true;
  49. }
  50. // If the robot has moved, update the filter
  51. else if(pf_init_ && lasers_update_[laser_index])//如果已经初始化并需要更新则更新运动模型
  52. {
  53. //这是amcl_odom.cpp中最重要的一个函数,实现了用运动模型来更新现有的每一个粒子的位姿(这里得到的只是当前时刻的先验位姿)
  54. // Use the action data to update the filter
  55. odom_->UpdateAction(pf_, (AMCLSensorData*)&odata);
  56. }
  57. bool resampled = false;
  58. // If the robot has moved, update the filter
  59. if(lasers_update_[laser_index])
  60. {
  61. //接下来一大片都是对原始激光雷达数据进行处理,转换到AMCLLaserData。包括角度最小值、增量到base_frame的转换、测距距离最大值、最小值。
  62. //具体看代码,篇幅限制不详细列了
  63. try
  64. {
  65. tf_->transformQuaternion(base_frame_id_, min_q, min_q);
  66. tf_->transformQuaternion(base_frame_id_, inc_q, inc_q);
  67. }
  68. ldata.range_max = std::min(laser_scan->range_max, (float)laser_max_range_);
  69. range_min = std::max(laser_scan->range_min, (float)laser_min_range_);
  70. ldata.ranges = new double[ldata.range_count][2];
  71. for(int i=0;i<ldata.range_count;i++)
  72. {
  73. // amcl doesn't (yet) have a concept of min range. So we'll map short
  74. // readings to max range.//激光雷达传上来的数据只标记了最大值最小值,但是没做处理,直接将原始数据传上来,
  75. if(laser_scan->ranges[i] <= range_min)//这里将最小值当最大值处理,因为在类似likelihood_field模型中,会直接将最大值丢弃
  76. ldata.ranges[i][0] = ldata.range_max;
  77. }
  78. //注意这里是amcl_laser.cpp的UpdateSensor,不是amcl_sensor.cpp的。通过判断前面设置的测量模型调用pf_update_sensor,
  79. //该函数需要传入相应模型的处理函数,这里所有的测量模型在《概率机器人》的第六章有详细讲,具体自己看,后面只针对自己使用的likelihood_field模型讲一下
  80. //pf_update_sensor实现对所有粒子更新权重,并归一化、计算出《概率机器人》8.3.5的失效恢复的长期似然和短期似然
  81. lasers_[laser_index]->UpdateSensor(pf_, (AMCLSensorData*)&ldata);
  82. lasers_update_[laser_index] = false;
  83. pf_odom_pose_ = pose;
  84. //多少次激光雷达回调之后进行重采样呗,我这里resample_interval_=0.5,只有一个激光雷达,每次都更新。
  85. // Resample the particles
  86. if(!(++resample_count_ % resample_interval_))
  87. {
  88. //按照一定的规则重采样粒子,包括前面说的失效恢复、粒子权重等,然后放入到kdtree,暂时先理解成关于位姿的二叉树,
  89. //然后进行聚类,得到均值和方差等信息,个人理解就是将相近的一堆粒子融合成一个粒子了,没必要维持太多相近的@TODO
  90. pf_update_resample(pf_);
  91. resampled = true;
  92. }
  93. // Publish the resulting cloud
  94. // TODO: set maximum rate for publishing
  95. if (!m_force_update) {
  96. //将新粒子发布到全局坐标系下,一般是map
  97. particlecloud_pub_.publish(cloud_msg);
  98. }
  99. }
  100. if(resampled || force_publication)
  101. {
  102. //遍历所有粒子簇,找出权重均值最大的簇,其平均位姿就是我们要求的机器人后验位姿,到此一次循环已经所有完成
  103. for(int hyp_count = 0;
  104. hyp_count < pf_->sets[pf_->current_set].cluster_count; hyp_count++)
  105. {
  106. if (!pf_get_cluster_stats(pf_, hyp_count, &weight, &pose_mean, &pose_cov))
  107. {
  108. break;
  109. }
  110. hyps[hyp_count].weight = weight;
  111. hyps[hyp_count].pf_pose_mean = pose_mean;
  112. hyps[hyp_count].pf_pose_cov = pose_cov;
  113. if(hyps[hyp_count].weight > max_weight)
  114. {
  115. max_weight = hyps[hyp_count].weight;
  116. max_weight_hyp = hyp_count;
  117. }
  118. }
  119. //将位姿、粒子集、协方差矩阵等进行更新、发布
  120. if(max_weight > 0.0)
  121. {
  122. geometry_msgs::PoseWithCovarianceStamped p;
  123. // Fill in the header
  124. p.header.frame_id = global_frame_id_;
  125. p.header.stamp = laser_scan->header.stamp;
  126. // Copy in the pose
  127. p.pose.pose.position.x = hyps[max_weight_hyp].pf_pose_mean.v[0];
  128. p.pose.pose.position.y = hyps[max_weight_hyp].pf_pose_mean.v[1];
  129. tf::quaternionTFToMsg(tf::createQuaternionFromYaw(hyps[max_weight_hyp].pf_pose_mean.v[2]),
  130. p.pose.pose.orientation);
  131. // Copy in the covariance, converting from 3-D to 6-D
  132. pf_sample_set_t* set = pf_->sets + pf_->current_set;
  133. for(int i=0; i<2; i++)
  134. {
  135. for(int j=0; j<2; j++)
  136. {
  137. p.pose.covariance[6*i+j] = set->cov.m[i][j];
  138. }
  139. }
  140. p.pose.covariance[6*5+5] = set->cov.m[2][2];
  141. pose_pub_.publish(p);
  142. last_published_pose = p;
  143. //这里就是所说的,map~base减去odom~base得到map~odom,最后发布的是map~odom。
  144. // subtracting base to odom from map to base and send map to odom instead
  145. tf::Stamped<tf::Pose> odom_to_map;
  146. try
  147. {
  148. tf::Transform tmp_tf(tf::createQuaternionFromYaw(hyps[max_weight_hyp].pf_pose_mean.v[2]),
  149. tf::Vector3(hyps[max_weight_hyp].pf_pose_mean.v[0],
  150. hyps[max_weight_hyp].pf_pose_mean.v[1],
  151. 0.0));
  152. tf::Stamped<tf::Pose> tmp_tf_stamped (tmp_tf.inverse(),
  153. laser_scan->header.stamp,
  154. base_frame_id_);
  155. this->tf_->transformPose(odom_frame_id_,
  156. tmp_tf_stamped,
  157. odom_to_map);
  158. }
  159. catch(tf::TransformException)
  160. {
  161. ROS_DEBUG("Failed to subtract base to odom transform");
  162. return;
  163. }
  164. latest_tf_ = tf::Transform(tf::Quaternion(odom_to_map.getRotation()),
  165. tf::Point(odom_to_map.getOrigin()));
  166. latest_tf_valid_ = true;
  167. if (tf_broadcast_ == true)
  168. {
  169. // We want to send a transform that is good up until a
  170. // tolerance time so that odom can be used
  171. ros::Time transform_expiration = (laser_scan->header.stamp +
  172. transform_tolerance_);
  173. tf::StampedTransform tmp_tf_stamped(latest_tf_.inverse(),
  174. transform_expiration,
  175. global_frame_id_, odom_frame_id_);
  176. this->tfb_->sendTransform(tmp_tf_stamped);
  177. sent_first_transform_ = true;
  178. }
  179. }
  180. else
  181. {
  182. ROS_ERROR("No pose!");
  183. }
  184. }
  185. else if(latest_tf_valid_)//if(resampled || force_publication)
  186. {
  187. }
  188. }

3.5 AMCLOdom::UpdateAction

首先获取上一帧的位姿,然后根据自己前面选择的机器人模型进行运动模型更新。这里由于我自己的模型是ODOM_MODEL_DIFF,所以只针对这个模型进行讲解,剩下的原理应该一样,细节上有区别,自己看。代码中有讲到,利用到的算法是《Probabilistic Robotics 2》136页的内容,如果是Probabilistic Robotics 1或者中文版其实就是5.4节中的采样算法,这里不再细述。

我直白一点的理解就是,已知上一时刻所有粒子的后验位姿、上一时刻到现在两个轮子的编码器值,给所有粒子随机采样当前时刻的先验位姿。为什么是随机,不是确定的直接从编码器计算得到呢?因为编码器等存在误差,所以采样到的每个粒子的先验位姿都是不确定的,但理论上是服从一个分布的(这里是正态,书本上给出三种分布),跟实际运动有关,如果运动越大、则采样到的跟直接里程计计算得到的值就越有可能相差的比较大(算法上表示为方差越大)。

  1. // Apply the action model
  2. bool AMCLOdom::UpdateAction(pf_t *pf, AMCLSensorData *data)
  3. {
  4. set = pf->sets + pf->current_set;
  5. pf_vector_t old_pose = pf_vector_sub(ndata->pose, ndata->delta);
  6. switch( this->model_type )
  7. {
  8. case ODOM_MODEL_DIFF:
  9. {
  10. // Implement sample_motion_odometry (Prob Rob p 136)
  11. double delta_rot1, delta_trans, delta_rot2;
  12. double delta_rot1_hat, delta_trans_hat, delta_rot2_hat;
  13. double delta_rot1_noise, delta_rot2_noise;
  14. //计算里程计得到的ut,即用旋转、直线运动和另一个旋转来表示相对运动
  15. // Avoid computing a bearing from two poses that are extremely near each
  16. // other (happens on in-place rotation).
  17. if(sqrt(ndata->delta.v[1]*ndata->delta.v[1] + //如果原地旋转的情况,定义第一个旋转为0,认为所有旋转都是第二个旋转做的
  18. ndata->delta.v[0]*ndata->delta.v[0]) < 0.01)
  19. delta_rot1 = 0.0;
  20. else
  21. delta_rot1 = angle_diff(atan2(ndata->delta.v[1], ndata->delta.v[0]),
  22. old_pose.v[2]);
  23. delta_trans = sqrt(ndata->delta.v[0]*ndata->delta.v[0] +
  24. ndata->delta.v[1]*ndata->delta.v[1]);
  25. delta_rot2 = angle_diff(ndata->delta.v[2], delta_rot1);
  26. //这里作者比书本多考虑了一种情况,就是机器人旋转小角度,然后往后走的情况,比如旋转-20°,然后往后走,计算出来可能是160°,
  27. //但实际只转了20°。按书本可能是比较大的方差(160°),不太准确,但这里的话还是按照比较小的方差来采样。
  28. //但我表示怀疑,万一真的转了160°再前向运动的话怎么办?还是用比较小的方差来采样,可能会使得采样的准确率下降。
  29. //实际中,编码器采样率很高,且设定了一个很小的变化角度就进行运动模型更新,所以我说的这种情况几乎不会发生
  30. // We want to treat backward and forward motion symmetrically for the
  31. // noise model to be applied below. The standard model seems to assume
  32. // forward motion.
  33. delta_rot1_noise = std::min(fabs(angle_diff(delta_rot1,0.0)),
  34. fabs(angle_diff(delta_rot1,M_PI)));
  35. delta_rot2_noise = std::min(fabs(angle_diff(delta_rot2,0.0)),
  36. fabs(angle_diff(delta_rot2,M_PI)));
  37. //对每个粒子用书本的方法进行更新
  38. for (int i = 0; i < set->sample_count; i++)
  39. {
  40. pf_sample_t* sample = set->samples + i;
  41. // Sample pose differences
  42. delta_rot1_hat = angle_diff(delta_rot1,
  43. pf_ran_gaussian(this->alpha1*delta_rot1_noise*delta_rot1_noise +
  44. this->alpha2*delta_trans*delta_trans));
  45. delta_trans_hat = delta_trans -
  46. pf_ran_gaussian(this->alpha3*delta_trans*delta_trans +
  47. this->alpha4*delta_rot1_noise*delta_rot1_noise +
  48. this->alpha4*delta_rot2_noise*delta_rot2_noise);
  49. delta_rot2_hat = angle_diff(delta_rot2,
  50. pf_ran_gaussian(this->alpha1*delta_rot2_noise*delta_rot2_noise +
  51. this->alpha2*delta_trans*delta_trans));
  52. // Apply sampled update to particle pose
  53. sample->pose.v[0] += delta_trans_hat *
  54. cos(sample->pose.v[2] + delta_rot1_hat);
  55. sample->pose.v[1] += delta_trans_hat *
  56. sin(sample->pose.v[2] + delta_rot1_hat);
  57. sample->pose.v[2] += delta_rot1_hat + delta_rot2_hat;
  58. }
  59. }
  60. }
  61. return true;
  62. }

3.6 LikelihoodFieldModel

算法原理请参看《概率机器人》6.4节,这里会给每个粒子(代表一个位姿)计算概率、更新概率,然后返回所有粒子概率的总和。

  1. double AMCLLaser::LikelihoodFieldModel(AMCLLaserData *data, pf_sample_set_t* set)
  2. {
  3. // Compute the sample weights遍历所有激光雷达数据点,
  4. for (j = 0; j < set->sample_count; j++)
  5. {
  6. sample = set->samples + j;
  7. pose = sample->pose;//遍历每个粒子,这是粒子对应的位姿,是经运动模型更新后先验位姿
  8. // Take account of the laser pose relative to the robot
  9. //这个应该是通过机器人与全局坐标系的位姿(每个粒子的位姿),计算激光雷达相对于全局坐标系的位姿。方便后续将激光雷达扫描的终点转换到全局坐标系
  10. //点进去pf_vector_coord_add这个函数,b对应的就是《概率机器人》P128,6.4第一个公式中的机器人在t时刻的位姿,a代表“与机器人固连的传感器局部坐标系位置”
  11. pose = pf_vector_coord_add(self->laser_pose, pose);//这里的laser_pose其实是laserReceived函数一开始初始化每个激光雷达时得到的激光雷达在base中的位姿
  12. p = 1.0;
  13. // Pre-compute a couple of things
  14. double z_hit_denom = 2 * self->sigma_hit * self->sigma_hit;//测量噪声的方差
  15. double z_rand_mult = 1.0/data->range_max;//无法解释的随机测量的分母
  16. //因为时间问题,并不是全部点都进行似然计算的,这里只是间隔地选点,我这里设置的是一共选择60个点~~~
  17. step = (data->range_count - 1) / (self->max_beams - 1);
  18. for (i = 0; i < data->range_count; i += step)
  19. {
  20. obs_range = data->ranges[i][0];
  21. obs_bearing = data->ranges[i][1];
  22. //继续上面《概率机器人》P128的后半段公式,得到激光雷达点最远端的世界坐标
  23. // Compute the endpoint of the beam
  24. hit.v[0] = pose.v[0] + obs_range * cos(pose.v[2] + obs_bearing);
  25. hit.v[1] = pose.v[1] + obs_range * sin(pose.v[2] + obs_bearing);
  26. //转换到栅格坐标
  27. // Convert to map grid coords.
  28. int mi, mj;
  29. mi = MAP_GXWX(self->map, hit.v[0]);
  30. mj = MAP_GYWY(self->map, hit.v[1]);
  31. //这是提前计算好的最近距离,计算函数在map_cspace.cpp的map_update_cspace中实现遍历计算,该函数是被AMCLLaser::SetModelLikelihoodField调用
  32. //而这个函数则只在AmclNode::handleMapMessage中调用???而这个handle在多个地方调用,暂时未清楚@TODO
  33. // Part 1: Get distance from the hit to closest obstacle.
  34. // Off-map penalized as max distance
  35. if(!MAP_VALID(self->map, mi, mj))
  36. z = self->map->max_occ_dist;
  37. else
  38. z = self->map->cells[MAP_INDEX(self->map,mi,mj)].occ_dist;
  39. //书本算法流程的公式
  40. // Gaussian model
  41. // NOTE: this should have a normalization of 1/(sqrt(2pi)*sigma)
  42. pz += self->z_hit * exp(-(z * z) / z_hit_denom);
  43. // Part 2: random measurements
  44. pz += self->z_rand * z_rand_mult;
  45. // TODO: outlier rejection for short readings
  46. assert(pz <= 1.0);
  47. assert(pz >= 0.0);
  48. // p *= pz;
  49. // here we have an ad-hoc weighting scheme for combining beam probs
  50. // works well, though...
  51. p += pz*pz*pz;//这里有点不太懂。。。理论上应该是上面注释掉的公式。这里应该是为了防止噪声,比如某个出现错误的0,其余都不管用了
  52. }//每个粒子的所有激光雷达点循环
  53. sample->weight *= p;
  54. total_weight += sample->weight;
  55. }//粒子循环
  56. return(total_weight);
  57. }

参考

https://wenku.baidu.com/view/92a45164a9956bec0975f46527d3240c8447a1d5.html
https://www.cnblogs.com/lysuns/articles/4710712.html
ROS官网参数配置

喜欢我的文章的话Star一下呗Star

版权声明:本文为白夜行的狼原创文章,未经允许不得以任何形式转载

发表评论

表情:
评论列表 (有 0 条评论,107人围观)

还没有评论,来说两句吧...

相关阅读