什么?没有实机、不装 ROS,也能玩导航算法?![从 A* 到动态重规划:用可视化平台理解 ROS 2 / Nav2 全局规划与局部控制]

《什么?没有实机、不装 ROS,也能玩导航算法?!从 A* 到动态重规划:用可视化平台理解 ROS 2 / Nav2 全局规划与局部控制》


想学习 ROS 2 / Nav2 导航,是否一定要先准备机器人、激光雷达、仿真环境,再处理 TF、话题、参数和一长串依赖?如果目标是先看懂导航算法的核心逻辑,答案是否定的。路径搜索、局部轨迹、速度控制和动态重规划,本质上都有可以独立观察的输入、中间状态和输出。先把这些因果关系看明白,再进入 Gazebo 或真实机器人,学习成本会低很多。

本文会先解释全局规划与局部控制的分工,再梳理 BFS、Dijkstra、A*、Greedy、DWB 和教学版 TEB 的基本思想,最后带你使用开源的 ROS Navigation Algorithm Visual Learning Platform 完成静态搜索、局部控制和三组动态重规划实验。Windows x64 用户可以直接运行便携 EXE,不需要安装 ROS、Python 或 PyQt5;想阅读和修改算法的读者,也可以从源码启动。

读完并实际操作后,你应该能够回答这些问题:

  • A* 为什么通常比 Dijkstra 更聚焦,但不保证在所有地图上都按固定比例减少搜索节点?
  • Greedy 为什么可能更快接近目标,却不保证得到最短路径?
  • 全局路径、DWB 候选轨迹、TEB 弹性带和机器人已行驶轨迹有什么区别?
  • 障碍突然出现后,机器人为什么应该从当前位置重规划,而不是回到起点?
  • 无路可走时,控制器为什么有时逐步制动,有时需要立即停车?
  • 在平台中应该记录哪些指标,才能避免只凭动画效果判断算法优劣?

1. 先明确环境与学习边界

1.1 两条使用路线

普通读者最短的体验路线是下载发布版程序。当前 v1.0.0 面向 Windows 10/11 Intel 或 AMD 64 位电脑提供单文件便携 EXE 和 ZIP 目录包,程序已经包含 Qt 运行组件。

你的目标 推荐方式 需要准备什么
立即观察算法和动态场景 Windows x64 便携 EXE 不需要 ROS、Python、Git
在无 Python 的电脑上运行 EXE 或完整解压后的 ZIP Windows 10/11 x64
阅读、修改和调试算法 克隆源码运行 run_nav.py 64 位 Python 3.10~3.14
继续进入真实 Nav2 先完成本文实验,再查看官方文档 对应 ROS 2 发行版和仿真或硬件环境

便携版的“无需安装环境”是指不需要额外安装 ROS 2、Python、PyQt5、虚拟环境和编译工具,并不是说程序可以脱离操作系统运行。Windows ARM64 和 macOS 目前不是 v1.0.0 的官方发布目标。Linux 用户可以查看仓库中的 AppImage 或源码说明。

1.2 平台不是 Nav2 官方插件

这套平台是独立运行的 Python + PyQt5 教学模拟器。它不会启动 ROS 2 节点,不订阅 nav_msgs/msg/OccupancyGrid,不维护真实 TF,也不会向底盘发布 geometry_msgs/msg/Twist。平台中的地图、位姿和速度都属于教学模型,不能直接替代真实机器人的定位、感知和控制系统。

但这种简化恰好适合算法入门。真实导航系统中,定位漂移、传感器延迟、坐标变换、网络通信和硬件限制会同时影响结果。初学者很容易看到“机器人没走对”,却不知道问题来自规划、控制、定位还是地图。教学平台暂时拿掉这些外围变量,让你先观察算法本身:

教学平台中的对象 可以对应理解的 Nav2 概念 平台没有复刻的内容
二值栅格地图 静态地图或 costmap 分辨率、未知区域、分层代价和膨胀层
plan() 返回的坐标序列 Planner Server 生成的全局路径 Action、插件生命周期和完整消息接口
DWB / TEB 教学模型 局部轨迹生成与控制思想 生产级参数、传感器延迟和完整约束
running、waiting 等状态 导航执行与恢复语义 行为树、生命周期节点和任务级恢复

因此正确的学习方式不是把平台参数直接复制到真实机器人,而是先在这里看懂“输入变化为什么导致输出变化”,再到所使用的 ROS 2 / Nav2 版本中核对真实接口。

1.3 读图时先认清四类线

平台会同时显示多种路径和轨迹。它们看起来都像线,却不是同一个对象:

  • 绿色参考路径回答“从当前位置到目标,大方向经过哪些栅格”。
  • DWB 的灰色或红色短曲线表示尚未执行的候选预测,红色通常代表碰撞淘汰。
  • TEB 的粉色位姿点表示局部弹性带节点,不是机器人已经走过的位置。
  • 已行驶轨迹记录机器人实际积分后的历史,地图变化后仍应保留。

后文做实验时,先确认自己观察的是哪一类对象,再讨论算法效果。把参考路径误认为机器人真实轨迹,是学习局部规划时最常见的误区之一。

2. 从地图到速度:全局规划与局部控制如何协作

2.1 全局规划回答“从哪里走”

全局规划器接收地图、起点和目标点,输出一条跨越较大范围的可通行路径。平台使用二维栅格,每个位置写成 (row, col),邻居规则为上、下、左、右四连通,每移动一格的代价为 1。

一条全局路径可以抽象为:

(12, 2) -> (12, 3) -> (12, 4) -> ... -> (12, 38)

这串坐标解决的是路线选择问题,但它没有告诉机器人当前朝向是否正确,也没有给出下一控制周期应该使用多大的线速度和角速度。即使全局路径是最短的栅格折线,机器人也不能在拐角处瞬间改变朝向。

在真实 Nav2 中,全局路径通常由 Planner Server 通过规划插件生成。平台把复杂接口压缩成 plan(map_data, algorithm, start, goal),让读者直接观察 Open、Closed、最终路径和搜索统计。

2.2 局部控制回答“下一步怎么动”

差速机器人的简化状态可以写成 (row, col, theta),控制量是线速度 v 和角速度 omega。平台使用固定控制周期 CONTROL_DT = 0.16 s,根据当前速度更新机器人位姿:

middle_theta = theta + omega * dt / 2
row_new      = row + v * sin(middle_theta) * dt
col_new      = col + v * cos(middle_theta) * dt
theta_new    = normalize(theta + omega * dt)

局部控制器不会重新解决整张地图上的最短路问题,而是结合当前位姿、上一周期速度、全局参考路径和附近障碍,选择短时间内可执行的运动。DWB 更强调速度窗口中的候选采样与评分;TEB 的教学模型更强调局部位姿带、障碍约束、曲率和连续速度。

真实 Controller Server 还要处理机器人 footprint、局部 costmap、进度检查器、目标检查器和速度限制。平台只保留最适合观察的部分,但全局路径与局部控制的职责边界是一致的。

2.3 动态重规划把导航变成闭环

静态路径搜索通常只执行一次:生成地图、运行算法、输出路径。真实导航则不断读取地图和位姿,环境变化后还要重新计算路线:

读取地图和当前机器人位姿
    -> 生成或更新全局参考路径
    -> 局部控制器选择 v、omega
    -> 积分得到新位姿并记录已行驶轨迹
    -> 地图出现新的障碍或可用通道变化
    -> 从机器人当前位置重新规划
    -> 有路:替换参考路径并继续
    -> 无路:制动或急停,进入等待
    -> 通路恢复:按照原来的运行意图恢复

图中的实线是主要数据流,虚线表示地图和位姿反馈。机器人每移动一步,下一次控制都必须基于新的位姿;地图版本变化后,全局参考路径也可能失效。导航因此不是“规划一次然后照着走”,而是规划、控制、反馈和恢复组成的闭环。

2.4 为什么不能把机器人送回起点

假设机器人已经走了十米,前方突然封路。环境发生变化,但任务仍然是从当前状态到达原目标。合理行为是保留当前位姿、速度、步数和历史轨迹,把当前机器人位置作为临时起点重新运行全局规划。

平台中的 MapData.revision 用于表示地图版本。一次合法的障碍笔画无论改变一格还是多格,都只增加一次版本;左右局部控制器发现版本变化后,各自把连续位姿转换为当前栅格位置,再独立运行 A*。由于 DWB 和 TEB 在事件发生时可能已经走到略有不同的位置,它们的新参考路径不必逐点相同。

只有起点或终点改变时,导航任务本身才发生变化,此时完整重置更合理。这个区别能帮助你理解:障碍编辑是环境事件,修改起终点是任务事件,两者不应该使用相同的重置策略。

3. 四种全局搜索算法

3.1 先看共同问题

BFS、Dijkstra、A* 和 Greedy 都在解决图搜索问题。平台中的每个可通行栅格是一个节点,相邻可通行栅格之间存在边。算法从起点维护待处理的 Open 集合,把已经确定处理过的节点放入 Closed,并用父节点关系回溯最终路径。

四种算法的主要区别不是“能不能绕开障碍”,而是下一步优先扩展哪个节点:

算法 主要优先级 在当前平台中的典型特点
BFS 层数或步数 单位代价下逐层扩展并保证最少步数
Dijkstra 已走代价 g 不看目标方向,扩展范围通常更宽
A* g + h 用启发式把搜索集中到目标方向
Greedy h 只追求看起来更接近目标,不保证最短

3.2 BFS:像波纹一样逐层扩展

BFS 使用先进先出的思想,先访问距离起点一层的节点,再访问下一层。只要每条边代价相同,第一次到达目标时得到的就是最少步数路径。

平台为了统一搜索动画和返回结构,让 BFS 与 Dijkstra 都通过优先队列按累计步数 g 排序。在当前每步代价都为 1 的地图上,它们可能得到相同的路径长度和相似的扩展顺序。这不表示两种算法在一般情况下完全相同:一旦边具有不同权重,BFS 就不再适用于最小总代价问题,而 Dijkstra 仍然成立。

观察 BFS 时,重点看黄色 Open 前沿是否像波纹一样向外扩散,以及蓝色 Closed 区域怎样逐层增长。最终绿色路径只是搜索结果的一小部分,不能用路径长度反推算法实际搜索了多少节点。

3.3 Dijkstra:只相信已经付出的代价

Dijkstra 的优先级是从起点到当前节点的真实累计代价:

priority(n) = g(n)

它不关心目标在哪个方向,总是先处理当前已知代价最小的节点。在非负边权图中,这种策略可以保证最小总代价路径。代价是搜索可能向多个方向均匀展开,尤其在开阔地图中,很多最终不会进入路径的节点也会被处理。

当前平台是四连通单位代价栅格,所以 Dijkstra 的优势主要通过搜索过程体现,而不是通过与 BFS 不同的最终路径体现。后续如果把地面风险、坡度或区域代价加入地图,Dijkstra 的带权语义才会更明显。

3.4 A*:用启发式减少无关搜索

A* 同时考虑已经走过的代价和对目标剩余代价的估计:

f(n) = g(n) + h(n)
h(n) = abs(row - goal_row) + abs(col - goal_col)

平台使用曼哈顿距离作为 h。对于只允许上下左右移动、每步代价为 1 的栅格,它不会高估无障碍条件下的剩余步数,因此可以在保持最短路性质的同时,让搜索更倾向目标方向。

A* 并不意味着任何地图上都只扩展一条细线。遇到长墙、凹形障碍或多个等价通道时,它仍然需要探索。启发式减少多少节点取决于地图、起终点和并列优先级规则,不能笼统地说“A* 永远比 Dijkstra 快多少倍”。

平台的优先队列还加入了递增序号。当多个节点具有相同优先级时,算法按稳定的入队顺序取出节点,使同一地图、随机种子和算法产生可重复的动画。这对教学截图和对照实验很重要。

3.5 Greedy:方向感强,但可能走弯路

Greedy Best-First Search 只使用启发式:

priority(n) = h(n)

它总是优先处理看起来离目标最近的节点,因此经常很快冲向目标方向。如果地图中存在诱导性的死胡同或凹形障碍,Greedy 可能深入错误通道,再花较长路线绕回去。它可以扩展更少的节点,却不保证最终路径最短。

这正是平台保留 Greedy 的原因:它能直观说明“搜索看起来更快”“扩展节点更少”和“路径质量更好”是三个不同判断。做 A* 与 Greedy 对照时,应同时记录路径步数、Closed 数量和 Open 峰值。

3.6 Open、Closed 和最终路径怎样读

Open 表示已经发现但尚未正式处理的候选节点,Closed 表示已经扩展的节点,最终路径则通过父节点从目标回溯得到。三者回答的问题不同:

  • 路径步数描述最终路线长度。
  • Closed 数量近似描述算法已经完成的搜索工作。
  • Open 峰值表示某一时刻最多有多少候选等待处理。
  • 搜索动画形状展示启发式怎样改变扩展方向。

两条路径同样长,不代表搜索代价相同;Closed 更少,也不保证 Open 峰值一定更小。比较算法时必须固定地图、起点、终点和参数,否则统计数字没有可比性。

4. 用一个最小 A* 程序建立直觉

在打开完整平台前,可以先用 Python 标准库运行一个四连通 A*。这个例子只解决“从 S 到 G 找一条最短栅格路径”,不包含机器人朝向、速度、footprint 和动态地图。

在空目录创建 mini_astar.py:

from heapq import heappop, heappush

GRID = [
    "S....#....",
    ".###.#.##.",
    ".....#....",
    ".#####.##.",
    ".........G",
]


def find(symbol):
    for row, line in enumerate(GRID):
        for col, value in enumerate(line):
            if value == symbol:
                return row, col
    raise ValueError("missing cell: " + symbol)


def heuristic(a, b):
    return abs(a[0] - b[0]) + abs(a[1] - b[1])


def neighbors(cell):
    row, col = cell
    for dr, dc in ((-1, 0), (1, 0), (0, -1), (0, 1)):
        nxt = row + dr, col + dc
        if 0 <= nxt[0] < len(GRID) and 0 <= nxt[1] < len(GRID[0]):
            if GRID[nxt[0]][nxt[1]] != "#":
                yield nxt


def astar():
    start = find("S")
    goal = find("G")
    heap = [(heuristic(start, goal), 0, start)]
    parent = {start: None}
    cost = {start: 0}
    closed = set()
    serial = 1
    max_open = 1

    while heap:
        _, _, current = heappop(heap)
        if current in closed:
            continue

        closed.add(current)
        if current == goal:
            break

        for nxt in neighbors(current):
            new_cost = cost[current] + 1
            if new_cost >= cost.get(nxt, 10**9):
                continue

            cost[nxt] = new_cost
            parent[nxt] = current
            priority = new_cost + heuristic(nxt, goal)
            heappush(heap, (priority, serial, nxt))
            serial += 1

        max_open = max(max_open, len(heap))

    if goal not in parent:
        return None, len(closed), max_open

    path = []
    current = goal
    while current is not None:
        path.append(current)
        current = parent[current]
    path.reverse()
    return path, len(closed), max_open


if __name__ == "__main__":
    path, expanded, max_open = astar()
    if path is None:
        print("NO PATH")
    else:
        print("path_length=", len(path) - 1)
        print("expanded=", expanded)
        print("max_open=", max_open)
        print("path=", path)

在文件所在目录执行:

python mini_astar.py

在本文给出的地图上,输出应为:

path_length= 13
expanded= 23
max_open= 3
path= [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0),
       (4, 1), (4, 2), (4, 3), (4, 4), (4, 5),
       (4, 6), (4, 7), (4, 8), (4, 9)]

这条路径先沿最左侧向下,再从最后一行穿过地图到达目标。path_length 为 13,是相邻坐标之间的移动次数,因此比坐标点数量少 1;expanded 为 23,说明算法为得到这条 13 步路径还处理了其他节点;max_open 为 3,表示搜索过程中候选集合的最大规模。三个数字描述的是不同维度,不能互相替代。

程序会输出路径长度、扩展节点数量、Open 峰值和完整坐标列表。代码中最值得理解的是四个对象:

  1. heap 保存 Open 候选,并按 g + h 排序。
  2. cost 保存起点到每个节点的当前最小已知代价。
  3. closed 保存已经正式扩展的节点。
  4. parent 保存路径来源,用于从目标反向回溯。

把某个 . 改成 # 后再次运行,可以观察路径和搜索统计怎样变化。如果堵死所有通道,程序会输出 NO PATH。这对应全局规划失败,但还没有回答“机器人正在运动时该怎么停”。动态系统必须继续考虑当前速度是否允许立即停止,以及之后地图恢复时是否自动继续。

如果想把这个程序改成 Dijkstra,只需让 priority = new_cost;改成 Greedy,则让 priority = heuristic(nxt, goal)。BFS 可以使用队列按层处理。这个小实验建立了算法直觉,但平台的价值在于把这些搜索过程和局部控制、动态地图放到同一个可观察界面中。

5. 从全局路径走向 DWB 与 TEB

5.1 参考路径不是可直接执行的运动

全局 A* 返回的是离散栅格序列。机器人有连续位姿、朝向、速度和加速度,不可能在每个栅格拐角瞬间转向。局部控制器需要从全局路径前部选择短期目标,再生成当前控制周期可执行的 v 和 omega。

因此至少要区分四层信息:

对象 由谁生成 是否已经执行
全局参考路径 BFS、Dijkstra、A* 或 Greedy 否,只描述路线
DWB 候选轨迹 不同 (v, omega) 的短时预测 否,只有一个候选会被采用
TEB 弹性位姿带 参考路径前部经过局部调整的节点 否,用于产生连续控制
已行驶轨迹 机器人位姿随时间积分 是,属于历史记录

5.2 DWB:在当前可达速度中做选择

DWB 的关键不是枚举所有可能速度,而是先根据上一周期速度和加速度上限得到当前动态窗口。假设上一周期速度为 (v_prev, omega_prev),本周期允许范围可以近似写成:

v_low  = v_prev - max_accel * dt
v_high = v_prev + max_accel * dt
w_low  = omega_prev - max_alpha * dt
w_high = omega_prev + max_alpha * dt

随后还要把范围限制在最大线速度和最大角速度内。平台默认从线速度窗口取 4 个值,从角速度窗口取 9 个值,通常形成 36 条候选;每条候选向前预测 14 步,每步 0.12 秒,预测时域约为 1.68 秒。

候选轨迹并不是都能执行。平台把机器人近似为半径 0.28 米的圆形 footprint,检查中心和圆周采样点。只要一个预测位姿与障碍冲突,该候选就会进入碰撞淘汰集合。剩余候选再综合局部目标距离、参考路径偏差、朝向误差、障碍间距、速度平滑性和前进量进行评分,选择得分最低的一条。

这解释了两个常见现象。第一,几何上看起来最直接的轨迹可能因碰撞或加速度限制被淘汰;第二,障碍突然出现后,DWB 的速度不能随意跳到另一个极端值,因为新窗口仍受到上一周期速度约束。

平台的评分可以近似理解为多个 critic 的加权和:

score = target_error
      + path_error
      + heading_error
      + clearance_cost
      + smoothness
      - progress_reward
      - velocity_reward

target_error 让轨迹末端靠近局部前视目标,path_error 约束轨迹不要偏离绿色参考路径太远,heading_error 关注末端朝向,clearance_cost 惩罚靠近障碍,smoothness 惩罚相对上一周期的速度突变。前进量和适度较高的线速度作为奖励降低总分。界面中的“最优评分”只有在当前候选集和当前权重下才有意义,不能跨地图直接比较。

如果所有候选都发生碰撞,DWB 不会继续沿原速度冲向障碍。教学控制器会把线速度目标降为零,并在角加速度约束内把角速度拉向零;主窗口还会在执行下一控制步前再次做碰撞检查。候选预测和执行前检查是两层不同的安全判断。

5.3 TEB 教学模型:调整局部弹性位姿带

TEB 思路通常把局部轨迹表示为一串带有时间信息的位姿。平台从机器人在全局参考路径上的最近位置开始,截取前部路径构成局部弹性带。第一个点使用当前连续位姿,其余点来自参考路径。

教学模型会对内部节点执行多轮平滑:相邻节点产生平滑作用,原参考路径提供锚定,附近障碍物产生推离作用。候选节点及相邻线段的中点还要经过 footprint 检查,避免单纯追求圆滑而穿过障碍。

平台没有运行生产级 TEB 中完整的通用非线性优化,也没有复刻所有约束和插件接口。它保留的是最适合观察的关系:弹性带形状、障碍物距离、局部曲率、朝向误差和时间间隔会共同影响线速度与角速度。

当弯道变急或朝向误差增大时,期望线速度会下降;角速度也只能在角加速度限制内逐步变化。接近目标后,线速度继续随剩余距离降低。读者看到的是连续控制量如何从局部几何关系中产生,而不是“平滑曲线越圆就一定越好”。

平台还会根据相邻弹性带点的距离和期望速度估计每一段的时间间隔,并显示平均值。它没有执行完整时空优化,但把“空间中的一串位姿”和“沿这些位姿运动需要多少时间”联系起来。弹性带形状改变后,局部曲率、期望速度和时间间隔也会一起变化。

当前教学参数中,TEB 的最大线速度和角速度分别为 1.00 m/s 与 1.60 rad/s,线加速度和角加速度上限分别为 1.00 m/s² 与 2.80 rad/s²。这些数值只用于模拟器,一个地图格也被约定为一米。真实机器人必须根据底盘、地图分辨率和安全要求重新标定,不能直接照搬。

5.4 DWB 与 TEB 应该比较什么

这张图用于区分参考路径、候选轨迹和弹性带。图中的速度参数只是示意标签,不应用来判断某条曲线的精确转向半径或坐标正负关系。真正实验时,以平台实时显示的速度窗口、候选状态、弹性带节点和机器人运动为准。

比较角度 DWB 更适合观察 TEB 教学模型更适合观察
内部表示 多组速度候选及预测轨迹 一串局部弹性位姿节点
障碍处理 碰撞候选直接淘汰并重新评分 障碍推力与安全检查调整位姿带
连续性 动态窗口和加速度边界 曲率、朝向、时间间隔和加速度边界
界面重点 有效候选、淘汰候选、最优评分 节点数、局部曲率、平均时间间隔

不要用“候选数量多”或“曲线更平滑”单独判断优劣。更合理的问题是:两侧是否安全到达、速度是否连续、遇到同一地图事件后怎样重规划、路径变长多少、等待多久,以及恢复过程是否符合预期。

6. 把算法放进可视化学习平台

6.1 平台为什么适合做对照实验

ROS Navigation Algorithm Visual Learning Platform 把两组实验放在同一张共享地图中。左右面板可以选择不同算法,同时开始、暂停或快速完成。固定地图和事件后,读者能把注意力放在算法差异,而不是反复手工搭建环境。

平台提供的关键观察点包括:

  • BFS、Dijkstra、A* 和 Greedy 的逐帧 Open/Closed 搜索前沿。
  • DWB 的动态速度窗口、安全候选、碰撞淘汰和最优轨迹。
  • TEB 教学模型的弹性带节点、局部目标、曲率和连续速度。
  • 左右控制器各自的位姿、速度、控制步数和已行驶轨迹。
  • 地图障碍的原子编辑、独立重规划、制动、等待和恢复。
  • 三组确定性动态场景与一键自动演示。

这不是单纯把最终路径画出来,而是把算法中间量也放到界面中。你可以暂停在某一帧,比较候选为什么被淘汰;也可以在机器人运动中画障碍,观察参考路径何时更新。

6.2 Windows 用户直接下载便携版

打开项目的 GitHub Releases 页面,下载当前版本的单文件程序:

ROS-Navigation-Learning-Platform-v1.0.0-win-x86_64.exe

下载后可以直接双击,不需要安装 Python、PyQt5、Git、ROS 2 或管理员权限。单文件版首次启动会解压 Qt 组件,窗口出现前可能需要等待数秒,不要连续重复双击。

Release 中还提供 ZIP 目录包。ZIP 版同样不需要 Python,但必须先“全部解压”,再运行目录中的 ROS-Navigation-Learning-Platform.exe,不要在压缩包预览窗口里直接启动。

首版程序未签名,Windows SmartScreen 可能显示“Windows 已保护你的电脑”或“未知发布者”。先确认文件来自项目 Release,再使用同一 Release 中的 SHA256SUMS-windows.txt 校验完整性:

Get-FileHash .\ROS-Navigation-Learning-Platform-v1.0.0-win-x86_64.exe -Algorithm SHA256

SHA256 应与校验文件中同名程序的一行一致。校验一致说明下载内容完整,但它不等同于代码签名。

6.3 源码学习者这样启动

需要阅读或修改算法时再克隆源码。支持 64 位 Python 3.10~3.14,运行依赖为 PyQt5 5.15.x。

git clone https://github.com/2303886347/ROS-Navigation-Algorithm-Visual-Learning-Platform.git
cd ROS-Navigation-Algorithm-Visual-Learning-Platform
python -m venv .venv

Windows CMD:

.venv\Scripts\activate.bat
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python run_nav.py

Linux:

source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python run_nav.py

启动命令必须在仓库根目录执行,正确写法是 python run_nav.py,不是 python Nav/run_nav.py。如果只是体验平台,优先使用发布版,不需要为了运行 GUI 专门搭建开发环境。

6.4 第一次打开先认识界面

顶部区域用于设置地图行列、随机种子和动画速度,也可以选择动态场景并加载、触发或恢复事件。左右实验面板各自拥有算法下拉框、运行状态、地图、算法说明、指标和本侧控制按钮。

“同时开始/继续”会启动尚未运行的面板;“停止全部”用于暂停活动实验;“快速完成”对全局算法直接显示最终结果,对 DWB/TEB 则批量推进控制步,但仍保留速度、加速度和碰撞检查,不会把机器人瞬移到终点。

左键从空白格开始拖动可添加障碍,从已有障碍开始拖动则擦除;右键设置终点,中键设置起点。修改障碍属于环境变化,活动局部控制器从当前位置重规划;修改起点或终点会改变任务定义,因此两侧完整重置。

6.5 第一次启动先做三分钟检查

正式记录实验前,先用三个短操作确认软件和界面状态正常。

第一项是全局搜索检查。加载回字地图,左右都选择 A*,点击“同时开始/继续”。两侧应显示逐步变化的 Open 和 Closed,快速完成后应出现绿色路径以及路径步数、Closed、Open 峰值。左右结果在同一地图上应一致,这一步用于排除某一面板没有启动或地图不同步。

第二项是局部控制检查。把左侧改为 DWB、右侧改为 TEB,重新加载地图并开始。DWB 应显示多条短时候选及最优结果,TEB 应显示弹性带位姿点;两侧机器人都应逐步运动,而不是直接从起点跳到目标。状态栏中的 v、omega、线加速度和角加速度会随控制周期更新。

第三项是动态场景检查。选择“前方突然封路”,点击“加载场景”,再点击“一键自动演示”。平台会自动配置左侧 DWB、右侧 TEB,并在合适进度触发障碍。重规划次数应从 0 变为 1,机器人位姿和历史轨迹不应回到起点。

如果三项都符合预期,说明入口、绘图、算法和动态状态链路已经工作。之后再做对照实验时,每次只改变一个变量。若第一项正常而第二项没有运动,重点检查局部算法选择和运行状态;若前两项正常而动态指标不变,检查是否真正加载并触发了动态场景。

界面支持中英文即时切换。教程中的算法名 A*、DWB、TEB 保持不变,按钮和指标可能随语言切换显示对应翻译。记录实验时建议固定一种语言,避免同一组截图中的标签不一致。

7. 先在平台里比较全局搜索

7.1 实验一:A* 与 Dijkstra

这组实验用于观察“相同最短路径目标下,启发式如何改变搜索范围”。

  1. 启动平台,点击“加载回字地图”。
  2. 左侧选择 A*,右侧选择 Dijkstra。
  3. 点击“同时开始/继续”,观察两侧 Open 和 Closed 前沿。
  4. 中途暂停,比较两侧当前扩展区域。
  5. 点击“快速完成”,记录路径步数、Closed 数量和 Open 峰值。

预期现象是两侧通常能得到相同长度的最短路径,但 A* 的搜索更集中在目标方向,Dijkstra 更像从起点按累计代价向外展开。具体扩展数量会受地图结构和并列顺序影响,不要预设一个固定比例。

如果两侧路径步数不同,先检查是否都已经完成、起终点和地图是否一致、状态栏显示的是最终值还是动画中间值。在当前单位代价地图上,A* 与 Dijkstra 都应以最短路径为目标。

完成这组实验后,你已经能直观看到启发式的作用。如果平台确实帮助你理解了 Open、Closed 和最终路径的区别,可以在项目仓库点击一个 Star。它既方便以后找到项目,也能让更多正在学习 ROS 2 / Nav2 的读者看到这个工具。

7.2 实验二:A* 与 Greedy

  1. 保持同一张固定地图,左侧选择 A*,右侧选择 Greedy。
  2. 同时开始并观察搜索前沿朝向。
  3. 快速完成后记录路径步数、Closed 和 Open 峰值。
  4. 在错误通道附近增加一段障碍,再运行一次。

Greedy 往往更积极地朝目标推进,动画可能显得很“聪明”。但当障碍迫使路径先远离目标再绕行时,只看 h 的策略容易进入不理想方向。实验结论应写成“在当前地图中 Greedy 扩展更少但路径多出多少步”,而不是笼统写“Greedy 更快”。

7.3 实验三:BFS 与 Dijkstra

把左侧改为 BFS、右侧改为 Dijkstra。在当前单位代价栅格中,两者很可能得到相同路径长度和近似扩展过程。这是正常现象,不是程序没有切换算法。

随后思考一个扩展问题:如果某些格子代价为 5,其他格子代价为 1,BFS 仍只会比较步数,而 Dijkstra 会比较总代价。平台当前没有带权地形,因此这组实验的重点是理解算法适用条件,不是强行制造不同结果。

7.4 用自定义起终点验证结论

固定回字地图适合第一次学习,但一张地图不足以支持普遍结论。右键可以设置终点,中键可以设置起点。建议先保存默认实验记录,再只移动终点,保持其他条件不变,重新比较 A* 与 Dijkstra。

如果目标靠近起点,启发式带来的差异可能很小;如果目标位于障碍另一侧,A* 的目标方向性会更明显;如果存在多条等长最短路,两侧绿色路径形状可能不同但步数相同。路径形状不同并不自动意味着某一侧错误,应继续检查邻居顺序、并列优先级和路径长度。

再生成一张随机地图并记住随机种子。相同种子可以复现同一布局,适合修改算法前后做对照。每次只改变地图、起点、终点或算法中的一个变量,否则无法判断指标变化来自哪里。

8. 再比较 DWB 与 TEB 局部控制

8.1 在同一弯道观察中间量

局部控制实验应固定地图、起终点和触发条件。可以加载“可用通道切换”场景但暂时不触发事件,左侧选择 DWB,右侧选择 TEB,然后点击“同时开始/继续”。

DWB 一侧重点观察:

  • 动态窗口的线速度和角速度上下界。
  • 有效候选与碰撞淘汰候选数量。
  • 灰色候选、红色淘汰轨迹和橙色最优结果。
  • 机器人接近弯道时速度窗口怎样随上一周期变化。

TEB 一侧重点观察:

  • 弹性带节点数和局部目标。
  • 节点怎样贴近绿色参考路径并避开障碍。
  • 平均时间间隔和局部曲率。
  • 转弯前后线速度和角速度是否连续变化。

8.2 不要只看谁先到终点

DWB 与 TEB 的最大速度、加速度、内部表示和目标函数并不相同,直接比较墙钟时间并不能说明某种思想普遍更好。平台也会受到动画速度和批量推进设置影响。

更合理的记录方式是:两侧是否都安全到达、用了多少控制步、轨迹是否贴近障碍、速度和加速度是否连续、动态事件后路径增加多少、是否进入等待,以及恢复花了多少仿真时间。

如果想观察障碍对 DWB 的影响,可以在机器人前方画一堵不完全封路的墙。部分预测轨迹会因 footprint 碰撞变红,其余候选重新评分。障碍很近时,机器人可能先减速或原地调整,而不是立即以最大速度绕开。

如果想观察 TEB 的变化,可以在弯道附近编辑障碍。旧弹性带会丢弃,新的全局参考路径生成后,局部节点重新构建并平滑。粉色节点是当前局部表示,橙色历史轨迹才说明机器人实际走过哪里。

8.3 做一次单变量参数实验

源码学习者可以在完成默认实验后修改一个局部控制参数。研究 DWB 时,优先选择障碍间距、路径偏差或速度平滑相关权重;研究 TEB 时,可以调整平滑强度或观察曲率降速相关逻辑。不要一次修改多个参数。

推荐记录四轮结果:默认参数、参数略微增大、参数明显增大、恢复默认。四轮都使用同一地图、同一起终点、同一动态场景和相同触发时机。观察轨迹是否离墙更远、候选评分怎样变化、速度是否更保守、控制步数是否增加,以及最终是否仍能到达目标。

参数变化产生的动画差异不等于算法改进。例如提高 DWB 的障碍惩罚可能让轨迹远离墙,但也可能使狭窄通道中的有效候选减少;增大 TEB 平滑作用可能让曲线更圆,却可能把节点推向障碍。导航调参本质上是在多个目标之间取舍。

修改源码后先运行同一人工实验,再运行项目测试。测试通过只能说明当前教学模型仍满足已有速度、加速度和无碰撞约束,不能证明参数适用于真实机器人。

9. 用三组场景理解动态重规划

9.1 地图事件发生后平台做了什么

用户拖动障碍时,画布先显示预览;松开鼠标后,整条笔画作为一个原子编辑提交。只要笔画覆盖起点、终点或活动机器人的 footprint,整笔都会被拒绝,避免出现前半段已经写入、后半段失败的半完成地图。

合法编辑提交后,每个活动的 DWB/TEB 面板分别从自己的当前位姿运行 A*。重规划只替换参考路径和局部缓存,机器人位姿、上一速度、控制步数和已行驶轨迹继续保留。

如果新路径存在,运行侧继续执行;如果没有路径,平台根据下一控制步是否安全进入不同状态:

当前情况 平台行为 通路恢复后
仍有替代路径 立即替换参考路径 从当前位置继续
无路,但下一步仍安全且有速度 按加速度限制进入 braking 按原运行意图恢复
下一步已经会碰撞 立即把速度归零并进入 waiting 运行侧继续,暂停侧保持暂停
暂停时遇到无路 保持速度为零并等待 仍保持暂停

9.2 场景一:前方突然封路

这是第一次体验动态重规划最合适的场景。

  1. 在“动态场景”中选择“前方突然封路”。
  2. 点击“加载场景”。
  3. 左侧选择 DWB,右侧选择 TEB。
  4. 点击“同时开始/继续”。
  5. 机器人接近中间墙前点击“触发事件”,或直接点击“一键自动演示”。
  6. 观察绿色参考路径、橙色历史轨迹和重规划指标。

事件会在原短路上加入三格障碍,替代路线至少增加 8 个栅格步。上图中两侧机器人没有回到 S,而是保留事件前的位姿和已行驶轨迹,从各自当前位置生成新的 A* 参考路径。

DWB 与 TEB 的局部速度不同,所以事件触发时的准确位姿可能略有差异。比较时不要要求新路径逐点相同,应关注两侧是否都保留历史、重规划次数是否增加、剩余路径变化是否为正,以及最终是否到达目标。

9.3 场景二:可用通道切换

这个场景不是简单添加障碍,而是在同一次地图更新中关闭原通道并开放备用通道。

  1. 加载“可用通道切换”。
  2. 启动左侧 DWB 和右侧 TEB。
  3. 在机器人接近中间墙前点击“触发事件”。
  4. 观察参考路径从原缺口切换到备用缺口。
  5. 对比 DWB 候选重新评分和 TEB 弹性带重建。

添加和移除必须属于同一个原子事件。如果先关闭旧通道,下一帧再打开新通道,控制器可能短暂得到“无路”并开始制动;如果先打开新通道再关闭旧通道,又会产生一个没有教学意义的中间路径。一次提交只增加一个地图版本,两侧也只重规划一次。

记录结果时关注路径切换、剩余路径增量和局部转向过程。DWB 会重新评价当前动态窗口中的候选;TEB 会基于新参考路径构造新的局部弹性带。

9.4 场景三:临时障碍出现后自动消失

“临时障碍(3.2 s)”用于观察同一个环境事件的出现和恢复。

  1. 加载“临时障碍(3.2 s)”。
  2. 手动启动并触发事件,或点击“一键自动演示”。
  3. 障碍出现后观察第一次重规划。
  4. 等待 3.2 秒仿真时间,障碍自动移除。
  5. 观察第二次重规划和参考路径恢复。

自动演示会把左侧设置为 DWB、右侧设置为 TEB,并在约 35% 路径进度处触发事件。较快的一侧可能暂时等待另一侧,保证障碍对双方都出现在前方;同步等待最长为 8 秒仿真时间。

预期两侧最终重规划次数为 2:一次响应障碍出现,一次响应障碍消失。若临时封锁期间仍有替代路线,机器人会绕行;如果你手动补齐障碍使地图完全断开,则可以继续观察制动、waiting 和通路恢复。

9.5 自定义实验:完全封路再恢复

内置三组场景都尽量保证事件后存在替代路径,便于稳定演示。如果要学习无路状态,可以加载“前方突然封路”,让 DWB 运行一段距离后,在竖墙所有开放位置补齐障碍。

若当前下一步仍安全且机器人有速度,状态会先进入 braking,速度逐步下降,归零后进入 waiting;若障碍离机器人太近,下一步已经会碰撞,则直接急停。擦除封锁后,原本运行的一侧自动继续,原本暂停的一侧仍保持暂停。

这个实验把三个问题分开:全局上是否存在路径、当前速度能否安全停下、用户之前希望机器人运行还是暂停。只有同时保存这些信息,恢复行为才可预测。

9.6 按时间线观察一次事件

动态实验最容易因为只截取事件后的单帧而漏掉关键过程。建议按时间线记录五个时刻:事件触发前、地图版本更新、参考路径替换、局部控制恢复、最终到达目标。临时障碍还要增加“障碍自动移除”这一时刻。

事件前记录两侧位姿、速度和剩余路径;事件发生时确认障碍是一次完整提交;路径更新后检查机器人没有回到起点;运动恢复后比较 DWB 候选与 TEB 弹性带;实验结束时确认状态、控制步数和重规划次数。这样即使左右控制器触发时位置略有不同,也能解释差异来自哪里。

使用“一键自动演示”可以减少触发进度差异,但它不会让两侧内部状态完全一致。较快的一侧可能进入同步等待,DWB 和 TEB 的速度模型也不同。实验报告应写明使用了自动触发还是手动触发。

10. 指标怎样读才不会误判算法

10.1 全局搜索指标

指标 实际含义 不应怎样解释
路径步数 最终离散路径包含的移动步数 不能单独代表搜索速度
Closed 数量 已经扩展处理的节点数 不是最终路径节点数
Open 峰值 候选集合曾达到的最大规模 不等于总扩展节点

比较全局算法时,至少固定地图、起点、终点和算法参数。路径相同但 Closed 不同,说明搜索工作量不同;Closed 较少但路径更长,可能是 Greedy 用最优性换取了方向性。

10.2 局部控制中间量

DWB 会显示有效候选、碰撞淘汰候选、速度窗口边界和最优评分。有效候选更多不等于轨迹一定更好,它只表示当前预测时域和 footprint 下有更多速度组合没有立即碰撞。

TEB 会显示弹性带节点、平均时间间隔、局部曲率和平滑信息。节点更多也不代表更优,因为数量主要取决于截取的参考路径长度。应结合障碍距离、速度连续性和最终是否到达目标解释。

10.3 动态重规划指标

指标 含义 常见误读
重规划次数 因地图版本变化重新生成参考路径的次数 不是局部控制每帧更新次数
上次重规划耗时 最近一次 A* 路径计算耗时 不包括机器人后续行驶时间
剩余路径变化 新路径步数减去事件前剩余步数 不等于已经行驶的距离
累计等待时长 无路或同步事件造成的仿真等待 不等于现实墙钟时间

重规划耗时可能只有毫秒级,但这不表示导航恢复只需几毫秒。新参考路径生成后,机器人仍要在速度、角速度和加速度约束下逐步跟踪。平台把路径计算和物理式运动分开显示,正是为了避免把两种时间尺度混在一起。

10.4 建议的实验记录

每次实验记录一行即可:

场景 | 左右算法 | 地图或种子 | 触发时机 | 路径步数/控制步数 |
重规划次数 | 上次耗时 | 剩余路径变化 | 等待时长 | 最终状态

结论应写成“在当前场景和参数下,A* 与 Dijkstra 路径同长但 A* 扩展更少”,或“事件后 DWB 的碰撞淘汰候选增加,TEB 的局部曲率升高,两侧都完成重规划并到达目标”。不要只写“算法 A 更好”,因为导航表现依赖地图、参数和触发位置。

10.5 四种常见错误结论

错误结论 问题在哪里 更好的写法
A* 永远比 Dijkstra 快 没有固定地图,也混淆计算耗时和扩展量 当前地图中 A* 的 Closed 数量更少
Greedy 扩展少,所以最好 忽略了最终路径长度和稳定性 Greedy 用较短搜索换来了多少路径增量
TEB 曲线更平滑,所以更安全 平滑不等于障碍距离更大 同时检查 footprint、曲率和到达结果
重规划只花 1 ms,所以立即恢复 忽略机器人后续受速度和加速度约束 区分路径计算耗时与运动恢复时间

截图也会影响判断。只裁剪地图看不到状态和指标,只截指标又无法确认事件位置。比较动态场景时,截图最好同时包含地图、两侧算法、状态栏和动态指标区域。

11. 从教学平台映射到真实 Nav2

11.1 沿数据流完成五步映射

完成平台实验后,可以按数据流逐项进入真实系统,而不是一开始就复制整套参数。

第一步是地图。平台使用二值 grid,真实 Nav2 会使用静态地图和分层 costmap,还要考虑地图分辨率、原点、未知区域、障碍层和膨胀层。

第二步是位姿。平台直接保存 (row, col, theta),真实系统需要定位模块和 TF 维护 map、odom、base_link 之间的关系。路径看起来正确但机器人偏离,问题可能来自定位或坐标变换,而不是规划算法。

第三步是全局规划。平台的 global_planners.py 直接返回坐标列表;Nav2 通常由 Planner Server 通过插件和 Action 生成 nav_msgs/Path。

第四步是局部控制。平台的 local_planners.py 返回 (v, omega) 和教学中间量;真实 Controller Server 还要读取局部 costmap、footprint、速度限制、目标检查器和进度检查器。

第五步是恢复。平台用 running、braking、waiting 和 paused 展示关键语义;Nav2 会通过行为树、行为或恢复插件、Action 状态和生命周期节点组织完整任务。

源码学习时可以按以下顺序阅读:

文件 主要内容
nav_app/global_planners.py BFS、Dijkstra、A*、Greedy 和搜索统计
nav_app/local_planners.py DWB、TEB、footprint、速度与制动约束
nav_app/scenarios.py 三组确定性动态地图事件
nav_app/main_window.py 双面板运行、地图更新、重规划和恢复

11.2 从异常现象倒查模块

进入真实 Nav2 后,可以沿用平台里建立的分层思路。全局路径本身穿墙,优先检查地图、costmap 和全局规划;全局路径正确但局部轨迹无法前进,检查局部 costmap、footprint、速度限制和控制器;界面中的路径与机器人实际位置错位,检查定位和 TF;环境恢复后任务没有继续,再检查行为树、进度检查器和恢复行为。

平台不能替你诊断真实系统,但它能让这些问题不再混成一句“导航失败”。只要先判断错误发生在地图、路径、局部控制、位姿反馈还是恢复状态,后续查看日志和话题就会更有方向。

当你能解释“障碍为什么改变 reference path”“为什么当前速度不能瞬间归零”“为什么暂停侧恢复后仍暂停”,再进入 Gazebo 或真实底盘,会更容易区分规划失败、控制失败、定位错误和感知更新问题。

12. 从零运行与常见问题

12.1 普通用户最短主线

  1. 从项目 Releases 下载 Windows x64 便携 EXE。
  2. 使用 SHA256SUMS-windows.txt 核对文件完整性。
  3. 双击启动平台。
  4. 选择“前方突然封路”并点击“加载场景”。
  5. 点击“一键自动演示”。
  6. 观察 DWB 与 TEB 的当前位置重规划、历史轨迹和四项动态指标。
  7. 再完成 A* / Dijkstra、A* / Greedy 和另外两组动态场景。

上图把普通用户和源码学习者的入口放在同一条实验主线上。普通用户不需要先学会搭建开发环境;源码学习者则可以在完成界面实验后,再进入算法文件修改参数并重复同一场景。

12.2 SmartScreen 提示未知发布者

v1.0.0 便携程序没有代码签名,因此 Windows 可能显示“未知发布者”。确认下载页面是项目官方 Release,核对 SHA256 后,再选择“更多信息”和“仍要运行”。不要运行来源不明的同名文件。

12.3 源码提示找不到 PyQt5

先确认已经激活虚拟环境,并检查 Python 版本:

python --version
python -c "import struct; print(struct.calcsize('P') * 8)"
python -c "import PyQt5; print('PyQt5 OK')"

当前要求为 64 位 Python 3.10~3.14。随后在仓库根目录执行:

python -m pip install -r requirements.txt
python run_nav.py

如果只是想体验算法,不需要解决源码依赖,直接改用 Release 便携版即可。

12.4 地图变化后机器人为什么不回起点

这是动态导航的正确行为。障碍变化只改变环境,没有改变目标任务。活动的 DWB/TEB 会保留位姿、速度、控制步数和历史轨迹,从当前位置重新执行 A*。只有修改起点、修改终点、重新生成地图或加载新场景才会完整重置。

12.5 为什么无路后不是总能立即停

如果下一控制步仍然安全,正在运动的机器人会按照加速度上限逐步减速,这比不受约束地把速度瞬间改为零更符合连续控制模型。如果下一步已经会碰撞,平台才会立即把速度归零。暂停状态本来速度就是零,因此可以直接进入等待。

12.6 TEB 为什么与真实插件不完全一样

平台中的 TEB 是教学模型,用弹性位姿带、时间间隔、障碍推力和曲率降速展示核心思想,没有运行完整生产优化器。它适合建立直觉,不适合直接替代真实 Nav2 或其他导航框架中的控制器插件。实际项目应以所用发行版和插件文档为准。

12.7 窗口打开了,但算法看起来没有变化

先确认两侧是否选择了不同算法,以及动画是否仍停在第 0 步。全局算法需要点击“开始”或“快速完成”才能看到最终统计;DWB/TEB 则要经过多个控制周期,机器人不会瞬间到达目标。

A* 与 Dijkstra 在简单地图上可能得到相同路径,BFS 与 Dijkstra 在单位代价地图上也可能非常接近。这不是切换失败。改用带有绕行结构的固定地图,或观察 Closed 和 Open 峰值,而不要只盯着绿色路径。

如果编辑地图后局部控制器没有回到起点,这是预期行为;如果修改起点或终点后仍保留旧运动状态,才需要重新检查任务是否确实提交成功。

13. 推荐的七步学习路线与总结

第一步,只运行 A* 与 Dijkstra,学会区分 Open、Closed 和最终路径。第二步,加入 Greedy,理解朝目标扩展更积极不等于路径最短。第三步,运行最小 A* 程序,把界面中的搜索前沿对应到 heap、cost、closed 和 parent。

第四步,在同一弯道比较 DWB 与 TEB,区分全局参考路径、候选轨迹、弹性带和已行驶轨迹。第五步,依次完成前方突然封路、可用通道切换和临时障碍,把注意力从某一帧的曲线转向地图版本、当前位置重规划和恢复状态。

第六步,手动制造完全封路,观察 braking、waiting、急停和通路恢复。第七步,克隆源码,只修改一个 DWB 评分权重或新增一个动态场景,固定地图和触发时机后重复实验。

每完成一步,都尝试用一句话说明“本次只改变了什么”“界面中的哪个中间量发生变化”“最终结果是否仍满足安全和到达目标”。能够说清因果关系,比记住某个默认参数更重要。

如果准备把学习继续到 Nav2,可以把平台实验记录作为问题清单:真实系统中的全局 costmap 在哪里更新、局部控制器读取什么 footprint、速度限制来自哪里、路径不可达时行为树进入哪个分支。这样进入官方教程时,你是在为已经理解的问题寻找真实接口,而不是被大量插件名称淹没。

这套平台最有价值的地方,不只是把六种算法画出来,而是把全局搜索、局部控制和在线环境变化放进同一条可观察链路。你能看到一次障碍编辑怎样成为地图事件,左右控制器怎样从各自当前位置重规划,无路时机器人怎样安全停车,通路恢复后又怎样按照原运行意图继续。

如果只是让动画跑到终点,平台仍然只是一个演示程序。真正的学习发生在你暂停动画、提出假设、只改变一个条件、记录中间量并解释结果的时候。建议为每组实验保留一张完整截图和一行结论,隔几天再换一张地图复现。能够在不同场景中重复解释同一种现象,才说明你掌握了算法思想,而不是记住了某次运行画面。

项目源码、Release、README 和动态场景都在 ROS Navigation Algorithm Visual Learning Platform。完成实验后,可以为仓库点一个 Star,并关注 GitHub 用户 2303886347,方便获取后续算法、场景和教学功能更新。更重要的是,尝试亲手改变一个参数、固定变量并解释结果,让平台成为验证导航算法假设的实验台,而不只是播放动画的软件。

14. 参考资料

真实 ROS 2 / Nav2 的插件、默认参数和消息接口会随发行版变化。进入仿真或真实机器人前,请以所使用发行版的官方文档为准。

5 个赞