LeRobot入门教程 09:LeRobot 仿真 (LIBERO)
1. LIBERO 是什么
LIBERO 是机器人操作领域常用的仿真基准。它在 MuJoCo(通过 robosuite)里搭出厨房桌面场景,让 Franka Panda 机械臂完成「把白色马克杯放到左边盘子」这类操作,并配好了 40 条语言指令和对应的专家演示。
好处是:不用真机、不用机械臂、不用摄像头,一台带 GPU 的服务器就能把整条链路跑通。
2. 四个 suite
LIBERO 按难度分四个 suite,LiberoEnv(task=...) 里的 task 就是选 suite:
| suite | 任务特点 | 单条 episode 最长步数 |
|---|---|---|
libero_spatial | 空间关系:同样的物体,换位置 | 280 |
libero_object | 物体泛化:换物体种类 | 280 |
libero_goal | 目标泛化:换目标状态 | 300 |
libero_10 | 长程任务,10 步以上组合 | 520 |
每个 suite 有 10 个任务(task_ids 就是选其中几个)。步数上限决定了跑一次评测最长要多久。
3. 手工搭一个环境
下面这段就是「用代码而不是命令行」建环境的写法,适合调试时看清每一步。
import numpy as np
from lerobot.envs import make_env
from lerobot.envs.configs import LiberoEnv
env_cfg = LiberoEnv(task="libero_spatial", task_ids=[0], observation_height=256, observation_width=256)
env = list(list(make_env(env_cfg, n_envs=1).values())[0].values())[0]
obs, info = env.reset()
print({k: getattr(v, "shape", None) for k, v in obs["pixels"].items()})
stderr:
[1m[33m[robosuite WARNING] [0mNo private macro file found! (__init__.py:7)
stderr:
WARNING:robosuite_logs:No private macro file found!
stderr:
[1m[33m[robosuite WARNING] [0mIt is recommended to use a private macro file (__init__.py:8)
stderr:
WARNING:robosuite_logs:It is recommended to use a private macro file
stderr:
[1m[33m[robosuite WARNING] [0mTo setup, run: python /root/miniconda3/lib/python3.12/site-packages/robosuite/scripts/setup_macros.py (__init__.py:9)
stderr:
WARNING:robosuite_logs:To setup, run: python /root/miniconda3/lib/python3.12/site-packages/robosuite/scripts/setup_macros.py
Creating LIBERO envs | suites=['libero_spatial'] | n_envs(per task)=1 | init_states=True
Restricting to task_ids=[0]
[info] using task orders [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Built vec env | suite=libero_spatial | task_id=0 | n_envs=1
Local assets not found. Downloading from HuggingFace Hub...
Assets already downloaded at /root/.cache/libero/assets
{'image': (1, 256, 256, 3), 'image2': (1, 256, 256, 3)}
4. make_env 返回的结构为什么要套两层 list()
env = list(list(make_env(env_cfg, n_envs=1).values())[0].values())[0]
看着别扭,是因为 make_env 为了支持「一次跑多个 suite、每个 suite 多个任务」,返回的是两层字典:
{ suite 名: { task_id: 向量化环境 } }
所以:
.values()拿到第一层(各 suite),[0]取第一个 suite;- 再
.values()拿到第二层(各 task),[0]取第一个 task; - 结果才是真正能
reset()/step()的那个环境。
如果以后要跑多个任务、多个并行环境,这个结构就有用了——现在为了看清,先按单任务写。
5. LiberoEnv 常用参数
| 参数 | 本例取值 | 默认值 | 说明 |
|---|---|---|---|
task | libero_spatial | libero_10 | 选哪个 suite |
task_ids | [0] | None(全部) | 只跑第 0 个任务,省时间 |
observation_height / observation_width | 256 / 256 | 360 / 360 | 图像分辨率,要和策略模型训练时一致 |
fps | — | 20 | 控制频率,必须匹配 robosuite 默认的 20 Hz |
init_states | — | True | 每次 reset 回到固定的初始状态,评测才可复现 |
hard_reset | — | True | 硬复位:重建场景,比软复位干净但慢 |
control_mode | — | relative | 动作是相对当前位姿的增量 |
camera_name | — | agentview_image,robot0_eye_in_hand_image | 两路相机 |
reset() 之后打印出来的 {'image': (1, 256, 256, 3), 'image2': (1, 256, 256, 3)} 里,第一个 1 是并行环境的数量(n_envs=1),不是 batch 维意义上的 1。
6. 两路相机和 [::-1] 这个翻转
obs["pixels"] 下的 image / image2 分别对应 agentview 和 eye_in_hand(映射关系写在 LiberoEnv.features_map 里)。
obs["pixels"]["image"][0][::-1] 里的 [::-1] 是垂直翻转。robosuite 的离屏渲染器输出的图像是上下颠倒的,直接显示会看到「天花板视角」。取第 0 个并行环境、翻转、再用 imageio 拼成 GIF:
- 这一段同时验证了三件事:MuJoCo 渲染通了、EGL 后端生效了、图像能正常取出来;
show()里用imageio.mimsave(..., format="GIF", fps=fps),新版 imageio 会提示fps已弃用、建议用duration(毫秒),只是警告,不影响输出。
import io, imageio.v2 as imageio
from IPython.display import display, Image as IPImage
def show(frames, fps=10):
b = io.BytesIO()
imageio.mimsave(b, frames, format="GIF", fps=fps)
display(IPImage(data=b.getvalue(), format="gif"))
show([obs["pixels"]["image"][0][::-1]])
stderr:
/root/miniconda3/lib/python3.12/site-packages/imageio/plugins/pillow.py:410: DeprecationWarning: The keyword `fps` is no longer supported. Use `duration`(in ms) instead, e.g. `fps=50` == `duration=20` (1000 * 1/50).
warnings.warn(

光看一帧不够,得让环境真的动起来:
7. 用一个随机策略跑 30 步
光看一帧不够,得让环境真的动起来:
frames = [obs["pixels"]["image"][0][::-1].copy()]
for _ in range(30):
a = np.random.uniform(-1, 1, (1,) + env.single_action_space.shape).astype(np.float32)
obs, *_ = env.step(a)
frames.append(obs["pixels"]["image"][0][::-1].copy())
show(frames)
env.close()

这段做了三件事:
frames先装入复位后的第一帧;- 循环 30 次,每次从动作空间均匀采样一个动作
np.random.uniform(-1, 1, ...)——这是随机乱动,不是智能行为,机械臂会在桌面上瞎戳; - 每步后把画面存下来,最后拼成 GIF。
为什么先跑随机动作:这是环境自检的标准动作。如果随机策略都能让画面连续变化、机械臂跟着动,说明仿真、渲染、动作空间都对上了。等下一册换成真策略,画面会明显「有目的」。
env.single_action_space.shape 是 (7,),和数据集里 action 的 7 维对上了——所以同一个策略模型既能吃真实数据,也能吃这个仿真环境。
最后 env.close() 释放 MuJoCo 的渲染上下文。忘了 close 会泄漏显存/内存,连续开几个环境就容易崩。
8. 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
一堆 [robosuite WARNING] No private macro file found! | robosuite 找不到可选的宏配置文件 | 无害,可忽略;想消掉就跑一次它提示的 setup_macros.py |
Local assets not found. Downloading... | 第一次运行要下 LIBERO 资产 | 正常,需要走加速通道(07 册《环境与网络》);下完会打印缓存路径 |
| 画面上下颠倒 | robosuite 离屏渲染的坐标系 | 取图时 [::-1] 翻转 |
| 开几个环境后显存爆了 | 忘了 env.close() | 每个环境用完就关,或用 with 管理 |