MirroS 提出的 Code-as-World 思路,是把一段真实世界的视频重写为可执行的 MuJoCo 物理程序。通俗地说,输入一段真实操作画面,经过解析、推理和生成后,系统会输出一个由 MJCF 模型和 Python 控制代码组成的仿真程序,让视频里的物理过程可以在 MuJoCo 引擎中重新跑一遍,并且可以被观察、修改和反复执行。这个思路的价值不只是“用仿真复现画面”,而是把“世界”从一个隐式的黑盒模型,变成一份显式、可读、可改、可组合的代码。本文会围绕这条主线展开:先讲清楚 Code-as-World 到底在解决什么问题,再说明为什么 MuJoCo 适合作为执行引擎,接着给出 Windows、WSL、Ubuntu 下的完整安装步骤,然后用一个最小物理程序演示如何让视频里的场景落地为可运行代码,最后补充常见问题排查、机械臂仿真场景和工程化发布清单。适合正在做机器人仿真、物理引擎应用、视频理解和强化学习环境搭建的开发者阅读。
1. 理解 Code-as-World:为什么“视频变成程序”是有价值的方向
1.1 通俗理解:先看视频,再写一个能模仿它的物理程序
人看一段机器人推方块视频时,能自然而然地理解“哪个物体在动、哪个物体静止、接触发生在什么时候、方块为什么最终停在那里”。但计算机要做到同样的事,传统做法通常是动作识别、语义分割或视频描述,输出结果是标签、文本或检测框。这些结果能回答“视频里发生了什么”,却无法回答“如何让这件事在一个物理引擎里重新发生”。
Code-as-World 换了一个角度:把视频内容直接转写成物理程序。程序里包含物体的尺寸、质量、摩擦系数、关节位置、驱动器类型和控制时序。仿真程序运行时,每一步都遵循动力学方程和接触模型,物理过程不再依赖神经网络的黑盒预测,而是由 MuJoCo 根据真实物理规则一步步推出来。
这里的核心判断是:理解一个动态场景,不等于能用语言描述它,而等于能把它重新“演”出来。能够重新演出来的程序,才是可验证的理解。
1.2 三层含义:世界是代码,代码是世界模型,程序可执行
Code-as-World 这个名称可以从三个层面拆开看。
第一层,世界被表示为代码。视频中的场景对象在程序里体现为 MJCF 模型中的<body>、<geom>、<joint>节点,物体的物理属性体现为mass、friction、size等参数,动态过程体现为控制器代码。也就是说,世界不是被“记录”在视频帧里,而是被“书写”成一段结构化代码。
第二层,代码本身就是世界模型。传统的世界模型通常是指一个神经网络,输入状态和动作,预测下一帧状态。Code-as-World 把世界模型替换成可解释的物理代码。摩擦系数设成 0.8 还是 0.2,关节是铰链还是滑动,都会直接决定仿真行为。因为一切都写在代码里,所以模型可以被检查、被修改,甚至被拆开与其他程序组合。
第三层,程序是可执行的。生成的代码通过 MuJoCo 的mj_step逐步推进,输出一条完整的轨迹。这条轨迹可以和真实视频逐帧对比,误差大了就反向调整参数,误差小了就说明程序已经能复现原视频的物理过程。
理解这三层,就明白为什么这个方向值得关注:它把“视觉理解”和“物理仿真”两个领域连了起来。
1.3 与端到端神经网络世界模型的取舍差异
面向动态场景建模,还存在另一条路线:用大量视频训练一个端到端的世界模型,输入当前帧和动作,直接预测下一帧像素或隐状态。这类模型的优势是端到端可微分、部署形态统一,但缺点也明显:中间过程不可解释、状态维度大、仿真步长受渲染限制,而且修改一个物理规律非常困难。
Code-as-World 选择了相反的取舍:先通过视觉感知提取结构化信息,再用物理引擎执行生成程序。代价是流程变长,误差会在视频解析、物理参数推断、代码生成等多个环节累积;收益是生成结果具有确定性和可调试性。仿真中如果发现物体运动不符合常识,可以直接检查参数,而不是去猜神经网络内部发生了什么。
对工程应用来说,可调试性往往比端到端训练更容易落地。生成出来的 MuJoCo 程序还能直接用于策略训练、数据增强和复盘分析,这是纯视觉模型难以直接提供的能力。
2. MuJoCo 为什么适合当“物理世界”的执行引擎
2.1 MuJoCo 的核心定位
MuJoCo 的全称是 Multi-Joint dynamics with Contact,即多关节接触动力学仿真器。它最初用于研究多关节系统与接触交互,后来逐步成为机器人强化学习研究中最常用的物理引擎之一。MuJoCo 的 Python 绑定通过 pip 分发,安装简单,免除了传统仿真环境里单独下载二进制、配置许可证的繁琐流程。
对比其他物理引擎,MuJoCo 的几个特性非常契合 Code-as-World 的目标。一是接触模型平滑:它把接触力和摩擦力做平滑近似,数值稳定性好,相同步长下不容易发散。二是求解速度快:单步求解成本低,适合批量和长时间仿真。三是声明式建模:MJCF 的 XML 描述方式天然适合程序生成,自动生成的文本结构可以直接作为模型输入。
在 Code-as-World 链路里,MuJoCo 不只是渲染器,而是“世界执行器”。生成的程序在 MuJoCo 中逐帧推演,产生可量化的轨迹,因此它充当了生成结果的验证器和物理规则的编译器。
2.2 MJCF:用声明式 XML 描述物理世界
MJCF 是 MuJoCo 的模型描述格式,用 XML 组织。一个 MJCF 文件可以在十几行内描述出一个包含刚体、关节、执行器、接触和传感器的物理系统。常用标签和作用如下。
| 标签 | 作用 | 关键属性示例 |
|---|---|---|
<mujoco> | 模型根节点 | model指定模型名 |
<compiler> | 编译选项 | angle角度单位、coordinate坐标系 |
<option> | 全局物理选项 | gravity重力、timestep仿真步长 |
<worldbody> | 世界根体,所有刚体挂在下面 | 无 |
<body> | 刚体定义 | name、pos、quat |
<joint> | 关节自由度 | type、axis、range、limited |
<geom> | 几何碰撞体 | type、size、mass、friction |
<actuator> | 驱动器 | motor、position、general |
<sensor> | 传感器 | accelerometer、gyro、framepos |
理解 MJCF 的核心在于层级关系:<worldbody>是绝对坐标系,其下的<body>通过<joint>相对父体运动,<geom>决定碰撞外形。这种树状结构非常适合自动生成:一个视频里的推杆、方块和地面,可以很容易映射成对应的 body 和 geom。
2.3 MjModel、MjData 与 mj_step:静态结构和动态状态
使用 MuJoCo Python API 时,最需要区分的是两个对象:MjModel和MjData。
MjModel是静态模型。它保存几何、惯性、关节约束、执行器参数等编译结果,在仿真过程中不变化。MjData是动态状态。它保存当前时刻的位置qpos、速度qvel、加速度、接触力、执行器输入和时间time。MjData会随着每一步mj_step被更新。
这种设计的好处是模型可以被多个仿真实例共享。比如同一份生成的 MJCF,可以同时开 100 个MjData跑 100 条不同控制轨迹,内存开销小,批量训练方便。调用mj_step(model, data)一次,仿真就往后推进一个固定时间步,时间步大小由<option timestep>决定。
注意:不要只验证程序能启动,还要确认
qpos和qvel在按预期变化。一个常见的假象是模型加载成功但仿真没有任何物理响应,多半是关节被锁定、执行器力矩为 0 或重力被关闭。
2.4 最小 MJCF 模型示例
下面是一个最简单的“方块落地”模型,用来验证环境是否可用。方块从高度 0.6 米处由静止释放,在重力作用下落到地面并保持静止。
<mujoco model="box_slide"> <compiler angle="degree" coordinate="local"/> <option gravity="0 0 -9.81" timestep="0.002"/> <worldbody> <geom name="floor" type="plane" size="2 2 0.1" friction="0.8"/> <body name="box" pos="0 0 0.6"> <freejoint/> <geom name="box_geom" type="box" size="0.2 0.2 0.2" mass="1.0" friction="0.8"/> </body> </worldbody> </mujoco>这里freejoint给方块提供 3 个平移自由度和 4 个旋转自由度,仿真时方块可以自由下落和翻滚。timestep="0.002"表示每步推进 2 毫秒,即每秒推进 500 步。后面的验证代码都会基于这个模型。
3. 安装 MuJoCo:Windows、WSL 与 Ubuntu 环境配置
3.1 用 pip 安装官方 Python 包
现在安装 MuJoCo 最直接的方式是安装官方 Python 包。从 MuJoCo 2.3.0 开始,DeepMind 将原生库和 Python 绑定一起打包发布在 PyPI 上,一条命令即可完成安装,不需要再单独申请 mjkey 许可证文件。
pip install mujoco python -c "import mujoco; print(mujoco.__version__)"安装完成后,第二条命令如果能打印出版本号,说明 Python 绑定已经可以导入。当前较新的 mujoco 包对 Python 版本有要求,落地前先确认自己的 Python 版本在包元数据声明的范围内。
这里特别提醒:不要再使用旧版mujoco-py包。mujoco-py是早期的第三方绑定方案,需要本地编译 C++ 代码,在新版本 Python 和较新 MuJoCo 下经常出现编译失败或 API 不一致的问题。新项目统一使用pip install mujoco即可。
3.2 Windows 11 下的配置要点
在 Windows 11 上安装 mujoco Python 包,整体比较简单。需要在系统中确认两件事。
第一,Python 安装是否正常。推荐从 python.org 下载官方安装包,安装时勾选“Add Python to PATH”,避免命令行找不到 python。也可以用 Conda 环境,便于隔离依赖。
第二,是否安装了 Microsoft Visual C++ Redistributable。Windows 下如果 import mujoco 时报vcruntime140.dll或MSVCP140.dll缺失,基本就是缺少 VC++ 运行库。到微软官网下载 x64 版本的 Visual C++ Redistributable 安装即可。
安装完成后,直接在命令行进入 Python,导入 mujoco,再运行一次离屏渲染验证。Window 11 桌面环境可以使用本机 OpenGL 渲染,实时查看器可以直接弹出窗口。
3.3 WSL2 / Ubuntu 下的配置要点
WSL2 和原生 Ubuntu 是机器人仿真开发最常见的两类环境。两者的关键问题是系统缺少 OpenGL 基础库,导致导入 mujoco 时提示libGL.so.1: cannot open shared object file。
在 Ubuntu 或 WSL2 里,先安装基础依赖,再安装 mujoco:
sudo apt update sudo apt install -y libgl1 libgl1-mesa-dev libglu1-mesa-dev libglew-dev pip install mujocoWSL2 用户还要注意图形显示。Windows 11 自带的 WSLg 可以让 WSL2 里的应用直接显示 GUI 窗口,前提是系统满足要求且 WSL2 已启用。检查方法:
echo $DISPLAY如果DISPLAY为空,说明 WSLg 没有接管图形环境,实时查看器无法弹出。可以在 Windows 10 上安装 X Server 并通过环境变量把 DISPLAY 指向 Windows 主机,也可以直接改用离屏渲染完成验证。
3.4 验证安装:能用离屏渲染才算真的装好
导入成功并不代表仿真环境完整。很多问题在渲染阶段才会暴露,因此安装后建议做一次离屏渲染验证。将上面保存的box_slide.xml放在当前目录,运行下面代码:
import mujoco from mujoco import Renderer model = mujoco.MjModel.from_xml_path("box_slide.xml") data = mujoco.MjData(model) renderer = Renderer(model, height=240, width=320) for _ in range(50): mujoco.mj_step(model, data) renderer.update_scene(data) image = renderer.render() print("render image shape:", image.shape)如果正常输出(240, 320, 3),说明仿真步进和渲染管线都可用。此时环境才算真正配置完成,可以进入后面的代码示例。
4. 写一个最小可执行的 MuJoCo 物理程序
4.1 项目结构与文件分工
把示例整理成一个小项目,方便后续扩展。推荐目录结构如下:
mujoco_demo/ ├── box_slide.xml ├── simulate.py ├── view.py └── render_verify.pybox_slide.xml是物理模型;simulate.py是无界面仿真入口;view.py是实时可视化入口;render_verify.py是离屏渲染验证脚本。这种分离方式在真实项目里也适用:模型文件、仿真逻辑、渲染逻辑各自独立,便于回归测试。
4.2 无界面仿真:只算状态,不弹窗口
在训练或批处理场景,通常不需要弹窗,只需要数据。simulate.py演示了如何执行 4 秒仿真,并打印方块的高度变化。
import mujoco model = mujoco.MjModel.from_xml_path("box_slide.xml") data = mujoco.MjData(model) TOTAL_STEPS = 2000 # 4 秒,timestep 为 0.002 秒 for i in range(TOTAL_STEPS): mujoco.mj_step(model, data) if i % 500 == 0: print(f"t={data.time:.3f}s z={data.qpos[2]:.4f}") print("final time:", data.time) print("final z:", data.qpos[2])这里data.qpos[2]是方块在世界坐标系下的 z 坐标。由于freejoint的前 3 个量是位置,方块刚体中心高度可以直接通过该索引读取。运行后能看到高度从 0.6 不断下降,最后稳定在 0.2 附近,也就是方块半边长对应的高度。
4.3 实时查看器:观察物理过程
如果要观察真实运动过程,使用 MuJoCo 的实时查看器。view.py通过launch_passive打开一个同步查看窗口。
import mujoco import mujoco.viewer model = mujoco.MjModel.from_xml_path("box_slide.xml") data = mujoco.MjData(model) with mujoco.viewer.launch_passive(model, data) as viewer: while viewer.is_running(): mujoco.mj_step(model, data) viewer.sync()launch_passive会创建窗口,但不会自动推进仿真。因此循环里必须自己调用mj_step,再调用viewer.sync()把最新状态同步到渲染线程。如果后续加入控制逻辑,控制器也是在循环内、mj_step之前执行,这与强化学习环境的标准交互方式一致。
4.4 运行结果与预期输出
无界面仿真跑完后,预期输出类似:
t=0.000s z=0.6000 t=1.000s z=0.2001 t=2.000s z=0.2000 t=3.000s z=0.2000 final time: 4.000 final z: 0.2000如果 z 始终不变,说明模型没有响应重力,需要检查<option gravity>是否被设置为 0,或者<freejoint/>是否被遗漏。如果 z 出现负值或 NaN,说明仿真发散,需要减小timestep,比如从 0.002 改成 0.0005。
实时查看器打开后,窗口里应该看到方块从空中落下、与地面接触后轻微弹跳、最终静止。这个最小闭环跑通后,才建议进入 Code-as-World 的生成链路研究。
5. 从真实视频到 MuJoCo 程序:Code-as-World 的参考实现链路
5.1 五步链路:从视频帧到可执行程序的必经环节
MirroS 这类 Code-as-World 系统,实际落地时通常不是一步直接生成代码,而是分成五个可独立验证的环节。
第一步是视频解析。对输入视频抽帧,检测每一帧里的目标物体,利用关键点模型估计人的姿态或机械臂关节角度,同时输出物体的 2D 检测框和类别。这一步的产物是“带语义标签的逐帧观测”。
第二步是运动重建。结合相机标定参数和 2D 关键点,把逐帧观测转换到三维空间,恢复物体在世界坐标系下的位置和姿态轨迹。这里需要处理遮挡、相机畸变和尺度不确定性问题,输出是“逐帧 3D 轨迹”。
第三步是结构识别。根据物体的运动耦合关系推断物理结构。哪些物体被固定在机器人末端,哪些物体通过铰链连接,哪些物体只是自由堆叠,哪些物体之间发生过接触。这个过程决定生成的 MJCF 树形结构,是整个链路里最依赖领域知识的一环。
第四步是物理参数推断。根据轨迹反推质量、惯性、摩擦系数、关节阻尼、执行器力限等参数。工程上常用系统辨识或逆动力学求解。参数不准会直接导致仿真轨迹和真实视频漂移。
第五步