ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ManiSkill 轨迹与视频录制指南:RecordEpisode Wrapper 与自定义渲染采集详解

ManiSkill 轨迹与视频录制指南:RecordEpisode Wrapper 与自定义渲染采集详解 ManiSkill 轨迹与视频录制指南RecordEpisode Wrapper 与自定义渲染采集详解【免费下载链接】ManiSkillManipulation Skill Framework, an open source GPU parallelized robotics simulator and benchmark项目地址: https://gitcode.com/GitHub_Trending/ma/ManiSkill本文是 ManiSkill 官方录制方案的技术指南围绕docs/source/user_guide/wrappers/record.md展开系统讲解如何利用RecordEpisodeWrapper 在单环境与 GPU 向量化环境中录制视频与轨迹数据并深入解析底层的 HDF5/JSON 数据格式、渲染采集 API 与源码实现原理。读完本文你将掌握 ManiSkill 中最规范的演示数据产出方式能够直接用于生成模仿学习训练集、强化学习结果录像与可视化素材。概述ManiSkill 提供的两条录制路径ManiSkill 为任务单环境与向量化环境提供了两种录制视频/轨迹的方式推荐方案RecordEpisodeWrapper—— 一个开箱即用的 Gymnasium Wrapper同时支持单环境与向量化环境能够把视频与/或轨迹数据以 ManiSkill 格式写入磁盘。它保存的是环境创建时指定的render_mode对应的画面rgb_array、sensors或两者合并的all。自定义方案Capture Individual Images—— 直接调用env.render_rgb_array()、env.render_sensors()等 API 自行逐帧采集图像再自己用 FFmpeg/imageio 等工具合成视频适用于有定制录制需求的场景。两种方式对单环境与向量化环境都适用。下文先讲推荐方案再讲自定义采集最后从源码层解析录制格式与实现原理。一、RecordEpisode Wrapper推荐的录制方案RecordEpisode定义于 mani_skill/utils/wrappers/record.py它继承自gym.Wrapper。其源码文档注释明确给出一个重要使用原则You generally should always apply this wrapper last, particularly if you include observation wrappers which modify the returned observations. The only wrappers that may go after this one is any of the vector env interface wrappers that map the maniskill env to a e.g. gym vector env interface.即RecordEpisode应当作为最后一个最外层Wrapper 应用尤其当你使用会修改观测的观测类 Wrapper 时。只有将 ManiSkill 环境映射为 Gym 向量接口的向量环境 Wrapper如ManiSkillVectorEnv可以包在它外面。这一点在所有官方示例如 examples/baselines/ppo/ppo.py、examples/baselines/sac/sac.py与测试用例tests/test_wrappers.py中都有体现。1.1 单环境录制默认在 reset 时落盘对于单环境Wrapper 默认在每次env.reset()时保存上一段轨迹与视频import mani_skill.envs import gymnasium as gym from mani_skill.utils.wrappers.record import RecordEpisode env gym.make(PickCube-v1, num_envs1, render_modergb_array) env RecordEpisode(env, output_dirvideos, save_trajectoryTrue, trajectory_nametrajectory, save_videoTrue, video_fps30) env.reset() for _ in range(200): obs, reward, terminated, truncated, info env.step(env.action_space.sample()) if terminated or truncated: env.reset()要点解读必须先import mani_skill.envs任务才会注册进gymnasium.make。render_modergb_array决定录制画面的来源若设为sensors或all视频内容会相应改变详见第三节。trajectory_nametrajectory会生成videos/trajectory.h5与videos/trajectory.json两个文件不传该参数时源码会以时间戳命名time.strftime(%Y%m%d_%H%M%S)。save_trajectoryTrue时 Wrapper 在__init__中立刻用h5py以w模式创建.h5文件并在内存中维护轨迹缓冲_trajectory_buffer。单环境默认save_on_resetTrue因此在每次reset()时reset()内部会先调用flush_video()与flush_trajectory()把上一段数据写入磁盘这意味着最后一个 episode 需要等env.close()时才会落盘close()中同样会执行 flush。1.2 向量化环境录制按max_steps_per_video分段落盘对于 GPU 并行化的向量化环境num_envs 1Wrapper不在 reset 时保存视频而是每积累max_steps_per_video步就 flush 一段视频到磁盘并开启新视频。原因在于向量化环境存在部分重置partial reset——某些子环境可能中途被单独重置此时在 reset 时切分视频的语义会变得模糊。import mani_skill.envs import gymnasium as gym from mani_skill.utils.wrappers.record import RecordEpisode from mani_skill.vector.wrappers.gymnasium import ManiSkillVectorEnv N 4 env gym.make(PickCube-v1, num_envsN, render_modergb_array) env RecordEpisode(env, output_dirvideos, save_trajectoryTrue, trajectory_nametrajectory, max_steps_per_video50, video_fps30) env ManiSkillVectorEnv(env, auto_resetTrue) # adds auto reset env.reset() for _ in range(200): obs, reward, terminated, truncated, info env.step(env.action_space.sample())注意这里RecordEpisode在内层、ManiSkillVectorEnv在外层符合向量接口 Wrapper 可以包在录制 Wrapper 外面的约定。ManiSkillVectorEnv来自 mani_skill/vector/wrappers/gymnasium.pyauto_resetTrue会为环境加上自动重置能力。这一点在源码中有硬性校验record.py的__init__if self.base_env.num_envs 1 and save_video: assert ( max_steps_per_video is not None ), On GPU parallelized environments, there must be a given max steps per video value ...即在 GPU 向量化环境上启用视频录制时max_steps_per_video必须显式给出否则直接抛断言错误。源码注释还建议如果环境不做部分重置可以把max_steps_per_video设为max_episode_steps即一整集的长度。在 mani_skill/examples/demo_random_action.py 中正是这样做的env RecordEpisode(env, record_dir, info_on_videoFalse, save_trajectoryFalse, max_steps_per_videogym_utils.find_max_episode_steps_value(env))其中gym_utils.find_max_episode_steps_value(env)用于自动探测该任务的单集最大步数。当你以--record_dir运行demo_random_action.py时例如python mani_skill/examples/demo_random_action.py -e PushCube-v1 --record_dir videos/{env_id}即会借助该 Wrapper 自动产出rgb_array模式的 mp4 视频。录制多环境视频的拼接方式当num_envs 1时capture_image()会把各子环境的画面用tile_images平铺到一张图中video_nrows int(np.sqrt(self.unwrapped.num_envs))即按行数取环境数的平方根最终输出一张拼接后的画面。1.3 参数详解RecordEpisode.__init__的完整参数取自 mani_skill/utils/wrappers/record.py参数类型默认值说明output_dirstr必填输出目录会在保存轨迹或视频时自动mkdir(parentsTrue, exist_okTrue)save_trajectoryboolTrue是否保存轨迹数据.h5 .jsontrajectory_nameOptional[str]None轨迹文件基础名为None时用时间戳%Y%m%d_%H%M%S命名save_videoboolTrue是否保存视频.mp4info_on_videoboolFalse是否把 info 中的标量信息如 reward、各指标以文字叠加到视频帧上。源码中通过put_info_on_image实现每帧会显示为到达该状态所执行的动作与奖励首帧通常是env.reset()后第一帧观测不带文字save_on_resetboolTrue是否在 reset 时自动保存上一段轨迹/视频。GPU 模拟环境利用并行渲染必须配合max_steps_per_video否则视频可能因部分重置而无法正确切分save_video_triggerOptional[Callable[[int], bool]]None自定义函数入参为当前累计环境步数返回True则该时间步开始写入视频用于按步数条件触发录制max_steps_per_videoOptional[int]None单个视频最多录制的步数达到后 flush 视频并开启新视频内部步数计数器归零clean_on_closeboolTrue关闭时是否重命名并剪除空轨迹调用clean_trajectoriesrecord_rewardboolTrue是否在轨迹中记录奖励写入 HDF5 的rewards键record_env_stateboolTrue是否记录环境状态写入 HDF5 的env_states键。若检测到状态字典不一致is_state_dict_consistent为假源码会自动关闭该项并发出警告video_fpsint30视频帧率传给images_to_video再由 imageio/FFMPEG 编码render_substepsboolFalse是否渲染物理子步每个物理步后都采集一帧。更慢但帧更多配合更高 FPS 可得到更平滑的视频。开启后源码会包装base_env._after_simulation_step钩子在 GPU 模拟时先_gpu_fetch_all()再采集avoid_overwriting_videoboolFalse为True时不断递增视频编号以避开覆盖已有视频适合恢复训练续跑场景source_typeOptional[str]None描述数据来源类别的简短字符串如 RL、motionplanning、teleoperation会写入 JSONsource_descOptional[str]None对数据采集过程的更详细描述会写入 JSON其中info_on_videoTrue的具体路径在 mani_skill/utils/visualization/misc.py 中extract_scalars_from_info提取 info 中的标量连同当步reward一起交给put_info_on_image绘制在画面上。1.4 视频编码细节视频最终由 misc.py 中的images_to_video落盘它调用 imageio 的 FFMPEG writerimageio.get_writer(output_path, fpsfps, qualityquality)默认quality5范围 0~10越高画质越好文件名为{video_name}.mp4空格与换行会被替换为下划线。flush_video在保存时会自动跳过无有效内容的空视频len(self.render_images) 1时忽略即只有 reset 首帧、没有真实动作的视频不保存。二、录制输出格式HDF5 JSONRecordEpisode保存轨迹时会产生同名的一对文件.h5二进制数组数据h5py 写入与.json元数据dump_json写入。这与 ManiSkill 官方演示数据集的格式完全一致可直接被 mani_skill/trajectory/datasets.py 的ManiSkillTrajectoryDataset等工具消费。2.1 JSON 元数据每个 JSON 文件对应record.py源码 docstring 与 demos.md包含env_infodict任务环境信息可用于初始化任务含env_id任务 id、max_episode_stepsint、env_kwargsdict初始化任务的键值参数重建环境所必需commit_info记录数据时仓库的 commit 信息get_commit_info()便于溯源episodeslist[dict]各 episode 信息source_type可选 str数据来源类别ManiSkill 官方数据集通常写human、motionplanning或rlsource_desc可选 str数据生成方式的长描述。每个 episode 信息包含episode_idint唯一编号对应 HDF5 中的traj_{episode_id}reset_kwargsdict重置任务的参数复现轨迹所必需。注意多环境num_envs 1时由于 reset 参数对 GPU 并行不具明确语义源码会写成空字典dict()control_modestr该 episode 使用的控制模式elapsed_stepsint轨迹长度transition 数附加信息单环境时含episode_seed多环境时还会视情况写入success/fail。仅凭元数据即可重建任务与初始状态env gym.make(env_info[env_id], **env_info[env_kwargs]) episode env_info[episodes][0] # picks the first env.reset(**episode[reset_kwargs])若数据是在 GPU 模拟中采集的随机化除 seed 外还依赖并行环境数为确保一致的起始状态建议使用轨迹中存储的首个环境状态通过env.set_state_dict恢复。2.2 HDF5 轨迹数据每个.h5文件由多条轨迹组成键为traj_{episode_id}如traj_0。每个轨迹是一个h5py.Group包含actions[T, A]np.float32T 为 transition 数terminated[T]np.bool_各时间步是否任务终止truncated[T]np.bool_各时间步是否被截断env_states[T1, D]np.float32可通过env.set_state_dict恢复环境状态可能不足以完全复现轨迹rewards[T]np.float32当record_rewardTrue时写入success可选[T]np.bool_任务定义了 success 时写入fail可选[T]np.bool_任务定义了 fail 时写入obs可选[T1, D]观测数据。值得注意的源码细节轨迹中的env_states与obs可能是字典结构键为实体 id、叶子为数组序列写入时由recursive_add_to_h5py递归地以嵌套 h5py Group 保存对视觉类数据键为rgb、depth、seg会使用gzip 压缩compression_opts5源码注释说明对图像序列而言 gzip 比 png 压缩更高效每段轨迹的actions、terminated等会去掉首个虚拟帧reset 时的占位数据elapsed_steps end_ptr - start_ptr - 1。环境状态的典型字典结构如下env_state env.get_state_dict() env_state { actors: { actor_id: [...numpy_actor_state...], ... }, articulations: { articulation_id: [...numpy_articulation_state...], ... } } 若要按时间步切片读取轨迹中的env_states或obs可使用 mani_skill/trajectory/utils/init.py 提供的工具import mani_skill.trajectory.utils as trajectory_utils env_states trajectory_utils.dict_to_list_of_dicts(env_states) # now env_states[i] is the same as the data env.get_state_dict() returned at timestep i i 10 env_state_i trajectory_utils.index_dict(env_states, i) # now env_state_i is the same as the data env.get_state_dict() returned at timestep i2.3 轨迹清理clean_trajectories当clean_on_closeTrue默认时close()会调用clean_trajectories同样位于 record.py将轨迹重命名为连续整数traj_0, traj_1, ...剪除elapsed_steps 0没有实际动作的轨迹同步更新 JSON 中的episode_id与episodes列表。这保证了产出数据的编号紧凑、无空轨迹便于后续数据集加载工具直接消费。三、自定义视频录制Capture Individual Images如果你希望使用自己的视频录制方案例如自定义剪辑逻辑、叠加自研可视化信息可以不使用 Wrapper直接调用渲染 API 逐帧采集。该方法对单环境与向量化环境同样适用import mani_skill.envs import gymnasium as gym N 1 env gym.make(PickCube-v1, num_envsN) images [] env.reset() images.append(env.render_rgb_array()) for _ in range(200): obs, reward, terminated, truncated, info env.step(env.action_space.sample()) images.append(env.render_rgb_array()) # env.render_sensors() # render sensors mode # env.render_all() # render all mode关键注意事项原文档强调env.render_rgb_array()、env.render_sensors()等的返回值都是GPU 上的批量 torch 张量你需要将其转换为 CPU 上的 numpy 数组才能保存到磁盘例如import torch img_np env.render_rgb_array().cpu().numpy() # (num_envs, H, W, 3)之后即可把每帧img_np[0]依次交给imageio/FFMPEG 或 PIL 编码为视频或图像序列。四、三种渲染模式的底层差异无论走 Wrapper 还是手动采集录制的画面内容都由环境创建时的render_mode决定。三种模式的实现位于 mani_skill/envs/sapien_env.pyrender_mode底层方法画面内容rgb_arrayrender_rgb_array(camera_nameNone)由已注册的 human render camera 拍摄的高质量 RGB 图像形状(num_envs, H, W, 3)可指定camera_name只取某相机否则拼接所有相机。会显示_hidden_objects列表中的对象sensorsrender_sensors()渲染 Agent 可感知使用的所有传感器画面以人类可读的图像形式拼接展示get_sensor_images()的结果拼贴隐藏对象不显示allrender_all()同时渲染 human render camera 与传感器两者拼接为一张图先update_render(update_sensorsTrue, update_human_render_camerasTrue)再合并两组图像而统一的render()方法则根据render_mode分发human打开 GUI 查看器render_human、rgb_array/sensors/all分别调用上述方法render_modeNone时会抛出RuntimeError(render_mode is not set.)。RecordEpisode内部正是通过self.env.render()获取画面的见capture_image()先env.render()再common.to_numpy转 numpy单帧补维度、多环境平铺。五、测试与真实使用场景印证仓库中的测试与示例印证了上述全部用法tests/test_wrappers.py 覆盖了多种录制组合test_recordepisode_wrapperCPU 单环境 info_on_videoTruetest_recordepisode_wrapper_gpuGPU 16 环境、render_modergb_array、max_steps_per_video50、save_trajectoryFalsetest_recordepisode_wrapper_gpu_render_sensorGPU render_modesensorssave_trajectoryTruetest_recordepisode_wrapper_partial_reset_gpuGPU 中途以env.reset(optionsdict(env_idx[0, 1, 14, 15]))做部分重置验证视频按列顺序正确切分。 这些测试同时展示了录制时如何配合ManiSkillVectorEnv包装以修复 TimeLimit 相关问题。mani_skill/examples/demo_random_action.py 提供了可直接运行的录制入口通过--record_dir参数即可为任意任务录制随机动作视频。mani_skill/trajectory/replay_trajectory.py 在回放演示数据时同样以RecordEpisode为底层录制工具env wrappers.RecordEpisode(...)并支持通过命令行参数控制save_trajectory、save_video等选项说明该 Wrapper 是 ManiSkill 全链路采集→回放→发布数据集共用的标准录制组件。官方提供的强化学习与模仿学习基线如 examples/baselines/ppo/ppo.py、examples/baselines/sac/sac.py在训练评测阶段也都接入RecordEpisode来保存评测视频。六、实用建议与常见坑Wrapper 顺序RecordEpisode必须包在观测类 Wrapper 外层只允许向量接口 WrapperManiSkillVectorEnv包在它外面否则录制到的观测与模型实际看到的不一致。GPU 向量化环境必须给max_steps_per_video否则直接断言失败若任务无部分重置可设为gym_utils.find_max_episode_steps_value(env)。GPU 张量转 numpy手动采集时记得.cpu().numpy()Wrapper 内部已自动完成common.to_numpy。轨迹复现单环境轨迹的reset_kwargs完整保留可直接复现GPU 多环境轨迹的reset_kwargs为空若需精确复现起始状态应使用env_states的第一个状态通过env.set_state_dict恢复。视频覆盖恢复训练续跑时建议开启avoid_overwriting_videoTrue自动递增编号避免覆盖已有视频。平滑视频想要更平滑的录像可开启render_substepsTrue并配合更高的video_fps代价是渲染开销增大。空轨迹处理clean_trajectories会在关闭时自动剪除零步数轨迹flush_video也会跳过没有实际动作的空视频。综上RecordEpisode是 ManiSkill 生态中统一、标准的录制组件单环境开箱即用GPU 向量化环境通过max_steps_per_video优雅解决部分重置带来的切分难题输出的 HDF5JSON 双文件格式与官方数据集完全一致可直接用于模仿学习训练、评测录像与数据发布需要高度定制的场景则可退回到render_rgb_array/render_sensors/render_all手动逐帧采集。【免费下载链接】ManiSkillManipulation Skill Framework, an open source GPU parallelized robotics simulator and benchmark项目地址: https://gitcode.com/GitHub_Trending/ma/ManiSkill创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表