TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

使用 ROS 2 和 Gazebo 进行机器人行驶仿真:不要相信轮子所说的“完美”

状态:草稿(2026-10-18),实测。 测量于 2026-10-17 在家实验室 Kubernetes 的一个临时 Pod 中进行。节点是一台迷你 PC(Intel Core Ultra 系列,22 线程,内存 31 GiB),Pod 的资源上限为 CPU 3 核、内存 4 GiB,且未分配 GPU 和 /dev/dri (无特权,无附加 capability)。镜像使用了 ros:jazzy(ros-base,Ubuntu 24.04.5),并通过 apt 添加了 ros-jazzy-ros-gz·ros-jazzy-turtlebot3-gazebo 。版本分别为 Gazebo Sim 8.15.0 (Harmonic)、ros_gz 1.0.24、turtlebot3_gazebo 2.3.7。重复次数为 B 组 2 次、C 组 3 次,其余 1 次,表中的数值为每次运行测量一次的结果(非中位数)。原始数据(rosbag2 mcap、汇总 JSON、1 秒间隔的 cgroup 采样)位于工作目录的 ROS 测量文件夹中,文中仅转录了隐藏了节点名称和地址的值。

目录

1. 直觉:闭着眼睛数步数的机器人

人们很容易相信,闭着眼睛连续四次“向前走十步,向左转 90 度”就能回到起点,因为步数数得很准。然而,如果地面很滑,少走了一步;或者转弯时脚打滑了,只转了 80 度而不是 90 度。只数步数的人是不会知道这些差异的。

轮式机器人的 里程计(odom) 就是这种数步数的方式。它通过对车轮转动的圈数进行积分,来宣称“我现在在这里”。而仿真器则额外提供了一样东西:物理引擎所知道的 真实位置(真实姿态) 。本文通过实际测量展示了并排记录这两个值时会看到什么。

2. 架构:单个 Pod 内的无界面仿真

파드 하나 안에서 도는 헤드리스 시뮬레이션 쿠버네티스 임시 파드: CPU 상한 3코어, 메모리 상한 4 GiB, GPU 없음 gz sim -s (화면 없음)물리 1 ms, 라이다·IMU ros_gz_bridgegz 토픽 ↔ ROS 2 토픽 주행 노드 (50 Hz)/odom 보고 /cmd_vel 냄 rosbag2 기록기mcap 파일 한 개 /odom /cmd_vel 기록하는 토픽: /odom /cmd_vel /imu /gt_poses(정답 자세) /scan 정답 자세는 gz 의 dynamic_pose 를 브리지로 꺼내 odom 과 나란히 기록합니다. 실행 A: 원본 월드(렌더링 라이다) B: 물리만 C: B + 실시간 제한 해제
图:在单个 Pod 内运行的无界面仿真:gz sim、桥接(bridge)、行驶节点、记录器

还测量了镜像成本。 ros:jazzy 压缩后为 283 MiB,下载耗时 18.3 s。在此基础上通过 apt 添加仿真软件包,下载了 768 个软件包、554 MB,安装后体积增加了 2,637 MB,耗时 272 s。顺便一提,仅从注册表清单来看, osrf/ros:jazzy-desktop 压缩后为 1,203.9 MiB, jazzy-simulation 为 698.3 MiB(未下载)。如果觉得每次安装的时间很可惜,下一步的工作就是将这个阶段固化为镜像。

3. 四种世界

名称 有什么不同
A TurtleBot3 的原始空白世界。由于包含 Sensors 系统(ogre2)以及需要渲染的 gpu_lidar ,因此使用 --headless-rendering 启动。
A2 在 A 的基础上放置了两个静态箱子。因为在空白世界中,激光雷达的值全部为无穷大,无法得知渲染是否正常运作。
B 去掉了 Sensors 系统。地面为内联平面,摩擦力与原始地面相同,即 mu 100,mu2 50。不使用渲染,也不使用网络。
C 将 B 中的 real_time_factor 改为 0,不绑定实时性,以最大速度运行。

4. 行驶节点:看 odom 而非时间前进

行驶节点不是“向前行驶 5 秒”这种开环控制。它是通过查看 /odom 依次寻找角点 (1,0)、(1,1)、(0,1)、(0,0) 的闭环控制,最大速度为 0.2 m/s,最大角速度为 0.6 rad/s,基于仿真时间的频率为 50 Hz。它必须采用与真实机器人航点行驶相同的方式,这样“相对于目标的误差”才能被理解为控制器、里程计和物理特性综合作用的结果。核心是直线行驶阶段的几行代码。

if self.phase == 'drive':
    tx, ty = CORNERS[self.leg]
    dx, dy = tx - x, ty - y
    # 진행 방향 위로 남은 거리. 지나치면 음수가 된다.
    seg_heading = HEADINGS[self.leg - 1] if self.leg else 0.0
    along = dx * math.cos(seg_heading) + dy * math.sin(seg_heading)
    if along <= POS_TOL:                      # 5 mm 안이면 멈추고 돌기 시작
        self.send(0.0, 0.0)
        self.mark('corner')
        self.phase = 'turn'
        return
    bearing = math.atan2(dy, dx) if math.hypot(dx, dy) > 0.05 else seg_heading
    w = max(-W_MAX, min(W_MAX, 2.0 * wrap(bearing - th)))
    v = max(0.02, min(V_MAX, 0.8 * along))
    self.send(v, w)

(摘自 TT Lab 实验代码 square_driver.py ,注释根据文章进行了精简或补充。旋转目标为每个角点指定了绝对方位 0、π/2、π、−π/2,以避免累积旋转误差。)

5. 结果:保持实时的代价与角落误差

运行 RTF 一圈墙钟时间 角落实际误差 mm (1→4) 终点位置实际误差 odom↔实际最大差 CPU 平均 anon 最大值 gz RSS
A 渲染激光雷达 1.000 47.5 s 11, 33, 40, 25 16 mm 41 mm 0.91 核 467 MiB 515 MiB
A2 A + 箱子 1.000 47.5 s 11, 47, 66, 43 50 mm 74 mm 0.92 核 466 MiB 509 MiB
B 仅物理 1.000 47.6 s 17, 23, 14, 5 16 mm 26 mm 0.73 核 196 MiB 156 MiB
B 重复 1.000 47.5 s 15, 33, 28, 13 16 mm 32 mm 0.74 核 196 MiB 未测量
C 最大速度 11.11 4.3 s 19, 27, 20, 7 16 mm 39 mm 2.88 核 196 MiB 156 MiB
C 重复 2 11.76 4.1 s 20, 28, 20, 8 17 mm 39 mm 2.54 核 196 MiB 未测量
C 重复 3 10.88 4.4 s 19, 28, 20, 7 16 mm 39 mm 2.86 核 196 MiB 未测量

RTF(实时因子)是 /odom 序言中的仿真时间增长量除以记录器接收到的墙钟时间增长量。CPU 是通过每秒读取一次 Pod cgroup 的 cpu.stat 来计算行驶区间的平均值,内存则是 memory.stat 的 anon 最大值。 memory.current 显示为 3.0–3.6 GiB,但其中大部分是在 apt 安装期间产生的页面缓存,因此实际持有的内存以 anon 为准。

렌더링을 빼고 실시간 제한을 풀면 한 바퀴 벽시계 시간(s) A 원본(렌더링 라이다) 47.5 A2 A + 상자 두 개 47.5 B 물리만 47.6 C 물리만 + 최대 속도 4.3 CPU 평균(코어, 상한 3) A 원본(렌더링 라이다) 0.91 A2 A + 상자 두 개 0.92 B 물리만 0.73 C 물리만 + 최대 속도 2.88 anon 메모리 최대(MiB) A 원본(렌더링 라이다) 467 A2 A + 상자 두 개 466 B 물리만 196 C 물리만 + 최대 속도 196 C 의 RTF(실시간 대비 속도)는 11.11. 같은 51.7 s 시뮬레이션이 4.3 s 에 끝납니다.
图:根据是否有渲染以及是否有实时限制的墙钟时间、CPU 和内存

6. 漏掉一行地面摩擦力设定时

在最初创建无渲染世界 (B) 时,将原始的地面模型更改为内联平面,却忘记编写摩擦力设置。此时将使用物理引擎 (ODE) 的默认值 mu 1。修改后的地面包含下面这一行。

FRIC = '<surface><friction><ode><mu>100</mu><mu2>50</mu2></ode></friction></surface>'  # 원본 바닥과 같게

下图记录了相同的行驶节点在两个世界中运行的情况。点击图片可以 4 倍速回放 rosbag 中记录的实际轨迹。

바퀴로 센 위치(odom)와 실제 위치 같은 주행 노드, 같은 1 m 정사각형. 바닥 마찰 한 줄만 다릅니다. ▶ 누르면 재생(4배속) 바닥 마찰을 적은 월드 odom 이 본 끝 위치 오차: 5 mm 실제 끝 위치 오차: 16 mm, 방향 -1.0° 바닥 마찰을 빠뜨린 월드 odom 이 본 끝 위치 오차: 5 mm 실제 끝 위치 오차: 0.50 m, 방향 -38° 실제(시뮬레이터 정답) odom(바퀴 회전으로 적분) 목표 정사각형 두 위치 사이 거리
图:相同的行驶节点在摩擦力不同的两个世界中留下的 odom 与实际轨迹
运行 每转实际角度 角落实际误差 mm (1→4) 终点位置实际误差 / 方向 odom 看到了终点位置误差
B(有摩擦力) 89.2, 89.7, 89.7, 89.7° 17, 23, 14, 5 16 mm / −0.98° 5 mm
B 漏掉摩擦力 80.3, 79.7, 80.3, 80.9° 10, 138, 344, 487 503 mm / −37.99° 5 mm
C 漏掉摩擦力 78.0, 79.2, 80.5, 80.9° 19, 164, 399, 533 549 mm / −40.24° 4 mm

在原地旋转时,车轮发生打滑,只转了大约 80° 而不是 90°,这种偏差在每个角点都累积了下来。然而,由于 odom 是对车轮关节角度进行积分的,因此它无法检测到打滑。因此,odom 一直到最后都报告说“在 5 mm 范围内返回”。如果仅凭 odom 来判断结果,这个世界将顺利通过测试。

我们还单独验证了真实姿态是否正确。运行结束后,将通过 gz model -m burger -p 查询到的位置与 /gt_poses 的最后一个值进行了比较。在漏掉摩擦力的 C 运行中, gz model 返回了 (−0.2505, 0.4883) m,方向旋转了 −40.0°,而 /gt_poses 为 (−0.2505, 0.4884) m,−40.2°。在有摩擦力的 B 中,这两个值完全相同,为 (−0.0130, 0.0099) m。

教训。 在仿真中评估机器人性能时,应将机器人自我估计的位置与仿真器的真实位置 同时 记录下来,并以真实位置作为评估标准。如果只看估计位置,物理设置上的失误会被误认为是机器人的成功。相反,如果只看真实位置,就无法得知估计器有多大程度的偏差。两者的差异(图中的红线)就是里程计漂移,这就是为什么在真实机器人中需要单独配备位置估计器的原因。

7. 在没有 GPU 的情况下运行激光雷达

A 和 A2 使用通过渲染来测量距离的 gpu_lidar(360 线,5 Hz)。Pod 中既没有 GPU 也没有 /dev/dri ,但它通过 --headless-rendering(EGL)启动并保持了 1.000 的 RTF。

8. 可用于 CI 的最大速度模式

C 是将 real_time_factor 设为 0 的 B。沿着 1 米正方形行驶一圈(仿真时间 47.7–51.7 s)在 4.1–4.4 s 的墙钟时间内完成,RTF 为 10.88–11.76。

这意味着可以通过每个 PR 运行一个 4 秒的测试,来验证“机器人能否在 2 cm 以内绕完 1 m 的正方形”。不过,该判定必须像第 6 节那样基于真值位姿来进行。

9. 遇到的问题

10. 局限性

11. 下期预告

下一篇将转向乒乓球机器人。我们将放弃轮子,把一个球放到 MuJoCo 中,测量反弹和旋转受模拟器单行配置的影响有多大。 机器人手臂本身的基础知识(坐标系、逆运动学)将在同步连载的“乒乓球机械臂基础”系列(目录)中进行介绍。

Titi 的小贴士。 如果只数步数,我总是回到了原地。但如果地面很滑,实际上我可能站在半米开外。所以在模拟器里,一定要把“真正的所在位置”也一起记下来哦!

参考资料

所有链接均已于 2026-10-18 验证可正常打开。版本均为测量时所使用的版本。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论