测量模型主要用来做scan-match。
光束模型比较古老,实际已经不怎么使用,它的缺陷比较明显:在杂乱的环境(非结构化环境clutter)中,输入的小变化会导致输出的大变化。amcl中实现了这个模型,但是可以选择用还是不用
似然场模型不仅适应于结构化和非结构化的环境,而且在任何环境中的期望值对位姿都是平滑的,还可以通过查表的得到栅格的似然度。
工具函数和类
1 2 3 4 5 6 7 8 9 10 11
| pf_vector_t pf_vector_coord_add(pf_vector_t a, pf_vector_t b) { pf_vector_t c;
c.v[0] = b.v[0] + a.v[0] * cos(b.v[2]) - a.v[1] * sin(b.v[2]); c.v[1] = b.v[1] + a.v[0] * sin(b.v[2]) + a.v[1] * cos(b.v[2]); c.v[2] = b.v[2] + a.v[2]; c.v[2] = atan2(sin(c.v[2]), cos(c.v[2])); return c; }
|
laserReceived 中的准备工作
这一段是紧接着laserReceived中运动模型的准备工作
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 68 69 70 71 72 73 74 75 76
| if(lasers_update_[laser_index]) { AMCLLaserData ldata; ldata.sensor = lasers_[laser_index]; ldata.range_count = laser_scan->ranges.size();
tf::Quaternion q; q.setRPY(0.0, 0.0, laser_scan->angle_min); tf::Stamped<tf::Quaternion> min_q(q, laser_scan->header.stamp, laser_scan->header.frame_id); q.setRPY(0.0, 0.0, laser_scan->angle_min + laser_scan->angle_increment为); tf::Stamped<tf::Quaternion> inc_q(q, laser_scan->header.stamp, laser_scan->header.frame_id); try { tf_->transformQuaternion(base_frame_id_, min_q, min_q); tf_->transformQuaternion(base_frame_id_, inc_q, inc_q); } catch(tf::TransformException& e) { ROS_WARN("Unable to transform min/max laser angles into base frame: %s", e.what()); return; } double angle_min = tf::getYaw(min_q); double angle_increment = tf::getYaw(inc_q) - angle_min;
angle_increment = fmod(angle_increment + 5*M_PI, 2*M_PI) - M_PI; ROS_DEBUG("Laser %d angles in base frame: min: %.3f inc: %.3f", laser_index, angle_min, angle_increment);
if(laser_max_range_ > 0.0) ldata.range_max = std::min(laser_scan->range_max, (float)laser_max_range_); else ldata.range_max = laser_scan->range_max; double range_min; if(laser_min_range_ > 0.0) range_min = std::max(laser_scan->range_min, (float)laser_min_range_); else range_min = laser_scan->range_min;
ldata.ranges = new double[ldata.range_count][2]; ROS_ASSERT(ldata.ranges); for(int i=0;i<ldata.range_count;i++) { if(laser_scan->ranges[i] <= range_min) ldata.ranges[i][0] = ldata.range_max; else ldata.ranges[i][0] = laser_scan->ranges[i]; ldata.ranges[i][1] = angle_min + (i * angle_increment); }
lasers_[laser_index]->UpdateSensor(pf_, (AMCLSensorData*)&ldata); lasers_update_[laser_index] = false;
pf_odom_pose_ = pose;
}
|
UpdateSensor函数
的调用过程:
1 2 3
| bool AMCLLaser::UpdateSensor(pf_t *pf, AMCLSensorData *data)
pf_update_sensor(pf, (pf_sensor_model_fn_t) LikelihoodFieldModel, data);
|
涉及两个函数:
LikelihoodFieldModel
似然域模型,
pf_update_sensor
粒子更新
LikelihoodFieldModel 函数
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| double AMCLLaser::LikelihoodFieldModel(AMCLLaserData *data, pf_sample_set_t* set) { AMCLLaser *self; int i, j, step; double z, pz; double p; double obs_range, obs_bearing; double total_weight; pf_sample_t *sample; pf_vector_t pose; pf_vector_t hit;
self = (AMCLLaser*) data->sensor; total_weight = 0.0;
for (j = 0; j < set->sample_count; j++) { sample = set->samples + j; pose = sample->pose;
pose = pf_vector_coord_add(self->laser_pose, pose); p = 1.0;
double z_hit_denom = 2 * self->sigma_hit * self->sigma_hit; double z_rand_mult = 1.0/data->range_max;
step = (data->range_count - 1) / (self->max_beams - 1); if(step < 1) step = 1;
for (i = 0; i < data->range_count; i += step) { obs_range = data->ranges[i][0]; obs_bearing = data->ranges[i][1];
if(obs_range >= data->range_max) continue; if(obs_range != obs_range) continue;
pz = 0.0; hit.v[0] = pose.v[0] + obs_range * cos(pose.v[2] + obs_bearing); hit.v[1] = pose.v[1] + obs_range * sin(pose.v[2] + obs_bearing);
int mi, mj; mi = MAP_GXWX(self->map, hit.v[0]); mj = MAP_GYWY(self->map, hit.v[1]); if(!MAP_VALID(self->map, mi, mj)) z = self->map->max_occ_dist; else z = self->map->cells[MAP_INDEX(self->map, mi, mj)].occ_dist;
pz += self->z_hit * exp(-(z * z) / z_hit_denom); pz += self->z_rand * z_rand_mult;
assert(pz <= 1.0); assert(pz >= 0.0); p += pz*pz*pz; } sample->weight *= p; total_weight += sample->weight; } return(total_weight); }
|
大致的思想是这样:
不解的地方:
测量模型的最后使用的公式是p += pz*pz*pz
,并不是书上的p *= pz
,怀疑3个pz对应 [x,y,θ]
sample->weight
在之前赋值为 1/max_particles
1 2 3
| #define MAP_GXWX(map, x) (floor( (x - map->origin_x) / map->scale + 0.5) + map->size_x / 2) #define MAP_GYWY(map, y) (floor( (y - map->origin_y) / map->scale + 0.5) + map->size_y / 2)
|
公式 step = (laser_range_count - 1) / (max_beams - 1);
:
因为时间问题,不对所有点进行似然计算,而是间隔选点,step就是步长.
lser_rang_count
可以用rostopic echo --noarr /scan
得到,max_beams
由自己设置. 如果step小于1,程序里会设置为1.
因为寻找最近障碍物是最耗时的,可以将似然域预先计算好,搜索时直接查表即可。我理解的查表是查激光点在查栅格地图的坐标。
似然域模型的最后重新确定了每个粒子的重要性权重sample->weight
pf_update_sensor 函数
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
| void pf_update_sensor(pf_t *pf, pf_sensor_model_fn_t sensor_fn, void *sensor_data) { int i; pf_sample_set_t *set; pf_sample_t *sample; double total;
set = pf->sets + pf->current_set;
total = (*sensor_fn) (sensor_data, set); if (total > 0.0) { double w_avg=0.0; for (i = 0; i < set->sample_count; i++) { sample = set->samples + i; w_avg += sample->weight; sample->weight /= total; } w_avg /= set->sample_count; if(pf->w_slow == 0.0) pf->w_slow = w_avg; else pf->w_slow += pf->alpha_slow * (w_avg - pf->w_slow); if(pf->w_fast == 0.0) pf->w_fast = w_avg; else pf->w_fast += pf->alpha_fast * (w_avg - pf->w_fast); } else { for (i = 0; i < set->sample_count; i++) { sample = set->samples + i; sample->weight = 1.0 / set->sample_count; } } return; }
|
代码比较简单,就是196页的AMCL算法的7~11行。 不解的地方是 sample->weight最后又除以了粒子总权重