最近在看AMCL的程序,想知道launch文件中关于odom的那些配置参数是怎么运行的,看完后做点笔记。

1.AMCL的launch文件关于odom的参数配置列表(以下讨论的都是差速模型):

odom参数列表
参数 默认值 描述
odom_model_type diff 里程计运动模型,种类有diff、ommi等
odom_alpha1 0.2 机器人旋转分量中的旋转噪音
odom_alpha2 0.2 机器人平移分量中的旋转噪音
odom_alpha3 0.2 机器人平移分量中的平移噪音
odom_alpha4 0.2 机器人旋转分量中的平移噪音
odom_frame_id odom 里程计用于哪个坐标系
base_frame_id base_link 机器人底盘坐标系
global_frame_id map 定位系统的参考坐标系
tf_broadcast true 是否发布map和odom坐标系之间的转换

2.里程计模型

假如对AMCL算法不清楚第一眼看过去大概就会有点懵。然后就会去翻AMCL的算法原理,一看发现原来在《概率机器人》中文版一书上讲过,但书上没有特别直接,所以还是得看程序具体怎么写的。但程序虽然是最直接的,为了理解程序最好还是要从算法原理上有一点基础的认识,所以这一小节简单概括一下里程计模型以及其采样算法,算法伪代码可以参考《概率机器人》书上103页。

里程计diff模型,也就是最常见的差速模型,两平行轮作驱动,另外附加一个万向轮作支撑。在AMCL算法中将机器人的每一步的广义运动进行了拆解,拆解成包含三个动作的序列,即:a.在起始点旋转,转向终止点的方向;b.沿着该方向做直线运动到终止点;c.在终止点进行旋转,转到目标方向,其运动示意图如下所示。学过机器人学大的会问,平面上的刚体运动可以拆分为一个旋转和一个平移,为什么这个要做3次运动。这个忽略了一个大前提,就是平面上的刚体有3个自由度:x、y、yaw,但是差速模型机器人虽然可以抵达平面上的任意位置和姿态,但是并不具备3个自由度的概念。想一下,差速模型的机器人只能直行和转弯,并不能沿着驱动轮的轴线进行运动吧?而且这用做的一大好处是可以提取相对距离便于定位校核的。

里程记测距模型:运动拆分

其中,代表在起始点的旋转,代表着第二段平移过程,代表着在终止点的旋转。

3.里程计噪音

要讨论里程计噪音,首先来讨论一下怎么样里程记没有噪声,也就是说,里程计读轮子的转圈和机器人的位移是精确对应的。当然理论上和仿真中可以做到:首先假设轮子和地面之间不打滑、地面绝对平整且光滑、轮子绝对圆润且不变形、里程计和机器人轮轴以及机器人轮子之间没有摩擦和迟滞可以做到完全时间同步...等等。当然现实中做不到,所以在差速模型的里程记计算中需要对机器人进行噪音估计,因为我们读到的直很可能不是真值。

在AMCL中,里程计是作为状态预测器存在的,通过接受当前的控制信号,从上一帧机器人状态对这一帧机器人状态进行预测,并与当前观测所得结果对当前预测进行加权打分。所以通过输入和输出我们知道,里程计在AMCL中的作用就是根据当前控制信号更新上一帧的能表征机器人状态的粒子集合。在AMCL中关于odom的伪代码如下所示:

AMCL算法中里程计预测模型

4.AMCL代码分析

在AMCL中,关于里程计在算法中做机器人位姿估计更新的代码,主要在amcl_odom.cpp中。主要执行函数是:bool AMCLOdom::UpdateAction(pf_t *pf, AMCLSensorData *data),这个函数输入是机器人上一帧位姿粒子状态pf和当前传感器信息data,程序运行完成就返回bool类型的true。

首先它利用switch...case...方法辨别里程计的模型,

然后,当model_type的值是 ODOM_MODEL_DIFF类型时,进行相应的机器人位姿更新:

case ODOM_MODEL_DIFF:{// Implement sample_motion_odometry (Prob Rob p 136)double delta_rot1, delta_trans, delta_rot2;double delta_rot1_hat, delta_trans_hat, delta_rot2_hat;double delta_rot1_noise, delta_rot2_noise;// Avoid computing a bearing from two poses that are extremely near each// other (happens on in-place rotation).if(sqrt(ndata->delta.v[1]*ndata->delta.v[1] + ndata->delta.v[0]*ndata->delta.v[0]) < 0.01)delta_rot1 = 0.0;elsedelta_rot1 = angle_diff(atan2(ndata->delta.v[1], ndata->delta.v[0]),old_pose.v[2]);delta_trans = sqrt(ndata->delta.v[0]*ndata->delta.v[0] +ndata->delta.v[1]*ndata->delta.v[1]);delta_rot2 = angle_diff(ndata->delta.v[2], delta_rot1);// We want to treat backward and forward motion symmetrically for the// noise model to be applied below.  The standard model seems to assume// forward motion.delta_rot1_noise = std::min(fabs(angle_diff(delta_rot1,0.0)),fabs(angle_diff(delta_rot1,M_PI)));delta_rot2_noise = std::min(fabs(angle_diff(delta_rot2,0.0)),fabs(angle_diff(delta_rot2,M_PI)));for (int i = 0; i < set->sample_count; i++){pf_sample_t* sample = set->samples + i;// Sample pose differences//double pf_ran_gaussian(double sigma)sigma。delta_rot1_hat = angle_diff(delta_rot1,pf_ran_gaussian(this->alpha1*delta_rot1_noise*delta_rot1_noise +this->alpha2*delta_trans*delta_trans));delta_trans_hat = delta_trans - pf_ran_gaussian(this->alpha3*delta_trans*delta_trans +this->alpha4*delta_rot1_noise*delta_rot1_noise +this->alpha4*delta_rot2_noise*delta_rot2_noise);delta_rot2_hat = angle_diff(delta_rot2,pf_ran_gaussian(this->alpha1*delta_rot2_noise*delta_rot2_noise +this->alpha2*delta_trans*delta_trans));// Apply sampled update to particle posesample->pose.v[0] += delta_trans_hat * cos(sample->pose.v[2] + delta_rot1_hat);sample->pose.v[1] += delta_trans_hat * sin(sample->pose.v[2] + delta_rot1_hat);sample->pose.v[2] += delta_rot1_hat + delta_rot2_hat;}

首先一开始是定义一些变量,注意这些变量选用的都是double类型的。

    double delta_rot1, delta_trans, delta_rot2;double delta_rot1_hat, delta_trans_hat, delta_rot2_hat;double delta_rot1_noise, delta_rot2_noise;

然后就开始计算 

    if(sqrt(ndata->delta.v[1]*ndata->delta.v[1] + ndata->delta.v[0]*ndata->delta.v[0]) < 0.01)delta_rot1 = 0.0;elsedelta_rot1 = angle_diff(atan2(ndata->delta.v[1], ndata->delta.v[0]),old_pose.v[2]);delta_trans = sqrt(ndata->delta.v[0]*ndata->delta.v[0] +ndata->delta.v[1]*ndata->delta.v[1]);delta_rot2 = angle_diff(ndata->delta.v[2], delta_rot1);

可以看到,相较于于书上,在计算时,多了个判定条件:

当 sqrt(ndata->delta.v[1]*ndata->delta.v[1] + ndata->delta.v[0]*ndata->delta.v[0]) < 0.01 时,也就是,注意,这里的单位是米,也就是说,以位移是否小于1cm作为判断条件,如果里程计量得机器人位移小于1cm,就置为0,其它的就和《概率机器人》一书中p103页一样。

其中:

表示机器人两帧间x方向上的位移,表示机器人两帧间y方向上的位移,表示机器人两帧间yaw(偏航)方向上的位移。表示机器人上一帧的yaw偏航角度。

这是根据里程计的测量结果得到机器人的三段动作的估计值。之前也讲过,由于机器人运动过程中存在着噪音,所以测量值很可能不是机器人的真实值。这样就需要对机器人运动过程中的里程计的噪声进行估计,来得到能涵盖机器人位姿真实值的区域,可以用一定数量的粒子来表征这个区域,然后对这些粒子进行采样来尽可能多的逼近机器人的真实值。简而言之,上面用机器人呢差速运动模型对机器人位姿的估计我不全信,我要获取这个位姿邻近的值看是否能够更好地满足观测。

这个采样采多大?怎么采?这就涉及到我们前面的AMCL的launch文件中的odom_alpha设计参数了。

为了前后对称地估计旋转过程中的噪音,我们看到代码里面首先是取了两次旋转角的锐角

    delta_rot1_noise = std::min(fabs(angle_diff(delta_rot1,0.0)),fabs(angle_diff(delta_rot1,M_PI)));delta_rot2_noise = std::min(fabs(angle_diff(delta_rot2,0.0)),fabs(angle_diff(delta_rot2,M_PI)));

然后将这两个角度带入pf_ran_gaussian()进行高斯采样,也就是书103页的sample()函数。书中没有具体写sample()函数的实现,但是pf_pdf.c代码中对这函数进行了解释:从零平均高斯分布中随机抽取,带有标准差sigma。

// Draw randomly from a zero-mean Gaussian distribution, with standard
// deviation sigma.
// We use the polar form of the Box-Muller transformation, explained here:
//   http://www.taygeta.com/random/gaussian.html
double pf_ran_gaussian(double sigma)
{double x1, x2, w, r;do{do { r = drand48(); } while (r==0.0);x1 = 2.0 * r - 1.0;do { r = drand48(); } while (r==0.0);x2 = 2.0 * r - 1.0;w = x1*x1 + x2*x2;} while(w > 1.0 || w==0.0);return(sigma * x2 * sqrt(-2.0*log(w)/w));
}

这里主要是用到了一个叫Box-Muller变换的东西。

Box-Muller变换

Box-Muller变换是通过服从均匀分布的随机变量,来构建服从高斯分布的随机变量的一种方法。也就是说,选取两个服从[0,1]上均匀分布的x1,x2,则要使y1、y2满足均值为0,方差为1的高斯分布,需使得:

具体证明过程有兴趣可以去看帅帅Go的博客:https://blog.csdn.net/weixin_41793877/article/details/84700875

这里讲一下它的应用。很明显,这个高斯随机数产生的方式比较繁琐,需要调用三角函数、sqrt()函数和ln()函数,这使得它的运行速度非常慢。其次,可以看得到,当x1接近0的时候,ln()函数将会给映射带来数值不稳定,而x1接近于0是可以接受的。最关键的是,y1、y2位于(0,1]区间,属于伪随机数。

那么,怎么样才能避免这些呢?数学直觉告诉我们,当有三角函数存在时,普通坐标系下搞不定可以放到极坐标形式进行计算。同样假设两个服从[-1,1]上均匀分布的x1,x2,,但是令:

那么,根据标准式,Box-Muller的极坐标式可以表示为:

其中,w在(0,1]范围内。y1和y2服从N(0,1)随机数。代码里面取的是y2。如果前面乘sigma,则表示N(0,sigma)分布的随机数。

根据Box-Muller高斯随机采样方法,其位姿估计代码如下所示:

      delta_rot1_hat = angle_diff(delta_rot1,pf_ran_gaussian(this->alpha1*delta_rot1_noise*delta_rot1_noise +this->alpha2*delta_trans*delta_trans));delta_trans_hat = delta_trans - pf_ran_gaussian(this->alpha3*delta_trans*delta_trans +this->alpha4*delta_rot1_noise*delta_rot1_noise +this->alpha4*delta_rot2_noise*delta_rot2_noise);delta_rot2_hat = angle_diff(delta_rot2,pf_ran_gaussian(this->alpha1*delta_rot2_noise*delta_rot2_noise +this->alpha2*delta_trans*delta_trans));

转换成算法公式就是:

注意到,这里的都是绝对量,分别和odom中两次旋转的弧度和一次平移的米的值进行相乘得到方差。公式中的单位主要根据代码中变量的赋值最后在AmclNode::getOdomPose()函数中看到数据单位初始的由来:

  x = odom_pose.pose.position.x;y = odom_pose.pose.position.y;yaw = tf2::getYaw(odom_pose.pose.orientation);

再次声明:平移的单位是米、偏航的单位是弧度。

在amcl的example的launch文件中,odom_alpha3也就是设置的比较大,并未设置成0.2反而设成0.8,这个得根据实际硬件和工况进行设置。这里用一张经典老图来表示一下不同方差下的正态分布,可以看到:方差越大,分布越分散,方差越小,分布越集中。

经典老图

那么,真实情况是怎么样呢?就拿的采样估算来说,假设机器人两帧之间移动了1cm,转动了0.01弧度(也就是0.5度),这个假设既便于计算也比较符合实际情况。根据公式:

对于正态分布:,我们取默认值:,则方差,我们可以绘制的正态分布图像。注意正态分布y轴代表概率,x轴代表当前取值。

alpha3=0.2,alpha4=0.2时的正态分布

可以看到,可能取值基本都分布于(-0.002,0.002)区间之间,这个偏差相较于0.01而言有20%的偏差。

同样可以改变的值,这次假设,则其分布图像如下所示:

alpha3=0.1,alpha4=0.1时的正态分布

肯定这样采样范围狭窄了不少,偏差大约只有10%。

假设,则:

alpha3=0.8,alpha4=0.2时的正态分布

采样范围相较于alpha都为0.2拓宽了不少。具体噪音估计参数效果需要根据实际情况调整,需要让机器人的真值时刻被包裹在粒子集合中,这样机器人位姿估计才能稳定收敛于真值。

最后一步当然就是对初始预测的修正,代码如下:

      sample->pose.v[0] += delta_trans_hat * cos(sample->pose.v[2] + delta_rot1_hat);sample->pose.v[1] += delta_trans_hat * sin(sample->pose.v[2] + delta_rot1_hat);sample->pose.v[2] += delta_rot1_hat + delta_rot2_hat;

这一步和书上完全一样,转换成公式就是:

注意,这里只是一次采样,也只是产生粒子群中的1个粒子,AMCL需要维持一定的粒子数目来表征机器人的位姿,所以需要像开头里的循环那样反复采集机器人位姿,并根据当前观测给该粒子加权重,添加进粒子群中进行后续优化。

至此,AMCL对里程计数据的处理就完成了。总的来说就是先进行基于当前数据的预测更新,然后进行高斯随机采样得到当前以当前数据为中心的位姿粒子群,最后进行观测赋予权值。

AMCL中odom的数据处理相关推荐

  1. AMCL中odom数据处理

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qq_17032807/article/ ...

  2. ROS中map、odom、base_link坐标系的理解和这三个坐标系在AMCL中的关系

    学了ROS快一年了,依旧对map坐标系.odom坐标系.base_link坐标系之间的关系不是很清晰,这段时间下定决心要捋清楚他们之间的关系. map坐标系:地图坐标系,是一个固定的坐标系: base ...

  3. 从Hadoop框架与MapReduce模式中谈海量数据处理(含淘宝技术架构)

    从hadoop框架与MapReduce模式中谈海量数据处理 前言 几周前,当我最初听到,以致后来初次接触Hadoop与MapReduce这两个东西,我便稍显兴奋,觉得它们很是神秘,而神秘的东西常能勾起 ...

  4. python数据处理常用函数_pytorch中的自定义数据处理详解

    pytorch在数据中采用Dataset的数据保存方式,需要继承data.Dataset类,如果需要自己处理数据的话,需要实现两个基本方法. :.getitem:返回一条数据或者一个样本,obj[in ...

  5. python编写交互界面查分app_Django项目中model的数据处理以及页面交互方法

    Django中Models是什么: 通常,一个Model对应一张数据表, Django中Models以类的形式表现, 它包含一些基本字段以及数据的一些行为 ORM: 对象关系映射(Object Rel ...

  6. python构造自定义数据包_pytorch中的自定义数据处理详解

    pytorch在数据中采用Dataset的数据保存方式,需要继承data.Dataset类,如果需要自己处理数据的话,需要实现两个基本方法. :.getitem:返回一条数据或者一个样本,obj[in ...

  7. 关于数据仓库中变化历史数据处理方式:全量表、快照表、拉链表

    关于数据仓库中变化历史数据处理方式:全量表.快照表.拉链表 一.全量表 二.快照表 三.拉链表 对于数据仓库中变化的历史数据存储,一般有三种方式:全量表.快照表.拉链表 一.全量表 直接全量导入,覆盖 ...

  8. AMCL代码详解(七)amcl中的kd-Tree

    1.kd-Tree基础 kd树(k-dimensional树的简称),是一种分割k维数据空间的数据结构,主要应用于多维空间关键数据的近邻查找(Nearest Neighbor)和近似最近邻查找(App ...

  9. AMCL代码详解(六)amcl中的重采样

    1.重采样判断 上一章讲述了amcl中如何根据激光观测更新粒子权重,当粒子更新完后amcl会需要根据程序判断是否需要进行重采样.这个判断在粒子观测更新权重后进行判断,代码在amcl_node.cpp中 ...

最新文章

  1. python爬取贴吧所有帖子-Python爬虫实例(一)爬取百度贴吧帖子中的图片
  2. .net下Selenium2使用方法总结
  3. 浅谈iptables防SYN Flood攻击和CC攻击
  4. C语言求最大公约数和最小公倍数的几种算法
  5. OpenShift 4 Hands-on Lab (11) 用户身份认证和资源访问限制
  6. 罗永浩“重新定义” 的 TNT 黄了?
  7. Linux学习笔记五:arm-2009q3交叉编译环境安装、U盘拷贝文件到开发板
  8. java连接DB2数据库
  9. 关于C语言编程智能小车毕业设计,基于单片机的多功能智能小车设计-毕业设计论文.doc...
  10. C-LODOP打印插件使用
  11. Phonegap 之 iOS银联在线支付(js调用ios端银联支付控件)
  12. 嵌入式c语言小学期实验报告,小学期单片机实验报告.doc
  13. win10录屏_不需要第三方软件,看看WIN10自带的几个强大的截图、录屏工具
  14. 希腊自助游 - 米岛圣岛,迷失在蓝白之间
  15. 苹果系统是通过服务器推送消息,客户端技术:一文带你了解iOS消息推送机制
  16. MTK平台Camera图片的Exif信息
  17. 红手指云手机屏蔽方案
  18. android studio 官方虚拟机,Android Studio 移动虚拟机
  19. JavaScript将扁平化数据转为树形结构
  20. 词法分析——输入缓冲

热门文章

  1. 电脑上的日期从1980年到2099年
  2. 什么是视距传播,如何验证微波链路视线
  3. 干货!任务型对话中语言理解的鲁棒性测试 |清华刘劼西
  4. 如何一键修改CAD图纸底图颜色?
  5. 零信任的过去、现在和未来
  6. 琐事记 - 2015/10/28
  7. 实例讲解新GRE填空的变化
  8. IPv4地址--公网地址可以有多少
  9. python对excel指定数据提取并保存到另一excel表中(一)
  10. Android动态更改TextView的字体大小