gmapping

本文详细介绍了GMapping算法中的粒子权重计算、权重传播机制、重采样策略以及地图保存过程。重点讲解了粒子权重如何反映位姿质量,以及重采样如何根据权重分布进行,展示了算法的关键步骤和技术细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

欢迎访问我的博客首页


1.权重


  粒子权重 Particle::weight 是 double 类型。从函数 GridSlamProcessor::init 中可以看出,刚创建的粒子,其权重被设置为 0。然后粒子的权重就在函数 GridSlamProcessor::processScan 中变化。我们把与粒子权重有关的代码列出如下。

bool GridSlamProcessor::processScan(const RangeReading &reading, int adaptParticles) {
	scanMatch(plainReading);
	updateTreeWeights(false);
	resample(plainReading, adaptParticles, reading_copy);
}

  函数 GridSlamProcessor::scanMatch 负责权重的来源。它把调用函数 ScanMatcher::likelihoodAndScore 计算的似然度加到粒子的权重,而这个似然度代表的是粒子所存位姿的精确度。所以我们可以知道,粒子的权重代表了粒子所存位姿的好坏,权重越高,位姿越好。

  函数 GridSlamProcessor::updateTreeWeights 负责权重的传播。传播前,它先对粒子的权重归一化。粒子的权重仅有相对意义,即,用于区分粒子的相对好坏。而归一化不改变权重的相对大小。

void GridSlamProcessor::updateTreeWeights(bool weightsAlreadyNormalized) {
    if (!weightsAlreadyNormalized) {
        normalize();
    }
    resetTree();
    propagateWeights();
}

double propagateWeight(GridSlamProcessor::TNode *n, double weight) {
    // 如果已经到达根结点,停止传播。
    if (!n)
        return weight;
    double w = 0;
    // 已经有这么多子结点传播来 acc 权重。
    n->visitCounter++;
    // 父结点的 acc 权重是其所有子结点的 acc 权重之和。
    n->accWeight += weight;
    // 所有子结点都已传播来 acc 权重。
    if (n->visitCounter == n->childs) {
        w = propagateWeight(n->parent, n->accWeight);
    }
    assert(n->visitCounter <= n->childs);
    // 一棵轨迹树根结点的 acc 权重。
    return w;
}

函数 GridSlamProcessor::resetTree 重置权重传播时用到的变量,其中一个是 accWeight。accWeight 也称为权重,但它仅用于权重传播过程中。我们用下图表示函数 propagateWeight 的权重传播。图中所示是粒子总数为 n=8 时,权重从叶结点向上传播。从图中可以看出,权重传播很简单:父节点的权重是其所有子结点的权重之和。
在这里插入图片描述
  函数 GridSlamProcessor::resample 负责权重的利用,即权重用于粒子重采样。上图中,有子结点的结点就是之前重采样时被采样到的点。可以看出,权重传播之后,每一代所有被采样的粒子的权重之和都是 w 1 + w 2 + . . . + w 8 = 1 w_1+w_2+...+w_8=1 w1+w2+...+w8=1。gmapping 建图结束后,会得到 n=8 条从根节点到叶结点的路径,我们把每条路径上的这些权重相加作为这条路径的权重,则权重最大的路径就是我们想要的地图(或称为轨迹),因为这个路径上的所有结点总体的位姿最好。

2.重采样


  重采样的频率决定着精度和效率。gmapping 的重采样策略如下。

inline bool GridSlamProcessor::resample(const double *plainReading, int adaptSize, const RangeReading *reading) {
    // 2.重采样:权重分散性 m_neff < 0.5 * 粒子数量。
    if (m_neff < m_resampleThreshold * m_particles.size()) {
    	// 重采样并增加轨迹结点。
    } else {
    	// 仅增加轨迹结点。
    }
}

具体的重采样方法如下。

// 调用者 GridSlamProcessor::resample。
/*Implementation of the above stuff*/
template <class Particle, class Numeric>
std::vector<unsigned int> uniform_resampler<Particle, Numeric>::resampleIndexes(const std::vector<Particle> &particles, // 归一化化权重。
                                                                                int nparticles) const {                 // 采样后的粒子数量。
    // 1.计算所有粒子的 acc 权重之和 cweight(值为 1) 和粒子数量 n。
    // 第一个实参是 GridSlamProcessor::m_weights。它已被 GridSlamProcessor::normalize 归一化,即元素之和为 1。
    Numeric cweight = 0;
    // compute the cumulative weights
    unsigned int n = 0;
    for (typename std::vector<Particle>::const_iterator it = particles.begin(); it != particles.end(); ++it) {
        cweight += (Numeric)*it;
        n++;
    }
    // 采样后的粒子数量 n:如果设置了采样数量,就使用设置的值。
    if (nparticles > 0)
        n = nparticles;
    // 2.把归一化权重之和 cweight 等分成 n 段,每段长度为 interval。从第一段随机选个起点 target,每隔 interval 设置一个 target。
    // 2.1 计算段长和 target。
    // compute the interval
    Numeric interval = cweight / n;
    // compute the initial target weight
    Numeric target = interval * ::drand48(); // 随机数范围 [0, 1]。
    // 2.2 每隔 interval 采样一次。
    // compute the resampled indexes
    cweight = 0;
    // 采样后的样本在原粒子集合中的下标。
    std::vector<unsigned int> indexes(n);
    n = 0;
    unsigned int i = 0;
    for (typename std::vector<Particle>::const_iterator it = particles.begin(); it != particles.end(); ++it, ++i) {
        cweight += (Numeric)*it;
        while (cweight > target) {
            indexes[n++] = i;
            target += interval;
        }
    }
    // 3.返回采样后的样本在原粒子集合中的下标。
    return indexes;
}

  我们把代码分为三部分。第一部分统计所有粒子的 acc 权重之和 cweight 和粒子数量 n。这一步是多余的,因为 cweight 已被 GridSlamProcessor::normalize 归一化为 1,而粒子数量是固定的。第三部分返回采样结果,即,采样后的粒子在原粒子集合中的下标。

  第二部分,我们以粒子数量 n=8 为例说明。首先把长度 cweight=1 的线段等分成 n=8 段,如下图。每段长度 interval=1/8,然后在第一段 [0, 1/8] 内随机选择一个 target 点(图中的红点)。

在这里插入图片描述

接下来开始重采样。target 会从它被随机确定的起始位置开始,按固定值 interval=1/8 的步长向后走。gmapping 的重采样思想是优先选择权重大的粒子。以 target=0.3/8 为例,当 n=8 个粒子的权重分别为 [0.1/8, 0.4/8, 1.9/8, 0.3/8, 2.8/8, 0.2/8, 0.5/8, 1.8/8] 时,重采样过程如下表所示。

在这里插入图片描述

3.保存地图


  n 个粒子会创建 n 个地图。保存地图时,选择最优粒子对应的轨迹结点。地图会以话题名 map 发布出去。map_server 包中的 map_saver 结点(map_saver.cpp) 订阅这个话题并保存地图。

void SlamGMapping::updateMap(const sensor_msgs::LaserScan &scan) {
	GMapping::GridSlamProcessor::Particle best = gsp_->getParticles()[gsp_->getBestParticleIndex()];
	for (GMapping::GridSlamProcessor::TNode *n = best.node; n; n = n->parent) {
	}
	sst_.publish(map_.map); // 以话题名 map 发布。
}

4.参考


  1. 论文
  2. 源码,github。
  3. ros wiki
  4. GMapping漫谈,知乎专栏,王金戈,2022。
  5. GMapping的基本原理,无处不在的小土,高乙超。
  6. 粒子滤波,CSDN,2023。
  7. 粒子滤波,B 站,2020。
<think>好的,我现在需要回答用户关于Gmapping在ROS中的实现和使用方法的问题。首先,我应该回忆一下Gmapping的基本概念。Gmapping是基于粒子滤波的SLAM算法,主要用于将激光雷达数据转换为栅格地图。用户可能想知道如何在ROS中安装、配置和使用它。 接下来,我需要检查用户提供的引用内容,看看是否有相关引用可用。引用[3]提到ROS用于输入、输出和参数处理,代码紧密封装,可能和Gmapping的ROS实现有关。虽然引用中没有直接提到Gmapping,但可以推测Gmapping作为ROS中的常用SLAM包,其使用方法可能类似。 然后,我需要确保回答符合系统级指令:行内数学公式用$...$,独立公式用$$...$$,正确使用LaTeX,中文回答,生成相关问题,引用标识自然添加。例如,提到Gmapping的数学原理时,可能需要用公式,比如粒子滤波的权重更新公式:$w^{(i)} = w^{(i)} \cdot p(z_t | x_t^{(i)}, m_{t-1}^{(i)})$,但不确定是否需要,根据用户问题,可能更关注实现步骤而不是数学细节。 接下来,我需要组织回答结构。首先介绍Gmapping,然后安装步骤,使用方法,参数调整,最后注意事项。确保每个部分清晰,引用相关的地方添加引用标识。例如,在提到ROS依赖时,可以引用[3]。 生成的问题部分需要与Gmapping相关,可能涉及参数调优、与其他算法的比较、故障排除等。需要确保问题具体且有价值。 最后,检查LaTeX语法是否正确,是否有遗漏的引用,以及是否符合用户要求的结构。确保没有使用Markdown格式,所有代码用三个反引号包裹,并正确标注语言类型。</think>### Gmapping算法在ROS中的实现与使用方法 Gmapping是基于粒子滤波的Rao-Blackwellized SLAM算法,可将激光雷达数据实时转换为栅格地图。其核心公式可表示为: $$p(x_{1:t}, m | z_{1:t}, u_{1:t}) = p(m | x_{1:t}, z_{1:t}) \prod_{k=1}^t p(x_k | x_{k-1}, u_k)$$ 其中$x$表示位姿,$m$为地图,$z$为观测数据,$u$为里程计输入[^1]。 #### 一、安装步骤 1. 安装ROS依赖(以Noetic为例): ```bash sudo apt-get install ros-noetic-slam-gmapping ``` 2. 验证安装: ```bash rospack find gmapping ``` #### 二、基础使用方法 1. 启动文件配置示例: ```xml <launch> <node pkg="gmapping" type="slam_gmapping" name="slam_gmapping"> <param name="delta" value="0.05"/> <param name="maxUrange" value="5.0"/> <remap from="scan" to="/laser/scan"/> </node> </launch> ``` 2. 关键参数说明: - `particles`:粒子数(默认30) - `map_update_interval`:地图更新频率(默认5秒) - `linearUpdate`:平移触发更新的阈值(默认0.5m) - `angularUpdate`:旋转触发更新的阈值(默认0.25rad) #### 三、数据要求 1. 必须的ROS话题: - `/scan`(sensor_msgs/LaserScan) - `/tf`中的坐标变换(odom→base_link→laser) 2. 建议配置: ```bash rosrun tf static_transform_publisher 0 0 0 0 0 0 base_link laser 100 ``` #### 四、地图保存 使用map_server包保存生成的地图: ```bash rosrun map_server map_saver -f ~/mymap ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值