
代码链接:https://github.com/LitchiCheng/mujoco-learning
视频链接:https://www.bilibili.com/video/BV1GdbX62EGa/?vd_source=5ba34935b7845cd15c65ef62c64ba82f
在 Mujoco 中做视觉策略的 RL 或者数据采集,相机视角(pos / quat)基本决定了观测图像的质量。但 XML 里的 camera 只有两组数字,想调视角只能"盲调":改数 → 跑一遍可视化 → 存张图看看 → 不对再改,一个相机往往要反复几十次。下面这两张就是典型的"盲调前后"------同一个场景,视角不对时只能看到一片空地
调整前

调整后

尤其腕部相机,它挂在某个 body 下,pos 用的是父 body 的局部坐标,轴向方向很反直觉,调起来更加痛苦。分享一个交互式相机安装位置调整工具 tune_camera.py:在窗口里实时看到相机的画面 + 6 个滑块,用键盘/鼠标微调 pos 和 rot,调好后直接写回 XML。
MuJoCo 相机的几个约定
这是调参前最重要的背景:
- 相机看向自身局部坐标系的 -Z 轴方向(上方向是 +Y 轴)
- pos 是相机在父 body 局部坐标系中的位置(挂在 world 上时才是世界坐标)
- quat 是相机相对父 body 的姿态,格式为 (w, x, y, z)
- 所以腕部相机不能只看世界里的"想让它往哪看",还要先搞清楚父 body 局部坐标轴在世界中的指向
工具的核心能力
uv run python tune_camera.py 启动后:
- 加载任意场景 XML,自动发现其中所有 (包括通过 引用的文件里的)。启动时会列出全部相机及定义文件,如本场景:
场景: model/trs_so_arm100/scene_with_cube_and_cameras.xml 1 wrist_camera (parent: Fixed_Jaw, 定义于: model/trs_so_arm100/so_arm100.xml) 2 top_camera (parent: world, 定义于: model/trs_so_arm100/scene_with_cube_and_cameras.xml) 3 side_camera (parent: world, 定义于: model/trs_so_arm100/scene_with_cube_and_cameras.xml)
场景里各相机的画面长这样(左 wrist / 中 top / 右 side):

- 打开 MuJoCo viewer 查看全局场景(可鼠标旋转,方便确认机械臂姿态)
- matplotlib 窗口实时显示当前所选相机的画面 + 6 个滑块(pos x/y/z、rot x/y/z,rot 用欧拉角、单位度),左上角同步显示当前 pos / quat 数值

- 键盘微调:pos 每按 1mm、rot 每按 1 度,按住 Shift ×0.1 精调、Ctrl ×10 粗调,按住连续步进
- 调好后按 S 双击确认,直接写回相机定义所在的 XML 文件(写前自动备份 .bak,写后重新加载校验)
实现要点
- 离屏渲染:创建一个隐藏的 GLFW 窗口,配合 MjvScene + MjrContext,走 mjv_updateScene → mjr_render → mjr_readPixels 拿到相机图像(640×480);场景冻结在初始 qpos(不跑 mj_step),预览稳定不漂
- matplotlib 使用 TkAgg 后端,与 MuJoCo 的 GLFW 窗口互不冲突
- 键盘监听用 pynput 全局监听(调参期间请勿在其他窗口打字,Esc 退出)
- 写回时只正则替换单行 标签内的 pos / quat 属性值,文件其余字节不动,避免破坏 XML 里的注释和其他内容
使用方法
uv run python tune_camera.py uv run python tune_camera.py --scene model/xxx/scene.xml uv run python tune_camera.py --scene scene.xml --camera top_camera uv run python tune_camera.py --scene scene.xml --pos-range 0.3 \ --home-q 0,-1.57,1.57,1.57,-1.57,0
按键说明:
|------------------|----------------------|
| 按键 | 功能 |
| A/D、Q/E、R/F | pos X/Y/Z 步进(1mm) |
| T/G、Y/H、U/J | rot X/Y/Z 步进(1度) |
| Shift / Ctrl(按住) | 步进 ×0.1 精调 / ×10 粗调 |
| 1..N / Tab | 切换相机 / 循环切换 |
| S(双击) | 保存并写回 XML(写前备份 .bak) |
| N | 取消保存 |
| Home | 复位当前相机到 XML 原始值 |
| Esc | 退出 |
实际例子:so_arm100 的三个相机
以工程中的 so_arm100 场景为例,wrist_camera 挂在夹爪 body(Fixed_Jaw)下,top_camera / side_camera 固定在 world 下:
<!-- model/trs_so_arm100/so_arm100.xml --> <camera name="wrist_camera" pos="-0.0029 -0.0001 0.3371" quat="1.0000 -0.0004 0.0000 0.0000"/> <!-- model/trs_so_arm100/scene_with_cube_and_cameras.xml --> <camera name="top_camera" pos="0.15 -0.15 0.5" quat="0.707 0 0 0.707" mode="fixed"/> <camera name="side_camera" pos="0.4063 -0.1426 0.4585" quat="0.6530 0.2713 0.2713 0.6530" mode="fixed"/>
top_camera 调好后的画面(夹爪 + 方块都在视野内):

启动后工具默认优先选中挂在非 world body 上的相机(一般也就是要调的"安装位置")。调整到满意后按 S,会先打印即将写入的内容:
================================================== 保存 将写入 model/trs_so_arm100/so_arm100.xml: <camera name="wrist_camera" pos="0.0500 0.0300 0.0600" quat="0.9870 0.0810 0.0000 0.1370"/> 保存 3 秒内再按 S 确认写入,按 N 取消
确认后再按 S,即完成写回。这样把"盲调四元数"变成了"看着画面调滑块",腕部相机一般一两分钟就能调好。
参考
- MuJoCo camera XML 属性:XML Reference - MuJoCo Documentation