整体目标
构建适用于 GR00T N1.5 微调的视觉-动作模仿学习数据集,任务为“Put the yellow and white mug in the microwave and close it”。数据采集与处理流程分为三个阶段:
- 遥操作录制演示:在 Isaac Lab 中注册包含 Franka Panda、微波炉、双视角摄像头的遥操作环境,使用键盘控制录制带图像与动作的轨迹。
- 生成额外演示:配置与注册 Isaac Lab Mimic 兼容环境,自动生成额外的演示。
- 数据集生成与格式转换:生成数据集,并转换为 GR00T N1.5 所需的微调模仿学习输入格式(参考 LeRobot 格式)。
官方教程
阶段一:遥操作录制演示
环境结构
使用 ManagerBasedRLEnvCfg 框架,继承自 MicrowaveEnvCfg,最终配置类为 FrankaMugPlaceEnvCfgIK,其核心组件包括:
- 机器人:Franka Panda,使用高增益 PD 控制器(
FRANKA_PANDA_HIGH_PD_CFG)。 - 物体:两个刚体 Mug(porcelain_mug 与 white_yellow_mug),加载自本地 USD 文件。
- 微波炉:可动门关节(
microjoint),初始化为关闭状态。 - 观测:包含低维状态(末端位姿、夹爪位置、杯子位姿等)与双视角 RGB 图像。
- 动作:7D 相对末端位姿 + 1D 二值夹爪控制,通过差分 IK 控制器实现。
- 遥操作设备:支持键盘(
Se3KeyboardCfg)与 OpenXR 手部追踪(预留)。
基于 Manager 的环境
如上图所示,一个 Task 的组件包括:
- Interactive Scene:导入 usd 文件
- Action Manager:控制机器人关节位置
- Observation Manager:定义环境的返回值
- Termination Manager:定义任务完成的情况
- Event Manager:定义事件发生时的行动,比如说环境重置时如何初始化
具体来说:
- InteractiveSceneCfg
- AssetBaseCfg:环境
- ArticulationCfg:机器人
- ActionsCfg
- JointPositionActionCfg:对 arm action
- BinaryJointPositionActionCfg:对 gripper action
- DifferentialInverseKinematicsActionCfg:遥操作专用,传入一个控制器
- ObservationsCfg
- PolicyCfg (ObsGroup):定义需要观察的信息,比如 actions/joint_pos/joint_vel 等,通过函数形式实现
- ObsTerm:传入 func,可以自定义,返回信息在 env 中的相应变量
- SubtaskCfg (ObsGroup):定义不同任务阶段需要用到的信息,比如 grasp、stack
- PolicyCfg (ObsGroup):定义需要观察的信息,比如 actions/joint_pos/joint_vel 等,通过函数形式实现
- TerminationsCfg
- DoneTerm:传入 func,可以自定义
- EventCfg
- EventTerm:传入 func 和 mode,其中 mode 常用的为 reset,即重置时执行随机初始化
- DevicesCfg:遥操作专用
Example
IsaacLab\source\isaaclab_tasks\isaaclab_tasks\manager_based\manipulation\stack\stack_env_cfg.pyStackEnvCfg ├── scene (ObjectTableSceneCfg) │ ├── robot: ArticulationCfg │ ├── ee_frame: FrameTransformerCfg │ ├── table │ ├── plane │ ├── light │ ├── observations (ObservationsCfg) │ ├── policy (PolicyCfg) │ ├── rgb_camera (RGBCameraPolicyCfg) │ └── subtask_terms (SubtaskCfg) │ ├── actions (ActionsCfg) │ ├── arm_action: JointPositionActionCfg │ └── gripper_action: BinaryJointPositionActionCfg │ ├── terminations: TerminationsCfg ├── rewards = None (NOT RL) ├── events = None ├── commands = None ├── curriculum = None
Reference
基于上述基础配置,衍生出:
manager_based\manipulation\stack\config\franka\stack_ik_rel_visuomotor_env_cfg.py是本文的主要参考内容。
观测配置
在 ObservationsCfg.PolicyCfg 中注册图像观测项:
wrist_cam = ObsTerm(
func=mdp.image,
params={
"sensor_cfg": SceneEntityCfg("wrist_cam"),
"data_type": "rgb",
"normalize": False,
},
)
table_cam = ObsTerm(
func=mdp.image,
params={
"sensor_cfg": SceneEntityCfg("table_cam"),
"data_type": "rgb",
"normalize": False,
},
)类似地,可以在 ObservationsCfg.PolicyCfg 中注册任何你需要的观测项,主要在于自定义 func 函数。例如:
mug_positions = ObsTerm(func=mdp.mug_positions_in_world_frame)
mug_orientations = ObsTerm(func=mdp.mug_orientations_in_world_frame)
microwave_door_angle = ObsTerm(func=mdp.microwave_door_angle)对应自定义函数,步骤包括:
- 从
env中找到目标物体 - 获取目标物体信息,例如位置
data.root_pos_w、姿态data.root_quat_w、关节角度data.joint_pos - 返回,
concat并返回,数据必须是二维张量
def mug_positions_in_world_frame(
env: ManagerBasedRLEnv,
mug1_cfg: SceneEntityCfg = SceneEntityCfg("porcelain_mug"),
mug2_cfg: SceneEntityCfg = SceneEntityCfg("white_yellow_mug"),
) -> torch.Tensor:
"""The position of the cubes in the world frame."""
mug1: RigidObject = env.scene[mug1_cfg.name]
mug2: RigidObject = env.scene[mug2_cfg.name]
return torch.cat((mug1.data.root_pos_w, mug2.data.root_pos_w), dim=1)
def mug_orientations_in_world_frame(
env: ManagerBasedRLEnv,
mug1_cfg: SceneEntityCfg = SceneEntityCfg("porcelain_mug"),
mug2_cfg: SceneEntityCfg = SceneEntityCfg("white_yellow_mug"),
):
"""The orientation of the cubes in the world frame."""
mug1: RigidObject = env.scene[mug1_cfg.name]
mug2: RigidObject = env.scene[mug2_cfg.name]
return torch.cat((mug1.data.root_quat_w, mug2.data.root_quat_w), dim=1)
def microwave_door_angle(
env: ManagerBasedRLEnv,
microwave_cfg: SceneEntityCfg = SceneEntityCfg("microwave"),
joint_name: str = "microjoint",
) -> torch.Tensor:
"""Return the door joint angle (in radians) of the microwave."""
microwave: Articulation = env.scene[microwave_cfg.name]
joint_ids = microwave.find_joints(joint_name)[0]
assert len(joint_ids) == 1, f"Expected exactly one joint named '{joint_name}', found {len(joint_ids)}"
door_angle = microwave.data.joint_pos[:, joint_ids[0]].unsqueeze(-1)
return door_angle动作配置
使用差分 IK 控制器实现末端位姿控制:
self.actions.arm_action = DifferentialInverseKinematicsActionCfg(
asset_name="robot",
joint_names=["panda_joint.*"],
body_name="panda_hand",
controller=DifferentialIKControllerCfg(
command_type="pose",
use_relative_mode=True,
ik_method="dls"
),
scale=0.5,
body_offset=DifferentialInverseKinematicsActionCfg.OffsetCfg(pos=[0.0, 0.0, 0.107]),
)
self.actions.gripper_action = mdp.BinaryJointPositionActionCfg(
asset_name="robot",
joint_names=["panda_finger.*"],
open_command_expr={"panda_finger_.*": 0.04},
close_command_expr={"panda_finger_.*": 0.0},
)最终动作维度为 8:[dx, dy, dz, dqx, dqy, dqz, dqw, gripper],其中 gripper 为开合状态(0.0 关,0.04 开)。
视觉配置
Tip
可以不使用摄像头
在 FrankaMugPlaceIKEnvCfg.__post_init__() 中显式定义两个 RGB 摄像头:
# Wrist-mounted camera
self.scene.wrist_cam = CameraCfg(
prim_path="{ENV_REGEX_NS}/Robot/panda_hand/wrist_cam",
update_period=0.0,
height=200,
width=200,
data_types=["rgb"],
spawn=sim_utils.PinholeCameraCfg(
focal_length=24.0,
focus_distance=400.0,
horizontal_aperture=20.955,
clipping_range=(0.1, 2)
),
offset=CameraCfg.OffsetCfg(
pos=(0.13, 0.0, -0.15),
rot=(-0.70614, 0.03701, 0.03701, -0.70614),
convention="ros"
),
)
# Overhead table-view camera
self.scene.table_cam = CameraCfg(
prim_path="{ENV_REGEX_NS}/table_cam",
update_period=0.0,
height=200,
width=200,
data_types=["rgb"],
spawn=sim_utils.PinholeCameraCfg(
focal_length=24.0,
focus_distance=400.0,
horizontal_aperture=20.955,
clipping_range=(0.1, 2)
),
offset=CameraCfg.OffsetCfg(
pos=(1.0, 0.0, 0.4),
rot=(0.35355, -0.61237, -0.61237, 0.35355),
convention="ros"
),
)设备配置
Tip
可选,比如使用 keyboard 控制的时候,可以把敏感度调小,便于操作
self.teleop_devices = DevicesCfg(
devices={
"handtracking": OpenXRDeviceCfg(
retargeters=[
Se3RelRetargeterCfg(
bound_hand=OpenXRDevice.TrackingTarget.HAND_RIGHT,
zero_out_xy_rotation=True,
use_wrist_rotation=False,
use_wrist_position=True,
delta_pos_scale_factor=10.0,
delta_rot_scale_factor=10.0,
sim_device=self.sim.device,
),
GripperRetargeterCfg(
bound_hand=OpenXRDevice.TrackingTarget.HAND_RIGHT, sim_device=self.sim.device
),
],
sim_device=self.sim.device,
xr_cfg=self.xr,
),
"keyboard": Se3KeyboardCfg(
pos_sensitivity=0.05,
rot_sensitivity=0.05,
sim_device=self.sim.device,
),
}
)环境注册
参考 Isaac Lab 官方 manager_based\manipulation\stack\config\franka\__init__.py,需在 __init__.py 中注册环境:
gym.register(
id="Isaac-Microwave-Franka-v0",
entry_point="isaaclab.envs:ManagerBasedRLEnv",
kwargs={
"env_cfg_entry_point": f"{__name__}.microwave_env_cfg:FrankaMugPlaceIKEnvCfg",
},
disable_env_checker=True,
)需要修改 id、kwargs 中对应项为自己的任务名称和类名;
对于一个模块(或包),__name__ 属性返回它的完整导入路径——据此替换为自己的路径。
数据录制
指令
- 模板 :
python scripts/tools/record_demos.py --task <task_name> --device cpu --teleop_device <teleop_device> --dataset_file ./datasets/dataset.hdf5 --num_demos 10- 自用:
python scripts/tools/record_demos.py --task Isaac-Microwave-Franka-v0 --device cpu --enable_cameras --teleop_device keyboard --dataset_file ./datasets/dataset.hdf5 --num_demos 10- 注意事项:
- 这里
<task_name>是自己注册的任务id- 设备包括
keyboard、spacemouse、handtracking等
通过 record_demos.py 调用该环境时,HDF5 文件将包含以下关键字段:
/obs/wrist_cam:[T, 200, 200, 3], uint8/obs/table_cam:[T, 200, 200, 3], uint8/obs/eef_pos,/obs/eef_quat,/obs/gripper_pos: 低维状态/actions:[T, 8], float32/dones,/rewards: 可选,由环境自动填充
以上只是例子,obs 里包含在 ObservationsCfg 中注册的所有变量。
录制后的 HDF5 文件可通过以下方式验证结构:
h5ls -r ./datasets/dataset.hdf5例如:
/data/demo_0/obs Group
/data/demo_0/obs/actions Dataset {1608, 7}
/data/demo_0/obs/eef_pos Dataset {1608, 3}
/data/demo_0/obs/eef_quat Dataset {1608, 4}
/data/demo_0/obs/gripper_pos Dataset {1608, 2}
/data/demo_0/obs/joint_pos Dataset {1608, 9}
/data/demo_0/obs/joint_vel Dataset {1608, 9}
/data/demo_0/obs/microwave_door_angle Dataset {1608, 1}
/data/demo_0/obs/mug_orientations Dataset {1608, 8}
/data/demo_0/obs/mug_positions Dataset {1608, 6}
/data/demo_0/obs/object Dataset {1608, 37}
/data/demo_0/obs/table_cam Dataset {1608, 200, 200, 3}
/data/demo_0/obs/wrist_cam Dataset {1608, 200, 200, 3}
阶段二:生成额外演示
Isaac Lab Mimic 是如何工作的
Isaac Lab Mimic 通过将输入的示范分割成子任务来工作。子任务是示范中用户定义的、在所有示范中共有的片段。子任务的例子包括 “抓取物体” 、 “将末端执行器移动到某个预定义位置” 、 “释放物体” 等等。请注意,大多数子任务是相对于机器人与之交互的某个物体来定义的。
子任务需要被定义,然后为每个输入示例添加标注。标注可以通过定义用于子任务检测的启发式算法来实现,如上面示例中所做的那样,或者也可以手动完成。
定义并标注了子任务后,Isaac Lab Mimic 利用少量辅助方法来转换子任务片段,并通过将它们拼接在一起生成新的演示,以匹配当前的任务。
对于每个这样生成的候选演示,Isaac Lab Mimic 使用布尔成功标准来判断演示是否成功执行任务,如果成功,则将其添加到输出数据集。候选演示的成功率在简单情况下可以高达 70%,而在复杂任务和机器人本身的复杂性影响下,成功率可能低于 1%。
子任务定义
Quote
子任务是一个
SubTaskConfig对象的列表,其中最重要的成员是:
object_ref是正在交互的对象。它将用于在数据生成过程中相对于该对象调整动作。如果当前子任务不涉及任何对象,则可以是None。subtask_term_signal是指示子任务是否处于活动状态的信号的 ID。对于具有多个末端执行器的环境,可以通过指定子任务约束来强制定义末端执行器之间的子任务顺序。这些约束在
SubTaskConstraintConfig类中定义。
参考 isaaclab_mimic\envs\franka_stack_ik_rel_visuomotor_mimic_env_cfg.py:
@configclass
class FrankaMugPlaceMimicEnvCfg(microwave_env_cfg.FrankaMugPlaceIKEnvCfg, MimicEnvCfg):
"""
Isaac Lab Mimic environment config class.
"""
def __post_init__(self):
# post init of parents
super().__post_init__()
# Override the existing values
self.datagen_config.name = "isaac_lab_franka_mugplace_ik_rel_visuomotor_D0"
self.datagen_config.generation_guarantee = True
self.datagen_config.generation_keep_failed = True
self.datagen_config.generation_num_trials = 10
self.datagen_config.generation_select_src_per_subtask = True
self.datagen_config.generation_transform_first_robot_pose = False
self.datagen_config.generation_interpolate_from_last_target_pose = True
self.datagen_config.generation_relative = True
self.datagen_config.max_num_failures = 25
self.datagen_config.seed = 1
# The following are the subtask configurations for the stack task.
subtask_configs = []
# 1) Subtask: open microwave door
subtask_configs.append(
SubTaskConfig(
# Reference object: microwave door / microwave frame
object_ref="microwave",
# The binary signal defined in SubtaskCfg
subtask_term_signal="door_opened",
# Allow some random offset around the finishing time
subtask_term_offset_range=(10, 20),
# Segment selection strategy
selection_strategy="nearest_neighbor_object",
selection_strategy_kwargs={"nn_k": 3},
# Action noise for this subtask
action_noise=0.03,
# Interpolation steps to smoothly transition
num_interpolation_steps=5,
# Extra fixed steps
num_fixed_steps=0,
# Noise during interpolation?
apply_noise_during_interpolation=False,
)
)
......参考 Isaac Lab 官方 isaaclab_mimic\envs\franka_stack_ik_rel_visuomotor_mimic_env.py,需要额外实现两个函数:
get_subtask_term_signals:必要get_expected_attached_object:非必要,当使用SkillGen的时候需要定义
什么是 SkillGen?
用于“在演示数据上自动生成技能级别数据集” 的工具链。
- 把整条演示拆成多个 subtask 片段
- 在每个片段附近采样更多偏移、生成更多演示(数据增强)
- 甚至自动补帧(interpolation)生成更丰富的 transitions
- 附带 noise 来增强模仿学习鲁棒性
要用 SkillGen,必须提供:
信息 来自你哪里? 为什么需要? subtask_term_signals ObsGroup → datagen_info 用来切分演示 subtask sequence env_cfg.subtask_configs 要知道任务流程 expected attached object get_expected_attached_object 拼接片段时对齐物体状态 interpolation steps SubTaskConfig 里的参数 保证动力学一致性 action noise SubTaskConfig 里的参数 子任务增强 如果不用 SkillGen?
- 需要定义 SubtaskCfg(用于 observation)
- 需要定义 get_subtask_term_signals(用于 annotation)
- 不需要 expected_attached_object
- 不需要 subtask_configs 列表
示例:
def get_subtask_term_signals(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]:
"""
Gets a dictionary of termination signal flags for each subtask in a task. The flag is 1
when the subtask has been completed and 0 otherwise. The implementation of this method is
required if intending to enable automatic subtask term signal annotation when running the
dataset annotation tool. This method can be kept unimplemented if intending to use manual
subtask term signal annotation.
Args:
env_ids: Environment indices to get the termination signals for. If None, all envs are considered.
Returns:
A dictionary termination signal flags (False or True) for each subtask.
"""
if env_ids is None:
env_ids = slice(None)
signals = dict()
subtask_terms = self.obs_buf["subtask_terms"]
signals["door_opened"] = subtask_terms["door_opened"][env_ids]
signals["reach"] = subtask_terms["reach"][env_ids]
signals["grasp"] = subtask_terms["grasp"][env_ids]
return signals
def get_expected_attached_object(self, eef_name: str, subtask_index: int, env_cfg) -> str | None:
"""
(SkillGen) Return the expected attached object for the given EEF/subtask.
Assumes 'stack' subtasks place the object grasped in the preceding 'grasp' subtask.
Returns None for 'grasp' (or others) at subtask start.
"""
if eef_name not in env_cfg.subtask_configs:
return None
subtask_configs = env_cfg.subtask_configs[eef_name]
if not (0 <= subtask_index < len(subtask_configs)):
return None
current_cfg = subtask_configs[subtask_index]
if "place" in str(current_cfg.subtask_term_signal).lower() or "place" in current_cfg.object_ref.lower():
# need previous subtask
if subtask_index > 0:
prev_cfg = subtask_configs[subtask_index - 1]
if "grasp" in str(prev_cfg.subtask_term_signal).lower():
return prev_cfg.object_ref
return None子任务标注
指令
python scripts/imitation_learning/isaaclab_mimic/annotate_demos.py --device cpu --enable_cameras --task Isaac-Microwave-Franka-Mimic --auto --input_file ./datasets/dataset.hdf5 --output_file ./datasets/annotated_dataset.hdf5
Quote
一旦子任务被定义,它们需要在源数据中进行标注。有两种方法可以标注源示范的子任务边界:手动标注或使用启发式方法。
通常手动标注是最简单的,因为输入示例的数量通常非常少。要执行手动标注,请使用
annotate_demos.py脚本,且不带--auto标志。然后按B暂停,按N继续,按S标注子任务边界。为了更精确的边界,或为了加速对给定任务的重复处理以进行实验,可以实现启发式方法来执行相同的任务。启发式方法是对环境中的观测。如何添加子任务项的示例可以在
source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/stack/stack_env_cfg.py中找到,其中它们作为一个观测组被添加,名为SubtaskCfg。这个示例使用了预构建的启发式方法,但自定义启发式方法也很容易实现。
如果使用启发式方法标注,定义的子任务必须按照固定顺序完成。
否则报错:
ValueError: subtask termination signal is not increasing: 136 should be greater than 802
示例(custom\microwave_env_cfg.py):
@configclass
class ObservationsCfg:
"""Observation specifications for the MDP."""
......
@configclass
class SubtaskCfg(ObsGroup):
"""Observations for subtask group."""
door_opened = ObsTerm(
func=mdp.microwave_door_opened,
params={
"microwave_cfg": SceneEntityCfg("microwave"),
"open_threshold": 0.4,
},
)
......
def __post_init__(self):
self.enable_corruption = False
self.concatenate_terms = False环境注册
参考 isaaclab_mimic\isaaclab_mimic\envs\__init__.py:
gym.register(
id="Isaac-Microwave-Franka-Mimic",
entry_point=f"{__name__}.microwave_mimic_env:FrankaMugPlaceIKMimicEnv",
kwargs={
"env_cfg_entry_point": FrankaMugPlaceMimicEnvCfg,
},
disable_env_checker=True,
)其中 id、entry_point 和 env_cfg_entry_point 都需要对应修改为自实现的名称;
FrankaMugPlaceMimicEnvCfg 是从对应文件直接导入的。
数据生成
指令
python scripts/imitation_learning/isaaclab_mimic/generate_dataset.py --device cpu --enable_cameras --num_envs 10 --generation_num_trials 10 --input_file ./datasets/annotated_dataset.hdf5 --output_file ./datasets/generated_dataset_small.hdf5
阶段三:数据集生成与格式转换
参考 Hugging Face Dataset 如 youliangtan/so101-table-cleanup,每条 episode 应包含:
observation.image.wrist:[T, H, W, 3]或 JPEG 序列observation.image.overhead: 对应table_camaction:[T, 8]language_instruction: 字符串(可选,但 GR00T 支持多模态指令)episode_id,frame_index: 元数据
为了适配主流策略,建议遵循以下命名约定:
- 图像观测:
observation.images.camera_name(如wrist_cam,table_cam)。 - 机器人状态:
observation.state(对应关节角度或末端位姿)。 - 动作:
action(对应机械臂目标位置或增量)。 - 任务指令:
task(字符串,模型通过此字段理解语义)。
环境配置
建议重新开一个环境,不要和 isaaclab 混在一起,依赖包有版本冲突
conda create -n env_lerobot python=3.10 conda activate env_lerobot pip install lerobot h5py这里使用 0.4.2 版本
转换
- 使用 LeRobot 官方 Python API 转换:创建一个空的
LeRobotDataset实例,然后循环读取 IsaacLab 的 HDF5 数据并将其“添加”进去 - 注意:
repo_id要自己设置- features 设置可以根据自己的想法调整
- payload 要根据自己的 HDF5 调整
import h5py
import shutil
import os
import numpy as np
from pathlib import Path
from lerobot.datasets.lerobot_dataset import LeRobotDataset
def convert_isaac_to_lerobot(hdf5_path, output_dir):
# 1. 彻底清理旧目录
if os.path.exists(output_dir):
print(f"正在清理旧目录: {output_dir}")
shutil.rmtree(output_dir)
f = h5py.File(hdf5_path, 'r')
demos = list(f['data'].keys())
task_label = "Put the yellow and white mug in the microwave and close it"
# 2. 创建数据集 (注意 features 的变化)
dataset = LeRobotDataset.create(
repo_id="your_name/franka_microwave",
fps=30,
root=output_dir,
robot_type="franka",
features={
"observation.images.table_cam": {"dtype": "video", "shape": (200, 200, 3), "names": ["height", "width", "channels"]},
"observation.images.wrist_cam": {"dtype": "video", "shape": (200, 200, 3), "names": ["height", "width", "channels"]},
"observation.state": {
"dtype": "float32",
"shape": (9,),
"names": [f"joint_{i}" for i in range(9)] # 确保 names 长度为 9
},
"action": {
"dtype": "float32",
"shape": (7,),
"names": [f"action_{i}" for i in range(7)] # 确保 names 长度为 7
},
},
use_videos=True,
)
for demo_name in demos:
print(f"正在转换回话: {demo_name}...")
demo_group = f['data'][demo_name]
num_frames = demo_group['obs/joint_pos'].shape[0]
for i in range(num_frames):
# 3. 构造 payload,这里必须带 task
payload = {
"observation.images.table_cam": demo_group['obs/table_cam'][i],
"observation.images.wrist_cam": demo_group['obs/wrist_cam'][i],
"observation.state": demo_group['obs/joint_pos'][i],
"action": demo_group['actions'][i],
"task": task_label, # 每一帧都带上指令,LeRobot 会自动提取到 meta/tasks.jsonl
}
dataset.add_frame(payload)
# 保存一个 episode
dataset.save_episode()
# 4. 使用 finalize() 保存
dataset.finalize()
f.close()
print(f"\n转换成功!数据已存至: {output_dir}")
if __name__ == "__main__":
convert_isaac_to_lerobot("./datasets/dataset.hdf5", "./datasets/lerobot_dataset")验证
注意:加载本地数据需同时指定 repo_id 和 root
from lerobot.datasets.lerobot_dataset import LeRobotDataset
# 加载已经转换好的本地数据
dataset = LeRobotDataset(
repo_id="your_name/franka_microwave",
root="./datasets/lerobot_dataset"
)
print(f"总帧数: {len(dataset)}")
print(f"Episode 总数: {dataset.num_episodes}")
# 检查任务指令是否正确存入
if hasattr(dataset.meta, 'tasks'):
print(f"任务指令: {dataset.meta.tasks}")
# 检查第一帧和最后一帧,确保视频和数据可访问
print(f"第一帧数据样本 (Action): {dataset[0]['action']}")
print(f"最后一帧索引: {len(dataset) - 1}")LeRobot 提供了一个非常方便的可视化工具,可以检查转换后的视频和动作轨迹是否对齐:
python -m lerobot.scripts.lerobot_dataset_viz --repo-id your_name/franka_microwave --root ./datasets/lerobot_dataset --episode-index 0这会启动一个浏览器窗口,可以逐帧检查图像是否清晰,以及动作曲线是否平滑。
检查动作是否对齐:
- 在下方的图表区域,展开
action。 - 关键动作点:拖动时间轴到机械臂刚好触碰到杯子的那一刻。
- 验证:此时观察
action曲线。如果动作曲线刚好在此时发生剧烈变化(尤其是代表夹持器的那一维),说明图像与动作是同步的。 - 如果视频里夹持器还没动,曲线就已经跳变了,说明转换时的帧率(FPS)设置不对。
检查状态完整性:
- 检查
observation/state。 - 确认它包含 9 个维度的曲线,且曲线是平滑的连续值,而不是全 0 或断断续续的死线。
多任务集成与泛化性提升
为了微调出更强大的 GR00T 1.5,建议将不同的原子任务合并到同一个数据集文件夹中。
为什么合并任务?
- 技能共享:模型可以学习到在不同任务中通用的“抓取”、“避障”和“识别物体”的能力。
- 防止过拟合:多任务训练强迫模型理解
task指令,而不是死记硬背某一个任务的固定轨迹。- 语义泛化:混合 “Open the door” 和 “Close the door” 可以帮助模型理解动词的语义差别。
只需修改主循环,遍历不同的 HDF5 文件并赋予不同的 task_label,最后统一调用一次 finalize():
# 伪代码:多任务批量处理
task_list = [
{"file": "pick_cup.hdf5", "label": "Pick up the mug"},
{"file": "move_to_oven.hdf5", "label": "Put the mug in the microwave"}
]
for item in task_list:
# 加载 HDF5 ...
# 每一帧:payload["task"] = item["label"]
# 每结束一个文件:dataset.save_episode()
dataset.finalize() # 统一计算所有任务的均值方差混合数据注意点
- 动作空间一致性:确保合并的所有任务
action含义相同。- 数据平衡:避免任务 A 有 1000 帧而任务 B 只有 10 帧,这会导致模型忽略弱势任务。
- 视觉多样性:在 IsaacLab 录制时,建议随机化桌子颜色、微波炉位置和光照。
