核心课程 / SLAM 与自主导航 / EXP-04
EXP-04 · 第 2 章 · 综合性 · 8 学时

AMCL 自适应蒙特卡洛定位

实验 3 里小车导航前"大概知道自己在哪"(默认 0,0,0)。但真实场景下小车可能被搬动、地图和现实对不上。这节课解决"我到底在地图哪里"——用 AMCL 粒子滤波,让小车拿雷达扫一扫,就自己猜出精确位置。

难度 ⭐⭐⭐ · 8 学时 粒子滤波似然场 初始位姿/initialpose

01问题引入:里程计漂移与"被绑架"的车

两个真实场景把定位问题逼到台前:

1麦轮原地旋转打滑严重(航向漂移约 45%)——里程计说"转了 90°",实际可能只有 50°
2同学把小车从教室这头搬到那头("绑架机器人")——上一秒的位姿全部作废
3地图与现实对不上,激光扫出来的墙落在"错误的地方",导航全线跑偏
4需要一个"拿眼前扫描与地图比对、不断自我纠偏"的定位器 —— AMCL
💡 一句话读懂这一课解决"我到底在地图哪里":用粒子滤波(预测 → 更新 → 重采样三步循环),让小车拿雷达扫一圈就自己猜出精确位置。就像黑店里的手电筒:地图是店铺布局,激光扫描是光斑,猜对了位置,光斑才和墙重合。

02原理通俗讲解:粒子滤波三步

🎲 蒙眼人猜位置:撒一把"假想我"在户型图上同时撒 500 个"假想的我"(粒子,每个都自带一组位置+朝向猜测)。预测:听到"向右转 10°、前进 1 步",500 个假想我一起挪(按运动模型加点抖动);更新(打分):拿当前雷达扫描和地图比对——"我"猜的位置若让扫到的墙和地图墙重合就加分,对不上就扣分;重采样:按分数抽签复制——高分的多生几个、低分的被淘汰。循环三步,假想我越挤越紧,最后那团"人堆"的中心 = 小车真实位置。
AMCL 参数本项目值通俗解释
min_particles / max_particles500 / 2000撒多少个"假想我"(KLD 自适应增减:迷茫时多撒、笃定时少撒)
alpha1 ~ alpha50.2运动有多"善变":打滑严重的车调大,让假想我敢散开
laser_model_typelikelihood_field似然场打分:先给地图算好"离墙距离场",扫一扫按光斑重合度打分,比 beam 模型快得多
update_min_a / update_min_d0.15 rad / 0.15 m每转 8.6° 或每走 15 cm 才重新打分一次——省算力,别每帧都算
laser_max_range / min_range8.0 / 0.10 m只信这个距离窗口内的扫描点(比雷达物理量程 12 m 更保守),太近的反光噪点直接弃
set_initial_pose / initial_posetrue / (0,0,0)启动时直接按 initial_pose 撒粒子;设为 false 则等外部发 /initialpose 再开始
recovery_alpha_fast / slow0.0 / 0.0随机粒子注入(=0 关闭):开成如 0.001 后,粒子会周期性"炸开重撒",用来救被绑架的车
自适应
KLD 采样

分布复杂(走廊对称、多房间)时自动加粒子,简单场景自动减——"迷茫就多猜几个",上下限由 min/max_particles 卡住。

输出
/particle_cloud + /amcl_pose

粒子云可视化 = 团的松紧直接反映"信心";amcl_pose 是带协方差的最终位姿。

假收敛
对称环境的陷阱

长走廊两头长得一样,粒子可能在"错误的那头"抱团——收敛 ≠ 收敛对了,换特征明显的房间再验。

双坐标系
AMCL 只发 map→odom

它把"纠正量"写进 map→odom 这根 TF,odom→base_footprint 仍由 EKF 平滑输出——所以定位校正时车不会抽搐。

03关联知识点

知识点 1
里程计 vs 雷达定位

里程计"记得自己走了多远"但会漂;雷达"看得见墙在哪"但只看眼前——两者互补正是滤波的思想。

知识点 2
map→odom→base TF

AMCL 负责发 map→odom 的修正量:定位跳变 = 这根 TF 被纠正,odom 链保持平滑小车才不抽搐。

知识点 3
初始位姿两法

RViz「2D Pose Estimate」手动给(发 /initialpose);或配置 set_initial_pose: true,让节点启动时就用 initial_pose (0,0,0) 起播。

知识点 4
/initialpose 话题

RViz 按钮发的就是它(带 6×6 协方差的 PoseWithCovarianceStamped)——程序化重定位可直接 publish 到这,协方差写大表示"我很不确定"。

知识点 5
收敛判定

粒子团半径 < 0.3 m、且激光点与地图墙线重合 = 收敛;散成一地 = 还没定位上。

知识点 6
绑架机器人问题

车被搬走后旧信念全部失效:默认 recovery_alpha=0 不会自动重撒 → 手动重给 /initialpose,或把 recovery_alpha_fast 开成 0.001 让算法自己找。

04实验目标

  • 说清粒子滤波 预测 → 更新 → 重采样 三步及各自输入输出
  • 用 RViz 与命令行 两种方式下发初始位姿,观察 /initialpose 生效
  • 观察 /particle_cloud:从散乱 → 聚团(半径 < 0.3 m)的收敛全过程
  • 完成一次"绑架测试":搬动小车后重新定位,记录恢复秒数
  • 完成调优三组 A/B/C 对照(粒子数 2000 / 3000、alpha 0.5),写出结论

05实验环境与准备

项目要求
地图EXP-02 验收合格的 map_01(对称走廊类场景会撞上"假收敛",选特征明显房间)
软件nav2_bringup(amcl 节点已含在 navigation.launch.py 中)+ RViz Nav2 配置
参数文件amcl 参数段:min/max_particles 500/2000、alpha1~5=0.2、likelihood_field、set_initial_pose true
硬件小车 C1 满电、3 m × 3 m 场地、桌面标记物(作参照)方便核对"真实位置"
对照工具秒表(记录绑架恢复时间)、纸笔(三组调优记录表)
💡 操作礼仪对小车轻拿轻放:搬动时先停导航目标,落地后重新给初始位姿;别在走廊两端这种"长得一样"的地方考它。

06动画讲解:粒子云收敛

左:初始位姿不确定,粒子(绿点)撒满一片;随小车移动与扫描比对,粒子在"真值"处越挤越紧,最终聚成一团 —— 团心即定位结果(青色箭头)。

粒子滤波:散乱猜测 → 打分重采样 → 收敛聚团 地图 = 已知布局 真实位置 amcl_pose · 协方差骤降 每一帧扫描都在给每个粒子打分 预测(随运动整体挪)→ 更新(比对打分)→ 重采样(高分多生)· 5 秒收敛循环
动画 4-1|粒子云 5 秒从满屋散点收缩到真值附近:团越紧 = 协方差越小 = 定位越自信

07互动讲解:粒子数 × 传感器噪声实验台

调两个旋钮,看定位表现怎么变——太少粒子覆盖不住真值,噪声太大分数全是干扰:

粒子滤波调参台 · MCL Lab
// 选择一种工况,观察收敛判定与建议 基线:min 500 / max 2000 · alpha 0.2 · likelihood_field · 团半径 < 0.3m 为收敛
调优组改什么预期现象
A 组max_particles 2000 → 3000复杂房间收敛更稳,CPU 升高;记录收敛秒数
B 组保持 3000,房间换对称走廊观察假收敛:团很紧但位置错——特征决定上限
C 组alpha1 0.2 → 0.5粒子更敢散开,动态/打滑场景恢复更快,代价是平时抖一点

08实操步骤:给初始位姿并观察收敛

1启动导航栈(AMCL 随 navigation.launch 起播,set_initial_pose 默认给 0,0,0)
2RViz 点「2D Pose Estimate」,在小车真实位置按下并拖出朝向箭头——看绿粒子被"吸"拢
3遥控走一段再停下,观察收敛稳定性;最后做绑架测试并计时
# ① 启动导航(AMCL 随 localization.launch 起播,参数 set_initial_pose=true)
ros2 launch car1_navigation navigation.launch.py map:=map_01

# ② 观察粒子云与位姿话题(另开终端;注意话题名是 particle_cloud,没有下划线连写)
ros2 topic echo /particle_cloud --once
ros2 topic echo /amcl_pose --once

# ③ 命令行下发初始位姿(与 RViz 按钮等价)——朝向四元数 w=1 即 yaw 0
ros2 topic pub -1 /initialpose geometry_msgs/msg/PoseWithCovarianceStamped \
  "{header: {frame_id: 'map'}, pose: {pose: {position: {x: 0.5, y: 0.3}, orientation: {w: 1.0}}}}"

# ④ 绑架测试:手动把车搬到 2m 外,重新 pub /initialpose,秒表记录恢复时间
#    对比两种做法:① 手动重给位姿  ② 开 recovery_alpha_fast 0.001 让算法自己找回

# ⑤ 调优后必须整栈重启(Ctrl+C 再 launch),别热改参数
🔍 验证小技巧在地面贴一张醒目胶带作"真实原点",让小车正对它站好再给初始位姿——之后 /amcl_pose 的读数应与摆放值一致,误差大就重给。判收敛看 /particle_cloud 散点是否收缩成一小团(半径 < 0.3 m),而不是看单个箭头。

09重点与易错点

⚠️ 重点定位出错时先回查两件事:① 初始位姿给准了吗(箭头方向 = 车头朝向,很容易给反 180°);② 粒子云收敛了吗(散成一地 = 没资格导航)。先位姿、后粒子、再参数。
⚠️ 常见坑现象 → 解法
初始位姿给反车头朝南箭头却朝北 → 重新按住拖方向;症状是"出发就拐弯撞墙"
粒子一锅粥不收敛团散、散点乱跳 → 地图特征弱/太对称,换个特征丰富处再给位姿
"收敛"了但位置错假收敛(对称走廊)→ 挪到有拐角家具的区域验证;必要时做绑架恢复测试
搬车后不恢复默认 recovery_alpha_fast/slow = 0.0 不会自动重撒 → 手动重给 /initialpose,或开 recovery_alpha_fast 0.001
粒子数盲目调大3000+ 未必更好:先确认地图与传感器没问题,KLD 已在 500~2000 间自适应
改参不生效AMCL 参数启动时读取 → Ctrl+C 整栈重启 navigation.launch
  • 粒子云从散乱到 聚团半径 < 0.3 m,激光点与地图墙线重合
  • RViz 与命令行 两种初始位姿方式都亲手用过
  • 绑架测试 恢复秒数有记录,A/B/C 三组调优表填写完整
  • 能解释 likelihood_field 为什么比 beam 模型快(先算好"离墙距离场",打分只查表)

10总结与思考

✅ 完成成果
车知道自己在哪了

两种方式给位姿、观察收敛、完成绑架测试与三组调优对照。

🧠 原理收获
滤波三步 + 自适应

预测/更新/重采样的循环本质;KLD"迷茫多撒、笃定少撒"。

🛠 技能收获
定位体检手法

/initialpose 发布、/particle_cloud 判读、协方差与团半径肉眼诊断。

❓ 排障思路
先位姿后参数

位姿给准 → 等收敛 → 仍不对再查地图特征与 alpha/粒子数。

📝 思考题 1为什么粒子需要"重采样"这一步?如果只保留"打分"不重采样,几百帧之后粒子群会发生什么?(提示:大部分粒子分数趋近 0 —— 穷光蛋繁殖不出后代)
📝 思考题 2长走廊两端一模一样,AMCL 可能"自信地错着"。请设计两个具体改进方向(提示从特征与机制下手:增加环境特征?让粒子周期性炸开?),并说明各自代价。