Ros navigation功能包集源码详解(一) amcl

Ros navigation功能包集源码详解(一) amcl_第1张图片
  本文主要解读amcl包对蒙特卡洛定位算法的具体实现过程,主要关注源码中定义的数据类型,及程序的具体实现流程,对于详细的算法原理不作过多解释(主要是我不太懂,讲不好),想了解涉及到的理论知识推荐阅读<< Probabilistic Robotics >>

1.定位包amcl的 功能

  输入:地图,激光数据,里程计
  输出:位姿pose(x,y,θ),粒子集

2.源码文件结构

Ros navigation功能包集源码详解(一) amcl_第2张图片
最为核心的是include目录和src目录,一共包含三个大类,map,pf,以及sensors,还有一个重要文件amcl_node.cpp
 1). map处理地图,pf是算法的核心粒子滤波(particle filter),sensors就是处理雷达跟里程计传感器的
 2). amcl_node.cpp,定义了一个可运行节点,定位过程主要在此文件中组织实现

3.主要数据类型与算法

3.1 pf

Ros navigation功能包集源码详解(一) amcl_第3张图片
a).eig3(指头文件和源文件,下同)实现的是一个3x3对称矩阵的特征值与特征向量的计算,首先用Householder矩阵将矩阵变换为三对角矩阵,然后使用ql分解迭代计算
b).pf_kdtree定义了一个kdtree以及维护方法来管理所有粒子
c).pf_pdf主要定义了一个从给定pdf中采样粒子的方法
d).pf_vector定义了三维列向量和三维矩阵和基本的运算方法
e).pf定义了粒子单元pf_sample_t,粒子集pf_sample_set_t,粒子滤波pf_t的数据类型,还有一个 pf_cluster_t表示粒子集的聚类信息,关键函数主要包含如下三个,分别对应粒子滤波中的运动更新,观测更新,重采样三个过程

// Update the filter with some new action
void pf_update_action(pf_t *pf, pf_action_model_fn_t action_fn, void *action_data);

// Update the filter with some new sensor observation
void pf_update_sensor(pf_t *pf, pf_sensor_model_fn_t sensor_fn, void *sensor_data);

// Resample the distribution
void pf_update_resample(pf_t *pf);

3.2 sensors

Ros navigation功能包集源码详解(一) amcl_第4张图片

a) amcl_sencor定义了基类,其成员函数都为虚函数,主要有如下两个,为两个派生类amcl_laser,amcl_odom提供统一接口

// Update the filter based on the action model.  Returns true if the filter
// has been updated.
public: virtual bool UpdateAction(pf_t *pf, AMCLSensorData *data);

// Update the filter based on the sensor model.  Returns true if the
// filter has been updated.
public: virtual bool UpdateSensor(pf_t *pf, AMCLSensorData *data);     

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

//amcl_laser.h   
// Laser sensor data   
class AMCLLaserData : public AMCLSensorData   
{
public:
AMCLLaserData () {ranges=NULL;};
virtual ~AMCLLaserData() {delete [] ranges;};
// Laser range data (range, bearing tuples)
public: int range_count;
public: double range_max;
public: double (*ranges)[2];
};
...
//amcl_laser.cpp
// Apply the laser sensor model
bool AMCLLaser::UpdateSensor(pf_t *pf, AMCLSensorData *data)
{
if (this->max_beams < 2)
return false;

// Apply the laser sensor model
if(this->model_type == LASER_MODEL_BEAM)
pf_update_sensor(pf, (pf_sensor_model_fn_t) BeamModel, data);
else if(this->model_type == LASER_MODEL_LIKELIHOOD_FIELD)
pf_update_sensor(pf, (pf_sensor_model_fn_t) LikelihoodFieldModel, data);  
else if(this->model_type == LASER_MODEL_LIKELIHOOD_FIELD_PROB)
pf_update_sensor(pf, (pf_sensor_model_fn_t) LikelihoodFieldModelProb, data);  
else
pf_update_sensor(pf, (pf_sensor_model_fn_t) BeamModel, data);

return true;
}
... 

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

public: void SetModelDiff(double alpha1, 
                        double alpha2, 
                        double alpha3, 
                        double alpha4);

public: void SetModelOmni(double alpha1, 
                        double alpha2, 
                        double alpha3, 
                        double alpha4,
                        double alpha5);

public: void SetModel( odom_model_t type,
                     double alpha1,
                     double alpha2,
                     double alpha3,
                     double alpha4,
                     double alpha5 = 0 );

// Update the filter based on the action model.  Returns true if the filter
// has been updated.
public: virtual bool UpdateAction(pf_t *pf, AMCLSensorData *data);

3.3 map

Ros navigation功能包集源码详解(一) amcl_第5张图片
a) map中主要定义了概率栅格地图的数据表示,如下所示

// Description for a single map cell.
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
{                                    
double origin_x, origin_y; // Map origin; the map is a viewport onto a conceptual larger map.
double scale;      // Map scale (m/cell) 
int size_x, size_y;  // Map dimensions (number of cells)  
map_cell_t *cells;  // The map data, stored as a grid
double max_occ_dist;  // Max distance at which we care about obstacles, 
                                      //for constructing likelihood field 
} map_t;

b) 此外还有四个源文件map_cspace.cpp,map_draw.c,map_range.c,map_store.c 不是主要内容(还没仔细读),暂不深究

4.amcl_node.cpp解读

4.1 类AmclNode的主要内容

 public:除构造函数和析构函数外,只有三个函数

void runFromBag(const std::string &in_bag_fn);//根据信息记录包来运行amcl,                                                                              //实际此函 数是把信息记录不断的发布出去
int process();  //这个好像只声明了但没定义          
void savePoseToServer();     //把位姿信息保存到参数服务器

 private:定义了很多变量,tf ,激光,里程,粒子滤波参数和变量等等,订阅者和发布者以及一些回调函数

4.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());//使boost::shared_ptr指向一个新的对象

 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();//销毁boost::shared_ptr指向的对象                               
 // To quote Morgan, Hooray!
 return(0);
}

  关键一句amcl_node_ptr.reset(new AmclNode());主要实现在于构造函数AmclNode()

4.3 构造函数AmclNode()

 主要包含如下内容:

//设置粒子滤波参数,运动模型参数,观测模型参数等各种
//消息发布:
1.pose_pub_ = nh_.advertise<geometry_msgs::PoseWithCovarianceStamped>("amcl_pose", 2, true);
        (位姿,均值方差表示的)
2.particlecloud_pub_ = nh_.advertise<geometry_msgs::PoseArray>("particlecloud", 2, true);
        (所有的粒子)
//服务: 
1.global_localization,(globalLocalizationCallback)
       这里的globalLocalization就是在地图范围内产生均匀分布的粒子
2.request_nomotion_update,(nomotionUpdateCallback)
        就是设置个标志,使amcl在无运动时也能够更新粒子
3.set_map,(setMapCallback)调用如下两个函数
    handleMapMessage(req.map);//进行地图转换 ,记录free space ,
    以及初始化pf_t 结构体,实例化运动模型(odom)和观测模型(laser)
    handleInitialPoseMessage(req.initial_pose);
    根据接收的初始位姿消息,在该位姿附近高斯采样重新生成粒子集
//消息订阅:
1.laser_scan_filter_,->registerCallback(boost::bind(&AmclNode::laserReceived,this, _1));
       回调函数laserReceived是粒子滤波主要过程,根据激光扫描数据更新粒子 
2.initial_pose_sub_ = nh_.subscribe("initialpose", 2, &AmclNode::initialPoseReceived, this);
        回调函数initialPoseReceived中实际调用handleInitialPoseMessage,在接收到的初始位姿附近采样生成    粒子集
//动态调参
    dsrv_ = new dynamic_reconfigure::Server<amcl::AMCLConfig>(ros::NodeHandle("~"));
    ...CallbackType cb = boost::bind(&AmclNode::reconfigureCB, this, _1, _2);
    dsrv_->setCallback(cb);
    动态配置参数服务,重新设置粒子滤波参数,设置运动模型参数,设置观测模型参数,订阅消息等,
    就是AmclNode构造函数里做的事情

  以上的最关键内容在回调函数laserReceived()

4.4 回调函数laserReceived()

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

4.5 amcl_node的重要内容大体如上,总结一下它的几个主要过程

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

参考资料:

  • http://wiki.ros.org/navigation
  • https://zhuanlan.zhihu.com/p/28137335
  • https://www.ncnynl.com/archives/201708/1911.html
  • https://blog.csdn.net/wangqiang319670/article/category/7418854
  • Learning ROS for Robotics Programming Second Edition
  • Probabilistic Robotics

* 注:欢迎指正,如内容有侵犯您的权益,请联系作者.

你可能感兴趣的:(ros,Navigation源码解读)