在AMCL中,里程计是作为状态预测器存在的,通过接受当前的控制信号,从上一帧机器人状态对这一帧机器人状态进行预测,并与当前观测的结果对当前预测进行加权打分。所以通过输入和输出我们知道,里程计在AMCL中的作用就是根据当前控制信号更新上一帧的能表征机器人状态的粒子集合。
给机器人一个初始位姿,粒子才根据初始位姿进行初始化分布,才会进行更新(UpdateAction函数
),否则不会有amcl_pose
话题输出,而更新依赖于读取到传感器数据发生变化,如在amcl中里程计的数据变化等,也就是机器人的运动
工具函数和类
1 | /* 两个处理角度的函数,保证角度都在(-π,π] 的范围内 */ |
laserReceived中的准备工作
下面来看回调函数laserReceived
中的运动模型准备工作,我们需要的是odata
,它的类型就是上面的AMCLOdomData
,以delta成员为例,它的赋值顺序是: odata.delta <—- delta <—- pose(当前位姿)和pfodom_pose(上一次位姿)
1 | // 获得雷达数据时刻的odom坐标系中的坐标,赋值给pose |
到此就是算法的准备工作了, pf_
是粒子滤波器,在handleMapMessage
中创建和初始化
UpdateAction
接下来就是运动模型的算法了,对应《概率机器人》103页:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67// Apply the action model
bool AMCLOdom::UpdateAction(pf_t *pf, AMCLSensorData *data)
{
AMCLOdomData *ndata;
ndata = (AMCLOdomData*) data;
// Compute the new sample poses
pf_sample_set_t *set;
// 确立当前的粒子集合,我们要产生的是一堆有位姿的粒子
set = pf->sets + pf->current_set;
// 上一时刻的位姿
pf_vector_t old_pose = pf_vector_sub(ndata->pose, ndata->delta);
// 与103页的各个delta量对应
double delta_rot1, delta_trans, delta_rot2;
double delta_rot1_hat, delta_trans_hat, delta_rot2_hat;
double delta_rot1_noise, delta_rot2_noise;
// 相较于于书上,在计算delta _rot1时,多了个判定条件以位移是否小于1cm作为判断条件
// 如果里程计量得机器人位移小于1cm,delta _rot1就置为0
if(sqrt(ndata->delta.v[1]*ndata->delta.v[1] +
ndata->delta.v[0]*ndata->delta.v[0]) < 0.01)
delta_rot1 = 0.0;
else
delta_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)));
// 用一定数量的粒子来表征这个区域,然后对这些粒子进行采样来尽可能多的逼近机器人的真实值
// 下面还是书上的公式,我们需要的是sample
for (int i = 0; i < set->sample_count; i++)
{
// 指针形式, 会给set的samples都赋值
pf_sample_t* sample = set->samples + i;
// Sample pose differences,pf_ran_gaussian函数:从零平均高斯分布中随机抽取,带有标准差sigma
// 将这两个角度带入pf_ran_gaussian()进行高斯采样,也就是书103页的sample()函数。
// 对每个粒子,不同的信息就是高斯采样产生的,其他部分不变
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 pose, 对初始预测的修正
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;
}
}