C/C++教程

(笔记)(6)AMCL包源码分析 | 粒子滤波器模型与pf文件夹(二)

本文主要是介绍(笔记)(6)AMCL包源码分析 | 粒子滤波器模型与pf文件夹(二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

上一讲介绍了粒子滤波器模型的相关理论以及pf.cpp中的几个关键函数,这一讲我们将对pf.cpp的代码进行详细分析。

先看pf.cpp引用的关键头文件,我们稍后再梳理这些头文件,现在先将pf.cpp的脉络梳理清楚。

#include "amcl/pf/pf.h"
#include "amcl/pf/pf_pdf.h"
#include "amcl/pf/pf_kdtree.h"  

在计算所需的粒子sample的数目的时候,这里使用KLD算法精髓里的直方图概念来表达,选用的是粒子sample集分布在直方图的k个bin里。

图1.直方图表示

//pf粒子滤波器是粒子sample集的来源,k是至少填充了一个粒子的直方图的位数
static int pf_resample_limit(pf_t *pf, int k);

1.创建一个粒子滤波器

创建一个粒子滤波器,输入为我们关心的粒子sample集的最小数目,最大数目, αslow , αfast ,粒子滤波器初始化模型,随机生成数据的函数。

// Create a new filter
pf_t *pf_alloc(int min_samples, int max_samples,
               double alpha_slow, double alpha_fast,
               pf_init_model_fn_t random_pose_fn, void *random_pose_data)

在函数内部对粒子滤波器,粒子sample集,粒子sample进行定义声明初始化。

{  
  int i, j;
  pf_t *pf;
  //粒子sample集
  pf_sample_set_t *set;
  //粒子sample
  pf_sample_t *sample;
  
  srand48(time(NULL));
  //根据粒子滤波器的类型创建pf
  pf = calloc(1, sizeof(pf_t));
  //对粒子滤波器的随机位姿生成模型进行初始化
  pf->random_pose_fn = random_pose_fn;
  pf->random_pose_data = random_pose_data;
  //粒子sample集的最小数目
  pf->min_samples = min_samples;
  //粒子sample集的最大数目
  pf->max_samples = max_samples;
  ...
}

接下来用到的几个参数:pop_err,pop_z,dist_threshold均与KLD算法有关。其中pop_error代表两个概率分布差的最大测度,pop_z用于确定样本数,基于参数sigma,代表标准正态分布上的1-sigma分位点。对典型值sigma,pop_z(1-sigma)的值在标准统计表里是现成的。dist_threshold为阈值。

//pop_error代表两个概率分布差的最大测度(真实分布与估计分布)
//[err] is the max error between the true distribution and the estimated
distribution.
pf->pop_err = 0.01;

//pop_z代表标准正态分布上的1-sigma分位点
//[z] is the upper standard normal quantile for (1 - p), where p is
the probability that the error on the estimated distrubition will be 
less than [err].
pf->pop_z = 3;
pf->dist_threshold = 0.5; 

叶子节点的数目不能高过粒子样本集的最大数目:

  // 叶子节点的数目不能高过粒子样本集的最大数目
  pf->limit_cache = calloc(max_samples, sizeof(int));

关于粒子滤波器的两个粒子sample集:current_set和set

 //将pf->current_set设置为0
 pf->current_set = 0;

 //使用一个for循环,遍历两次:从而刷新两个set
 for (j = 0; j < 2; j++)
  {
    //set是指针,这里使用j依次读取pf->sets的元素set
    set = pf->sets + j;
    //设置粒子集set的sample集最大数目
    set->sample_count = max_samples;
    //给粒子sample集set以粒子sample集的最大数目分配内存
    set->samples = calloc(max_samples, sizeof(pf_sample_t));
    
    //这里使用一个for循环遍历该粒子sample集set,从而读取到每一个sample对象
    for (i = 0; i < set->sample_count; i++)
    {
      //sample是指针,这里使用i依次读取set->samples的元素sample
      sample = set->samples + i;
      //对粒子sample的位姿和权重进行初始化
      sample->pose.v[0] = 0.0;
      sample->pose.v[1] = 0.0;
      sample->pose.v[2] = 0.0;
      sample->weight = 1.0 / max_samples;
    }

    // HACK: is 3 times max_samples enough?
    //粒子sample集set的kdtree数据结构,用三倍于粒子sample集的最大数目来分配内存:
    //实际上这里需要计算复杂度,很显然代码的作者并没有计算,而是简单粗暴分配三倍内存
    set->kdtree = pf_kdtree_alloc(3 * max_samples);
    //对粒子sample集set的簇cluster进行初始化
    set->cluster_count = 0;
    set->cluster_max_count = max_samples;
    //给粒子sample集set的簇cluster分配内存
    set->clusters = calloc(set->cluster_max_count, sizeof(pf_cluster_t));
    //对粒子sample集set的均值和协方差进行初始化
    set->mean = pf_vector_zero();
    set->cov = pf_matrix_zero();
  }

还有几个参数需要初始化,它们是 wslow , wfast , αslow , αfast 。

  pf->w_slow = 0.0;
  pf->w_fast = 0.0;

  pf->alpha_slow = alpha_slow;
  pf->alpha_fast = alpha_fast;

最后,将粒子sample集set收敛于0。

//set converged to 0
 pf_init_converged(pf);

至此,就完成了粒子滤波器的创建。

2.销毁一个粒子滤波器

关于释放一个粒子滤波器的内存,这里用到一个for循环,遍历两次,其实就是把粒子滤波器的两个粒子sample集set给依次释放内存了。而在粒子sample集set内部,先后将粒子sample集set的簇cluster占据的内存,粒子sample集set所使用的数据结构kdtree占据的内存,粒子sample集set的sample对象占据的内存释放,这就完成了一个粒子滤波器的销毁。

// Free an existing filter
void pf_free(pf_t *pf)
{
  int i;

  free(pf->limit_cache);
 //因为有两个set
  for (i = 0; i < 2; i++)
  {
    free(pf->sets[i].clusters);
    pf_kdtree_free(pf->sets[i].kdtree);
    free(pf->sets[i].samples);
  }
  free(pf);
  return;
}

3.初始化粒子滤波器

这里用了两种方法来初始化粒子滤波器,第一种方法是使用高斯模型(均值,协方差)来初始化滤波器,还有一种方法是使用模型来初始化。

先看第1种方法:

 // 使用高斯模型(均值,协方差)初始化滤波器
void pf_init(pf_t *pf, pf_vector_t mean, pf_matrix_t cov)
{
  int i;
  //创建粒子sample集set,粒子sample对象,概率密度函数pdf
  pf_sample_set_t *set;
  pf_sample_t *sample;
  pf_pdf_gaussian_t *pdf;
  
  //初始化粒子sample集set
  set = pf->sets + pf->current_set;

  //创建kdtree为了选择性采样
  // Create the kd tree for adaptive sampling 
  pf_kdtree_clear(set->kdtree);

  //粒子sample集set的sample数目设定
  set->sample_count = pf->max_samples;
  
  //使用高斯模型(均值,协方差)创建pdf
  pdf = pf_pdf_gaussian_alloc(mean, cov);
  
  //计算新的粒子sample对象的位姿
  //使用for循环遍历粒子sample集set的每个sample对象
  for (i = 0; i < set->sample_count; i++)
  {
    sample = set->samples + i;
    //对粒子sample的权重进行初始化
    sample->weight = 1.0 / pf->max_samples;
    //对粒子sample的位姿使用pdf模型产生
    sample->pose = pf_pdf_gaussian_sample(pdf);
    
    //以kdtree数据结构的方式添加/存储粒子sample 或者说以kdtree数据结构的方式表达直方图
    // Add sample to histogram(kdtree)
    pf_kdtree_insert(set->kdtree, sample->pose, sample->weight);
  }
  //对 w_{slow}  , w_{fast}  进行设置
  pf->w_slow = pf->w_fast = 0.0;
  
  //销毁释放pdf占用的内存
  pf_pdf_gaussian_free(pdf);
  
  //再次计算粒子sample集set的簇cluster的统计特性,输入为粒子滤波器pf,和set  
  // Re-compute cluster statistics
  pf_cluster_stats(pf, set); 
  
  //粒子sample集set收敛到0
  //set converged to 0
  pf_init_converged(pf);

  return;
}

然后是第2种方法:

//使用pf_init_model_fn_t模型初始化
void pf_init_model(pf_t *pf, pf_init_model_fn_t init_fn, void *init_data)
{
  int i;
  //创建粒子sample集set,粒子sample对象
  pf_sample_set_t *set;
  pf_sample_t *sample;

  //初始化粒子sample集set
  set = pf->sets + pf->current_set;

  // Create the kd tree for adaptive sampling 创建kdtree为了选择性采样
  pf_kdtree_clear(set->kdtree);

  set->sample_count = pf->max_samples;

  //计算新的粒子sample对象的位姿
  //使用for循环遍历粒子sample集set的每个sample对象
  // Compute the new sample poses
  for (i = 0; i < set->sample_count; i++)
  {
    sample = set->samples + i;
    sample->weight = 1.0 / pf->max_samples;
    //看这里!生成粒子位姿的方式不一样,不一样!
    //对粒子sample的位姿使用pf_init_model_fn_t模型生成
    sample->pose = (*init_fn) (init_data);

    //以kdtree数据结构的方式添加/存储粒子sample 或者说以kdtree数据结构的方式表达直方图
    // Add sample to histogram(kdtree)
    pf_kdtree_insert(set->kdtree, sample->pose, sample->weight);
  }
  //下面的都跟上面第1种方法一样啦,一样啦
  pf->w_slow = pf->w_fast = 0.0;

  // Re-compute cluster statistics
  pf_cluster_stats(pf, set);
  
  //set converged to 0
  pf_init_converged(pf);

  return;
}

4.粒子滤波器收敛

首先是粒子滤波器初始化收敛:

void pf_init_converged(pf_t *pf){
  pf_sample_set_t *set;
  set = pf->sets + pf->current_set;
 //就是这么简单,将converged标志符改成0
  set->converged = 0; 
  pf->converged = 0; 
}

其次是粒子滤波器更新收敛: 这里的关键在于dist_threshold,粒子的位姿pose在x,y方向上的值分别减去平均值mean_x,mean_y,得到的差值与dist_threshold比较,如果差值大于dist_threshold那就显示没收敛,粒子还处于发散的状态中。

int pf_update_converged(pf_t *pf)
{
  int i;
  //创建粒子sample集set,粒子sample对象
  pf_sample_set_t *set;
  pf_sample_t *sample;

  //初始化粒子sample集set
  set = pf->sets + pf->current_set;
  //初始化men_x,mean_y
  double mean_x = 0, mean_y = 0;

  //计算新的粒子sample对象的位姿
  //使用for循环遍历粒子sample集set的每个sample对象
  for (i = 0; i < set->sample_count; i++){
    sample = set->samples + i;
    //计算粒子sample对象位姿pose的x,y方向上的和
    mean_x += sample->pose.v[0];
    mean_y += sample->pose.v[1];
  }
   //计算粒子sample对象位姿pose的x,y方向上的均值mean
  mean_x /= set->sample_count;
  mean_y /= set->sample_count;

  //使用for循环遍历粒子sample集set的每个sample对象
  for (i = 0; i < set->sample_count; i++){
    sample = set->samples + i;

 //这里的关键在于dist_threshold,粒子的位姿pose在x,y方向上的值分别减去平均值mean_x,
 //mean_y,得到的结果与dist_threshold比较,如果结果大于dist_threshold那就显示没收敛,
 //粒子还处于发散的状态中,至少dist_threshold表示不满意!
    if(fabs(sample->pose.v[0] - mean_x) > pf->dist_threshold || 
       fabs(sample->pose.v[1] - mean_y) > pf->dist_threshold){
      set->converged = 0; //发散情况下,converged设置为0
      pf->converged = 0; //发散情况下,converged设置为0
      return 0;
    }
  }
  set->converged = 1; //收敛情况下,converged设置为1
  pf->converged = 1; //收敛情况下,converged设置为1
  return 1; 
}

5.粒子滤波器随运动和观测更新

关于原理部分请跳转至上一讲的“2.粒子滤波器与蒙特卡罗定位”。

Churlaaaaaaa:5.AMCL包源码分析 | 粒子滤波器模型与pf文件夹(一)2 赞同 · 0 评论文章

首先是根据运动来更新粒子滤波器,其实是更新粒子的位姿:

// Update the filter with some new action
void pf_update_action(pf_t *pf, pf_action_model_fn_t action_fn, void *action_data)
{
 
  //创建粒子sample集set
  pf_sample_set_t *set;

  //初始化粒子sample集set
  set = pf->sets + pf->current_set;
  //引入运动模型action_fn,举个例子,这里我选择差分模型:odom_diff!
  (*action_fn) (action_data, set);
  
  return;
}

然后是根据观测来更新粒子滤波器,其实是更新粒子的权重:

// 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)
{
  int i;
  //创建粒子sample集set,粒子sample对象
  pf_sample_set_t *set;
  pf_sample_t *sample;

  //想想看这个total是干什么的呢?
  double total;

  //初始化粒子sample集set
  set = pf->sets + pf->current_set;

  //引入sensor_fn,这里我选择似然域模型:likelihood_filed 来计算粒子sample集set的权重
  total = (*sensor_fn) (sensor_data, set);
  
  //又一个重要参数n_eff
  set->n_effective = 0;
  
  //如果呢,观测模型计算出的粒子sample集set的权值(或者说概率probability)大于0,那么:
  if (total > 0.0)
  {
    //粒子sample集sample对象的权重归一化,设置w_avg
    // Normalize weights
    double w_avg=0.0;

    //使用for循环遍历粒子sample集set的每个sample对象
    for (i = 0; i < set->sample_count; i++)
    {
     
      sample = set->samples + i;
     //利用粒子sample集sample对象的权重对w_avg赋值
      w_avg += sample->weight;
     //粒子集sample对象的权重归一化
      sample->weight /= total;
     //利用粒子sample集sample对象的权重的平方对粒子集set的n_eff参数赋值
      set->n_effective += sample->weight*sample->weight;
    }
    // Update running averages of likelihood of samples (Prob Rob p258)
    //将w_avg除以粒子集sample数目,得到新的w_avg
    w_avg /= set->sample_count;
    
    //重新刷新w_slow的值,怎么刷新呢?
    //如果w_slow等于0,那么将w_avg的值赋给它
    if(pf->w_slow == 0.0)
      pf->w_slow = w_avg;
    //如果w_slow不等于0,那么它将获得alpha_slow*(w_avg-w_slow)
    else
      pf->w_slow += pf->alpha_slow * (w_avg - pf->w_slow);

    //重新刷新w_fast的值,怎么刷新呢?
    if(pf->w_fast == 0.0)
     //如果w_fast等于0,那么将w_avg的值赋给它
      pf->w_fast = w_avg;
    //如果w_fast不等于0,那么它将获得alpha_fast*(w_avg-w_fast)
    else
      pf->w_fast += pf->alpha_fast * (w_avg - pf->w_fast);
  }
 //如果呢,观测模型计算出的粒子sample集set的权值(或者说概率probability)不大于0,那该怎么办呢?
  else
  {
    // Handle zero total
    //使用for循环遍历粒子sample集set的每个sample对象,并将每个sample的权重设置得一模一样的
    //都是weight = 1.0 / sample_count;
    for (i = 0; i < set->sample_count; i++)
    {
      sample = set->samples + i;
      sample->weight = 1.0 / set->sample_count;
    }
  }
  最后,将粒子sample集set的n_eff参数设置为:1/n_eff
  set->n_effective = 1.0/set->n_effective;
  return;

}

6.复制粒子sample集set

// copy set a to set b
void copy_set(pf_sample_set_t* set_a, pf_sample_set_t* set_b)
{
  int i;
  double total;

  //创建粒子sample对象sample_a,sample_b
  pf_sample_t *sample_a, *sample_b;
  
  //清除粒子sample集set_b的kdtree
  // Clean set b's kdtree
  pf_kdtree_clear(set_b->kdtree);

  //从set_a那里复制sample来创建set_b
  // Copy samples from set a to create set b
  total = 0;
  //将set_b的sample数目初始化为0
  set_b->sample_count = 0;

  //使用for循环遍历粒子sample集set_a的每个sample对象,再copy到set_b里
  for(i = 0; i < set_a->sample_count; i++)
  {
    sample_b = set_b->samples + set_b->sample_count++;

    sample_a = set_a->samples + i;

    assert(sample_a->weight > 0);

    // Copy sample a to sample b:不仅仅是sample的位姿,权重也要一并复制
    sample_b->pose = sample_a->pose;
    sample_b->weight = sample_a->weight;
    
    //一直在累积粒子sample_b的权重
    total += sample_b->weight;
    
    //将粒子sample_b加入到直方图里,这里用kdtree的数据结构表示,结果就是插入到粒子sample集set_b里
    // Add sample to histogram(kdtree)
    pf_kdtree_insert(set_b->kdtree, sample_b->pose, sample_b->weight);
  }
   
  //将粒子sample集set_b的权重归一化
  // Normalize weights
  for (i = 0; i < set_b->sample_count; i++)
  {
    sample_b = set_b->samples + i;
    sample_b->weight /= total;
  }
  //将粒子sample集set_b的收敛状态设置得跟粒子sample集set_a一致
  set_b->converged = set_a->converged;
}

7.对分布进行重采样

// Resample the distribution
void pf_update_resample(pf_t *pf)
{
  int i;
  double total;

 //创建粒子sample集set_a,set_b
 //创建粒子sample对象sample_a,sample_b
  pf_sample_set_t *set_a, *set_b;
  pf_sample_t *sample_a, *sample_b;

  //double r,c,U;
  //int m;
  //double count_inv;
  double* c;

 //Attention:新的参数:w_diff!
  double w_diff;

 //初始化粒子sample集set_a,set_b,但是两个有点细微的区别
  set_a = pf->sets + pf->current_set;
  set_b = pf->sets + (pf->current_set + 1) % 2;

 //如果选择性采样标志不等于0,那么:
  if (pf->selective_resampling != 0)
  {
    //如果粒子sample集set_a的n_eff > 0.5 * (set_a的粒子sample数目),那么:
    if (set_a->n_effective > 0.5*(set_a->sample_count))
    {
      //将粒子集set_a复制到set_b
      // copy set a to b
      copy_set(set_a,set_b);

      //再次计算簇的统计特性,输入为pf和粒子sample集set_b
      // Re-compute cluster statistics
      pf_cluster_stats(pf, set_b);
      
      //使用新创建的粒子sample集,其实就是对current_set的值刷新一下
      // Use the newly created sample set
      pf->current_set = (pf->current_set + 1) % 2;
      return;
    }
  }

  //结束选择性采样标识的判断,下面我们进入重头戏部分。这里弃用了低方差采样,改用传统的采样方法

  // Build up cumulative probability table for resampling.
  // TODO: Replace this with a more efficient procedure
  // (e.g., http://www.network-theory.co.uk/docs/gslref/GeneralDiscreteDistributions.html)
  //用粒子sample集set_a的粒子sample数目来创建c的内存
  c = (double*)malloc(sizeof(double)*(set_a->sample_count+1));
  c[0] = 0.0;
  //根据粒子sample集set_a的粒子sample数目,遍历,遍历,
  //从而将粒子sample集set_a的粒子sample对象的权重依次取出来,存在c里
  for(i=0;i<set_a->sample_count;i++)
    c[i+1] = c[i]+set_a->samples[i].weight;
  //创建kdtree为选择性采样做准备
  // Create the kd tree for adaptive sampling
  pf_kdtree_clear(set_b->kdtree);
  
  //从粒子sample集set_a中draw samples去创造粒子sample集set_b
  // Draw samples from set a to create set b.
  total = 0;
  //将粒子sample集set_b的sample数目初始化为0
  set_b->sample_count = 0;
  
  //w_diff = 1.0 - w_fast/w_slow;
  w_diff = 1.0 - pf->w_fast / pf->w_slow;
  if(w_diff < 0.0)
    w_diff = 0.0;//如果w_diff小于0,那就将w_diff置为0咯!
  //printf("w_diff: %9.6f\n", w_diff);
 
  //由于呢,KLD适应性采样不是那么容易用低方差采样实现,所以这里采用传统方法实现
  // Can't (easily) combine low-variance sampler with KLD adaptive
  // sampling, so we'll take the more traditional route.
  /*
  虽然这么说,代码的作者还是贴上了低方差采样原理的出处,当然是在《概率机器人》里了
  // Low-variance resampler, taken from Probabilistic Robotics, p110
  count_inv = 1.0/set_a->sample_count;
  r = drand48() * count_inv;
  c = set_a->samples[0].weight;
  i = 0;
  m = 0;
  */
  
  //当粒子sample集set_b的sample数目小于粒子滤波器的sample的最大数目
  while(set_b->sample_count < pf->max_samples)
  {
    //逐步从粒子sample集的set_b中取出sample对象
    sample_b = set_b->samples + set_b->sample_count++;
 
    //当随机数小于w_diff;w_diff的赋值在上面实现过了
    if(drand48() < w_diff)
     //使用随机生成位姿的模型生成sample_b的位姿;真够传统的方法呀,拍拍手。
      sample_b->pose = (pf->random_pose_fn)(pf->random_pose_data);

    //当随机数大于w_diff;那就复杂了,怎么生成sample_b的位姿呢?进入else吗?这里注释掉了
    低方差重采样的代码实现,谁也没跑过,摊摊我的小手,我也不知道。
    else
    {
     
      // Can't (easily) combine low-variance sampler with KLD adaptive
      // sampling, so we'll take the more traditional route.
      /*
      //低方差重采样实现,实际上没用哦
      // Low-variance resampler, taken from Probabilistic Robotics, p110
      U = r + m * count_inv;
      while(U>c)
      {
        i++;
        // Handle wrap-around by resetting counters and picking a new random
        // number
        if(i >= set_a->sample_count)
        {
          r = drand48() * count_inv;
          c = set_a->samples[0].weight;
          i = 0;
          m = 0;
          U = r + m * count_inv;
          continue;
        }
        c += set_a->samples[i].weight;
      }
      m++;
      */
      //简单采样器
      // Naive discrete event sampler
      double r;
      //生成随机数
      r = drand48();
      //遍历粒子sample集set_a,把c拉出来跟r做比较;
      for(i=0;i<set_a->sample_count;i++)
      {
        if((c[i] <= r) && (r < c[i+1]))
          break;
      }
      assert(i<set_a->sample_count);

      //遍历粒子sample集set_a,将sample_a取出来
      sample_a = set_a->samples + i;

      assert(sample_a->weight > 0);
      
      //把sample_a的位姿赋给sample_b
      // Add sample to list
      sample_b->pose = sample_a->pose;
    }
   //将sample_b的权重初始化为1
    sample_b->weight = 1.0;
   //计算sample_b所在set的权重和
    total += sample_b->weight;
   //把粒子sample_b添加到kdtree里,其实也就是添加到粒子sample集set_b里。
    // Add sample to histogram(kdtree)
    pf_kdtree_insert(set_b->kdtree, sample_b->pose, sample_b->weight);
    
   //检查看是否有足够的粒子sample了呢?
  //这里使用pf_resample_limit函数,输入为粒子sample集set_b的kdtree的叶子节点个数,这也代表
  //着直方图的k个bin。
    // See if we have enough samples yet
    if (set_b->sample_count > pf_resample_limit(pf, set_b->kdtree->leaf_count))
      break;
  }
  //重设w_slow和w_fast
  // Reset averages, to avoid spiraling off into complete randomness.
  if(w_diff > 0.0)
    pf->w_slow = pf->w_fast = 0.0;

  //fprintf(stderr, "\n\n");

  // 使用for循环,遍历粒子sample集set_b,将sample_b权重归一化
  for (i = 0; i < set_b->sample_count; i++)
  {
    sample_b = set_b->samples + i;
    sample_b->weight /= total;
  }
  
  // 重新计算粒子sample集set_b的簇的统计特性
  // Re-compute cluster statistics
  pf_cluster_stats(pf, set_b);

 //重新设置current_set的值 
 // Use the newly created sample set
  pf->current_set = (pf->current_set + 1) % 2; 
  //粒子滤波器更新收敛
  pf_update_converged(pf);
 //释放c的内存
  free(c);
  return;
}

8.计算所需的粒子sample数目

给定直方图的bins的数目k,计算所需的粒子sample的数目,这个可真难呀!

// Compute the required number of samples, given that there are k bins
// with samples in them. (跟historgram有关) 
//This is taken directly from Fox et al.
int pf_resample_limit(pf_t *pf, int k)
{
  double a, b, c, x;
  int n;
  
 //如果k超出边界,那就返回粒子sample的最大数目
  // Return max_samples in case k is outside expected range, this shouldn't
  // happen, but is added to prevent any runtime errors 约束 && k
  if (k < 1 || k > pf->max_samples)
      return pf->max_samples;

  //pf粒子滤波器 && limit_cache ,pop_err,pop_z;
  // Return value if cache is valid, which means value is non-zero positive 
  if (pf->limit_cache[k-1] > 0)
    return pf->limit_cache[k-1];
  //如果k等于1呢,那也是返回粒子sample的最大数目
  if (k == 1)
  {
    pf->limit_cache[k-1] = pf->max_samples;
    return pf->max_samples;
  }
  
  //这里用到直方图的k,粒子滤波器的pop_err,pop_z;
  a = 1;
  b = 2 / (9 * ((double) k - 1));
  c = sqrt(2 / (9 * ((double) k - 1))) * pf->pop_z;
  x = a - b + c;
  //这里计算得出n
  n = (int) ceil((k - 1) / (2 * pf->pop_err) * x * x * x);
  //如果n小于粒子sample的最小数目,那么返回粒子sample的最小数目
  if (n < pf->min_samples)
  {
    pf->limit_cache[k-1] = pf->min_samples;
    return pf->min_samples;
  }
  //如果n大于粒子sample的最小数目,那么返回粒子sample的最大数目
  if (n > pf->max_samples)
  {
    pf->limit_cache[k-1] = pf->max_samples;
    return pf->max_samples;
  }
  //如果n很听话呢,那就好好利用它吧!
  pf->limit_cache[k-1] = n;
  return n;
}

9.为一个粒子sample集重新计算其簇的均值/协方差进而得出粒子集合的均值/协方差

// Re-compute the cluster statistics for a sample set
void pf_cluster_stats(pf_t *pf, pf_sample_set_t *set)
{
  int i, j, k, cidx;
  pf_sample_t *sample;
  pf_cluster_t *cluster;
  
  // Workspace
  double m[4], c[2][2];
  size_t count;
  double weight;

  // Cluster the samples:聚集粒子sample成簇
  pf_kdtree_cluster(set->kdtree);
  
  // Initialize cluster stats:初始化簇的数目
  set->cluster_count = 0;
  //根据粒子sample集set的簇的最大数目,逐步遍历每个簇
  for (i = 0; i < set->cluster_max_count; i++)
  {
    cluster = set->clusters + i;
    cluster->count = 0;
    cluster->weight = 0;
    cluster->mean = pf_vector_zero();
    cluster->cov = pf_matrix_zero();

    for (j = 0; j < 4; j++)
      cluster->m[j] = 0.0;
    for (j = 0; j < 2; j++)
      for (k = 0; k < 2; k++)
        cluster->c[j][k] = 0.0;
  }
  //初始化滤波器的统计特性
  // Initialize overall filter stats
  count = 0;
  weight = 0.0;
  //粒子sample集的均值和协方差初始化
  set->mean = pf_vector_zero();
  set->cov = pf_matrix_zero();
  for (j = 0; j < 4; j++)
    m[j] = 0.0;
  for (j = 0; j < 2; j++)
    for (k = 0; k < 2; k++)
      c[j][k] = 0.0;
  //计算簇的统计特性
  // Compute cluster stats
  //遍历set的所有sample对象
  for (i = 0; i < set->sample_count; i++)
  {
    sample = set->samples + i;

    //printf("%d %f %f %f\n", i, sample->pose.v[0], sample->pose.v[1], sample->pose.v[2]);
    //获得这个sample对象的簇标签
    // Get the cluster label for this sample
    cidx = pf_kdtree_get_cluster(set->kdtree, sample->pose);
    assert(cidx >= 0);
    //根据簇的最大数目来判断:
    if (cidx >= set->cluster_max_count)
      continue;
    if (cidx + 1 > set->cluster_count)
      set->cluster_count = cidx + 1;
    //这里取出了簇对象
    cluster = set->clusters + cidx;
    //簇的数目加1,权重也接收了来自粒子sample对象的赋值
    cluster->count += 1;
    cluster->weight += sample->weight;
    //粒子sample对象的权重也在统计着
    count += 1;
    weight += sample->weight;
 
    //计算簇的均值
    // Compute mean
    cluster->m[0] += sample->weight * sample->pose.v[0];
    cluster->m[1] += sample->weight * sample->pose.v[1];
    cluster->m[2] += sample->weight * cos(sample->pose.v[2]);
    cluster->m[3] += sample->weight * sin(sample->pose.v[2]);

    //计算簇的均值的和:m[0],m[1],m[2],m[3]
    m[0] += sample->weight * sample->pose.v[0];
    m[1] += sample->weight * sample->pose.v[1];
    m[2] += sample->weight * cos(sample->pose.v[2]);
    m[3] += sample->weight * sin(sample->pose.v[2]);
    
    //计算簇的协方差,先用双重for循环实现一个遍历
    // Compute covariance in linear components
    for (j = 0; j < 2; j++)
      for (k = 0; k < 2; k++)
      {
        //对簇的协方差进行赋值
        cluster->c[j][k] += sample->weight * sample->pose.v[j] * sample->pose.v[k];
        //计算簇的协方差的和:c[j][k]
        c[j][k] += sample->weight * sample->pose.v[j] * sample->pose.v[k];
      }
  }

  // Normalize:归一化;使用for循环遍历粒子sample集的簇,将均值和协方差归一化
  for (i = 0; i < set->cluster_count; i++)
  {
    cluster = set->clusters + i;
        
    cluster->mean.v[0] = cluster->m[0] / cluster->weight;
    cluster->mean.v[1] = cluster->m[1] / cluster->weight;
    cluster->mean.v[2] = atan2(cluster->m[3], cluster->m[2]);

    cluster->cov = pf_matrix_zero();

    // Covariance in linear components:线性部分的协方差
    for (j = 0; j < 2; j++)
      for (k = 0; k < 2; k++)
        cluster->cov.m[j][k] = cluster->c[j][k] / cluster->weight -
          cluster->mean.v[j] * cluster->mean.v[k];

    // Covariance in angular components; I think this is the correct
    // formula for circular statistics.:角度部分的协方差
    cluster->cov.m[2][2] = -2 * log(sqrt(cluster->m[2] * cluster->m[2] +
                                         cluster->m[3] * cluster->m[3]));

  }
  //计算粒子sample集set的均值
  // Compute overall filter stats
  set->mean.v[0] = m[0] / weight;
  set->mean.v[1] = m[1] / weight;
  set->mean.v[2] = atan2(m[3], m[2]);
  //计算粒子sample集set的线性方向的协方差
  // Covariance in linear components
  for (j = 0; j < 2; j++)
    for (k = 0; k < 2; k++)
      set->cov.m[j][k] = c[j][k] / weight - set->mean.v[j] * set->mean.v[k];
  //计算粒子sample集set的角度方向的协方差
  // Covariance in angular components; I think this is the correct
  // formula for circular statistics.
  set->cov.m[2][2] = -2 * log(sqrt(m[2] * m[2] + m[3] * m[3]));

  return;
}

10.设置选择性采样

void pf_set_selective_resampling(pf_t *pf, int selective_resampling)
{
  //这里将selective_resampling标识符简单设置一下即可
  pf->selective_resampling = selective_resampling;
}

11.计算均值和协方差

这里相比前面简直太简单了,就不加注释了。

// Compute the CEP statistics (mean and variance).
void pf_get_cep_stats(pf_t *pf, pf_vector_t *mean, double *var)
{
  int i;
  double mn, mx, my, mrr;
  pf_sample_set_t *set;
  pf_sample_t *sample;
  
  set = pf->sets + pf->current_set;

  mn = 0.0;
  mx = 0.0;
  my = 0.0;
  mrr = 0.0;
  
  for (i = 0; i < set->sample_count; i++)
  {
    sample = set->samples + i;

    mn += sample->weight;
    mx += sample->weight * sample->pose.v[0];
    my += sample->weight * sample->pose.v[1];
    mrr += sample->weight * sample->pose.v[0] * sample->pose.v[0];
    mrr += sample->weight * sample->pose.v[1] * sample->pose.v[1];
  }

  mean->v[0] = mx / mn;
  mean->v[1] = my / mn;
  mean->v[2] = 0.0;

  *var = mrr / mn - (mx * mx / (mn * mn) + my * my / (mn * mn));

  return;
}

12.获得一个粒子簇的统计特性:均值/协方差/权重

// Get the statistics for a particular cluster.
int pf_get_cluster_stats(pf_t *pf, int clabel, double *weight,
                         pf_vector_t *mean, pf_matrix_t *cov)
{
  pf_sample_set_t *set;
  pf_cluster_t *cluster;

  set = pf->sets + pf->current_set;

  if (clabel >= set->cluster_count)
    return 0;
 //注意这里的clabel哦!
  cluster = set->clusters + clabel;
 //这里取出簇的权重,均值和协方差
  *weight = cluster->weight;
  *mean = cluster->mean;
  *cov = cluster->cov;

  return 1;

好了,艰难地分析完了粒子滤波器所在的核心--pf.cpp,除了粒子滤波器这个磨人的小妖精,还有什么需要我们关心的呢?哎呀,搞了半天,kdtree是个啥呀?概率密度函数pdf咋生成的啊?这个协方差矩阵咋求解啊?怎么看到了eigen-descomposition(特征值分解)呢?那又怎么分解呢?

 

转自:6.AMCL包源码分析 | 粒子滤波器模型与pf文件夹(二) - 知乎 (zhihu.com)

 

这篇关于(笔记)(6)AMCL包源码分析 | 粒子滤波器模型与pf文件夹(二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!