Ros学习——movebase源码解读之amcl

1.amcl的cmakelists.txt文件

add_executable(amcl  src/amcl_node.cpp)

target_link_libraries(amcl

    amcl_sensors amcl_map amcl_pf
    ${Boost_LIBRARIES}
    ${catkin_LIBRARIES}
)

该项目生成一个amcl节点;以及amcl_sensors amcl_map amcl_pf三个库

2.amcl node

2.1 类结构

class amcl_node
{
public:
    amcl_node();
    ~amcl_node();
    void runFromBag(const std::string &in_bag_fn);//根据信息记录包来运行amcl

    int process();
    void savePoseToServer();////把位姿信息保存到参数服务器

private:
    std::shared_ptr<tf2_ros::TransformBroadcaster> tfb_;
    std::shared_ptr<tf2_ros::TransformListener> tfl_;
    std::shared_ptr<tf2_ros::Buffer> tf_;
    bool sent_first_transform_;
    tf2::Transform latest_tf_;
    bool latest_tf_valid_;

    static pf_vector_t uniformPoseGenerator(void* arg);

    static std::vector<std::pair<int, int> > free_space_indices;
    // Callbacks
    bool globalLocalizationCallback(std_srvs::Empty::Request& req,
        std_srvs::Empty::Response& res);
    bool nomotionUpdateCallback(std_srvs::Empty::Request& req,
        std_srvs::Empty::Response& res);
    bool setMapCallback(nav_msgs::SetMap::Request& req,
        nav_msgs::SetMap::Response& res);

    void laserReceived(const sensor_msgs::LaserScanConstPtr& laser_scan);
    void initialPoseReceived(const geometry_msgs::PoseWithCovarianceStampedConstPtr& msg);
    void handleInitialPoseMessage(const geometry_msgs::PoseWithCovarianceStamped& msg);
    void mapReceived(const nav_msgs::OccupancyGridConstPtr& msg);

    void handleMapMessage(const nav_msgs::OccupancyGrid& msg);
    void freeMapDependentMemory();
    map_t* convertMap(const nav_msgs::OccupancyGrid& map_msg);
    void updatePoseFromServer();
    void applyInitialPose();

    //parameter for what odom to use
    std::string odom_frame_id_;

    //paramater to store latest odom pose
    geometry_msgs::PoseStamped latest_odom_pose_;

    //parameter for what base to use
    std::string base_frame_id_;
    std::string global_frame_id_;

    bool use_map_topic_;
    bool first_map_only_;

    ros::Duration gui_publish_period;
    ros::Time save_pose_last_time;
    ros::Duration save_pose_period;

    geometry_msgs::PoseWithCovarianceStamped last_published_pose;

    map_t* map_;
    char* mapdata;
    int sx, sy;
    double resolution;

    message_filters::Subscriber<sensor_msgs::LaserScan>* laser_scan_sub_;
    tf2_ros::MessageFilter<sensor_msgs::LaserScan>* laser_scan_filter_;
    ros::Subscriber initial_pose_sub_;
    std::vector< AMCLLaser* > lasers_;
    std::vector< bool > lasers_update_;
    std::map< std::string, int > frame_to_laser_;

    // Particle filter
    pf_t *pf_;
    double pf_err_, pf_z_;
    bool pf_init_;
    pf_vector_t pf_odom_pose_;
    double d_thresh_, a_thresh_;
    int resample_interval_;
    int resample_count_;
    double laser_min_range_;
    double laser_max_range_;

    //Nomotion update control
    bool m_force_update;  // used to temporarily let amcl update samples even when no motion occurs...

    AMCLOdom* odom_;
    AMCLLaser* laser_;

    ros::Duration cloud_pub_interval;
    ros::Time last_cloud_pub_time;

    // For slowing play-back when reading directly from a bag file
    ros::WallDuration bag_scan_period_;

    void requestMap();//请求服务static_server提供map,然后调用handleMapMessage处理地图信息

    // Helper to get odometric pose from transform system
    bool getOdomPose(geometry_msgs::PoseStamped& pose,
        double& x, double& y, double& yaw,
        const ros::Time& t, const std::string& f);

    //time for tolerance on the published transform,
    //basically defines how long a map->odom transform is good for
    ros::Duration transform_tolerance_;

    ros::NodeHandle nh_;
    ros::NodeHandle private_nh_;
    ros::Publisher pose_pub_;
    ros::Publisher particlecloud_pub_;
    ros::ServiceServer global_loc_srv_;
    ros::ServiceServer nomotion_update_srv_; //to let amcl update samples without requiring motion
    ros::ServiceServer set_map_srv_;
    ros::Subscriber initial_pose_sub_old_;
    ros::Subscriber map_sub_;

    amcl_hyp_t* initial_pose_hyp_;
    bool first_map_received_;
    bool first_reconfigure_call_;

    boost::recursive_mutex configuration_mutex_;
    dynamic_reconfigure::Server<amcl::AMCLConfig> *dsrv_;
    amcl::AMCLConfig default_config_;
    ros::Timer check_laser_timer_;

    int max_beams_, min_particles_, max_particles_;
    double alpha1_, alpha2_, alpha3_, alpha4_, alpha5_;
    double alpha_slow_, alpha_fast_;
    double z_hit_, z_short_, z_max_, z_rand_, sigma_hit_, lambda_short_;
    //beam skip related params
    bool do_beamskip_;
    double beam_skip_distance_, beam_skip_threshold_, beam_skip_error_threshold_;
    double laser_likelihood_max_dist_;
    odom_model_t odom_model_type_;
    double init_pose_[3];
    double init_cov_[3];
    laser_model_t laser_model_type_;
    bool tf_broadcast_;

    void reconfigureCB(amcl::AMCLConfig &config, uint32_t level);

    ros::Time last_laser_received_ts_;
    ros::Duration laser_check_interval_;
    void checkLaserReceived(const ros::TimerEvent& event);
};

2.2 main函数

int main(int argc, char** argv)
{
  ros::init(argc, argv, "amcl");
  ros::NodeHandle nh;

  // Override default sigint handler
  signal(SIGINT, sigintHandler);

  // Make our node available to sigintHandler
  amcl_node_ptr.reset(new AmclNode());

  if (argc == 1)
  {
    // run using ROS input
    ros::spin();
  }
  else if ((argc == 3) && (std::string(argv[1]) == "--run-from-bag"))
  {
    amcl_node_ptr->runFromBag(argv[2]);
  }

  // Without this, our boost locks are not shut down nicely
  amcl_node_ptr.reset();

  // To quote Morgan, Hooray!
  return(0);
}

2.3 关键步骤

0.构造函数AmclNode()

——>参数配置:粒子滤波参数,运动模型参数,观测模型参数等

——>updatePoseFromServer():从参数服务器中获取初始位姿及初始分布

——>pose和particle息发布:

  1. amcl_pose: geometry_msgs::PoseWithCovarianceStamped,后验位姿+一个6*6的协方差矩阵(xyz+三个转角)
  2. particlecloud:geometry_msgs::PoseArray,粒子位姿的数组

——>创建服务:

  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://handleMapMessage()进行地图转换 ,记录free space ,以及初始化pf_t 结构体,实例化运动模型(odom)和观测模型(laser);          //handleInitialPoseMessage(req.initial_pose); 根据接收的初始位姿消息,在该位姿附近高斯采样重新生成粒子集
  4. dynamic_reconfigure::Server动态参数配置器。

——>订阅话题:

  1. scan_topic_:sensor_msgs::LaserScan,AmclNode::laserReceived():回调函数laserReceived是粒子滤波主要过程,根据激光扫描数据更新粒子
  2. initialpose:AmclNode::initialPoseReceived():这个应该就是订阅rviz中给的初始化位姿,调用AmclNode::handleInitialPoseMessage,只接受global_frame_id_(一般为map)的坐标,并重新生成粒子。在接收到的初始位姿附近采样生成 粒子集。
  3. map:AmclNode::mapReceived这个在use_map_topic_的时候才订阅,否则requestMap();我这里也没有订阅,因为只使用了一个固定的地图。

——>一个15秒的定时器:AmclNode::checkLaserReceived,检查 15上一次收到激光雷达数据至今是否超过15秒,如超过则报错。

1.requestmap()

——>requestMap:一直请求服务static_map直到成功

——>handleMapMessage():  1.将受到的msg转换成标准地图,0->-1(不是障碍);100->+1(障碍);else->0(不明)

              2.提取非障碍部分,列入Vector类型的free_space_indices

              3.创建粒子滤波器——>updatePoseFromServer()——>初始化粒子滤波器——>初始化传感器(odom,laser)——>applyInitialPose()

2.laserReceived()

——>获取laser对应于baselink的坐标

——>获取baselink对应于odom的坐标
——>根据里程计的变化值+高斯噪音 更新 pf_t中samples的内里程计值(运动模型)
odom->updateAction()
——>根据当前雷达数据更新各里程计对应的权值weights
laser_[laser_index]->updateSensor()
——>得到滤波结果后,分别在话题/amcl_pose和/ particlecloud上发布位姿和粒子集

3.主要过程

  • 构造时初始化,从参数服务器中获取数据初始化各类参数;(接收地图设置,gui显示发布频率,保存位姿到参数服务器频率,laser测距范围及其概率模型参数,odom概率模型参数,粒子滤波及kld重采样参数,从参数服务器获取初始位姿,然后初始化了订阅者,发布者,服务)
  • 地图加载,两种方式(1.订阅/map话题2.请求服务得到地图),得到地图后也有个初始化过程(将消息类型的地图转换为定义的map类数据,统计free状态的栅格索引,从参数服务器获取位姿信息,并初始化粒子滤波器pf_,初始化odom模型参数,初始化laser模型参数)
  • 粒子滤波,订阅laser_scan的回调函数中处理,得到结果后发布位姿和粒子集
  • initialpose的回调,接收到初始位姿消息后,融入最新的里程改变,然后在该位姿附近重新生成粒子集

4.主要数据类型与算法

4.1 pf

1. eig3.c

实现的是一个3x3对称矩阵的特征值与特征向量的计算,首先用Householder矩阵将矩阵变换为三对角矩阵,然后使用ql分解迭代计算 。

2. pf_kdtree.c定义了一个kdtree以及维护方法来管理所有粒子 :创建、销毁、清除元素、插入元素、计算概率估计、比较、查找、

typedef struct
{
  // Cell size
  double size[3];

  // The root node of the tree
  pf_kdtree_node_t *root;

  // The number of nodes in the tree
  int node_count, node_max_count;
  pf_kdtree_node_t *nodes;

  // The number of leaf nodes in the tree
  int leaf_count;

} pf_kdtree_t;

3.pf_pdf.c主要定义了一个从给定pdf中采样粒子的方法

4.pf_vector.c定义了三维列向量和三维矩阵和基本的运算方法:加、减、全局和局部坐标系变换、是否NAN或INF

5.pf.c定义了粒子单元pf_sample_t,粒子集pf_sample_set_t,粒子滤波pf_t的数据类型,还有一个 pf_cluster_t表示粒子集的聚类信息,关键函数主要包含如下三个,分别对应粒子滤波中的运动更新,观测更新,重采样三个过程

4.2 sensors

1. amcl_sencor.cpp

——>定义了基类,以虚函数InitSensor()、UpdateSensor()、UpdateAction()提供接口

2. amcl_laser.cpp

——>定义了激光数据类型,三种观测更新模型(详细见<<概率机器人>>),具体实现了UpdateSensor,用于计算粒子权值

3. amcl_odom.cpp
——>具体实现了基类定义的UpdateAction函数,用于根据运动更新粒子,定义了两种运动模型,差分和全向

4.3 map

——>map中主要定义了概率栅格地图的数据表示

typedef struct
{
int occ_state;// Occupancy state (-1 = free, 0 = unknown, +1 = occ)
double occ_dist;// Distance to the nearest occupied cell
} map_cell_t;
// Description for a map
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;

部分参考:https://blog.csdn.net/qq_27753669/article/details/80011156

原文地址:https://www.cnblogs.com/yrm1160029237/p/10304368.html

时间: 2024-08-01 03:12:36

Ros学习——movebase源码解读之amcl的相关文章

Spark学习之路 (十六)SparkCore的源码解读(二)spark-submit提交脚本

讨论QQ:1586558083 目录 一.概述 二.源码解读 2.2 find-spark-home 2.3 spark-class 2.4 SparkSubmit 正文 回到顶部 一.概述 上一篇主要是介绍了spark启动的一些脚本,这篇主要分析一下Spark源码中提交任务脚本的处理逻辑,从spark-submit一步步深入进去看看任务提交的整体流程,首先看一下整体的流程概要图: 回到顶部 二.源码解读 2.1 spark-submit # -z是检查后面变量是否为空(空则真) shell可以

QCustomplot使用分享(二) 源码解读

一.头文件概述 从这篇文章开始,我们将正式的进入到QCustomPlot的实践学习中来,首先我们先来学习下QCustomPlot的类图,如果下载了QCustomPlot源码的同学可以自己去QCustomPlot的目录下documentation/qcustomplot下寻找一个名字叫做index.html的文件,将其在浏览器中打开,也是可以找到这个库的类图.如图1所示,是组成一个QCustomPlot类图的可能组成形式. 一个图表(QCustomPlot):包含一个或者多个图层.一个或多个ite

jdk1.8.0_45源码解读——HashMap的实现

jdk1.8.0_45源码解读——HashMap的实现 一.HashMap概述 HashMap是基于哈希表的Map接口实现的,此实现提供所有可选的映射操作.存储的是<key,value>对的映射,允许多个null值和一个null键.但此类不保证映射的顺序,特别是它不保证该顺序恒久不变.  除了HashMap是非同步以及允许使用null外,HashMap 类与 Hashtable大致相同. 此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能.迭代col

基于Docker的TensorFlow机器学习框架搭建和实例源码解读

概述:基于Docker的TensorFlow机器学习框架搭建和实例源码解读,TensorFlow作为最火热的机器学习框架之一,Docker是的容器,可以很好的结合起来,为机器学习或者科研人员提供便捷的机器学习开发环境,探索人工智能的奥秘,容器随开随用方便快捷.源码解析TensorFlow容器创建和示例程序运行,为热爱机器学者降低学习难度. 默认机器已经装好了Docker(Docker安装和使用可以看我另一篇博文:Ubuntu16.04安装Docker1.12+开发实例+hello world+w

jQuery源码解读第4章---对extend的解读

为什么我们一开始就说extend呢 其实我读源码的过程中,发现其实我们方法就在源码中都调用了extend Callbacks Deferred这些工具方法 所以我们很有必要先学习这些,,,,,,,,,,,这样对我们后续的学习很有帮助 对extend的学习,,,,,首先看下extend我们平时是怎么用的 1...合并对象 extend(dest,src1,src2,,,,,) 它的含义就是将src1,src2......合并到dest中 然后放回的结果就是合并后的dest eq: var dest

struct2源码解读(1)之struts2启动

struct2源码解读(1)之struts启动 之前用struct2.spring.hibernate在开发一个电子商城,每天都在重复敲代码,感觉对struct2.spring.hibernate的理解都在使用层面上,虽然敲了几个月代码,但是技术水平还是得不到显著提高.于是就想着研究下struct2.spring.hibernate的源码,研究完后不仅对struct2.spring.hibernate加深了了解,同时也加强了java的学习,例如xml的解析,字符操作,线程等等,受益匪浅.想着当初

Redis内存管理的基石zmallc.c源码解读(附录):源码结构表

Redis内存管理的基石zmallc.c源码解读(一) Redis内存管理的基石zmallc.c源码解读(二) 前面两篇博文,细致地介绍了zmalloc.c文件的各个函数,不过大家要想深入学习Redis,还需要自己去看源码才是,我梳理了一下zmalloc.c文件的结构,为大家阅读源码提供便利. 全局变量 名称 类型 说明 used_memory static size_t Redis已用内存空间的大小 zmalloc_thread_safe static int 标识是否线程安全 used_me

源码解读—HashTable

在上一篇学习过HashMap(源码解读—HashMap)之后对hashTable也产生了兴趣,随即便把hashTable的源码看了一下.和hashMap类似,但是也有不同之处. public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable  实现接口:Map,Cloneable,Serializable 继承自Diction

RequireJs 源码解读及思考

写在前面: 最近做的一个项目,用的require和backbone,对两者的使用已经很熟悉了,但是一直都有好奇他们怎么实现的,一直寻思着读读源码.现在项目结束,终于有机会好好研究一下. 本文重要解读requirejs的源码,backbone源码分析将会随后给出. 行文思路: requirejs 基本介绍 requirejs使用后的几个好奇 requirejs源码解读 requirejs基本介绍 由于篇幅有限,这里不会详解requirejs的使用和api,建议读者朋友自己去用几次,再详读api.