包装环境#

gymnasium.Wrapper 可在不修改环境实现的情况下改变观测、奖励或执行过程,常用于录制视频和限制时间。

ManagerBasedRLEnv 和 DirectRLEnv 都实现 gymnasium.Env。先创建环境,再用 env = wrapper(env, *args, **kwargs) 依次套用包装器。顺序很重要:每个包装器接收其内层的输出。

例如,这是如何将一个环境包装起来强制在 step 或 render 之前调用 reset 的方式 :

import gymnasium as gym

from isaaclab.app import launch_simulation

import isaaclab_tasks  # noqa: F401
from isaaclab_tasks.utils import load_cfg_from_registry

cfg = load_cfg_from_registry("Isaac-Reach-Franka", "env_cfg_entry_point")
# start the simulation runtime this configuration needs, and stop it on exit
with launch_simulation(cfg):
    # create base environment
    env = gym.make("Isaac-Reach-Franka", cfg=cfg)
    # wrap environment to enforce that reset is called before step
    env = gym.wrappers.OrderEnforcing(env)

用于记录视频的包装器#

gymnasium.wrappers.RecordVideo 包装器可用于记录环境的视频。该包装器接受一个 video_dir 参数,指定要保存视频的位置。根据指定的步数或情节,以指定的间隔将视频以 mp4 格式保存。

要使用该包装器,您需要首先安装 ffmpeg 。在 Ubuntu 上,可以通过运行以下命令进行安装 :

sudo apt-get install ffmpeg

注意

默认情况下,在无头模式运行环境时,Omniverse 视口会被禁用。这样做是为了通过避免不必要的渲染来提高性能。

使用 RTX 3090 GPU 运行 Isaac-Reach-Franka 环境时,不同渲染模式的性能如下:

  • 没有开启离屏渲染的无 GUI 执行 : ~65,000 FPS

  • 开启了离屏渲染的无 GUI 执行 : ~57,000 FPS

  • 全渲染的 GUI 执行 : ~13,000 FPS

用于渲染的视口相机是场景中称为 "/OmniverseKit_Persp" 的默认相机。相机的姿势和图像分辨率可以通过 ViewerCfg 类进行配置。

ViewerCfg 类的默认参数 :
@configclass
class ViewerCfg:
    """Configuration of the scene viewport camera.

    .. deprecated::
        :class:`ViewerCfg` is deprecated and will be removed in a future release.
        Configure the viewport camera via :class:`~isaaclab_visualizers.kit.KitVisualizerCfg`
        and add it to :attr:`~isaaclab.sim.SimulationCfg.visualizer_cfgs` instead, which applies when
        ``--visualizer kit`` selects the Kit visualizer::

            from isaaclab.sim import SimulationCfg
            from isaaclab_visualizers.kit import KitVisualizerCfg

            sim_cfg = SimulationCfg(visualizer_cfgs=[KitVisualizerCfg(eye=(7.5, 7.5, 7.5), lookat=(0.0, 0.0, 0.0))])
    """

    eye: tuple[float, float, float] = (7.5, 7.5, 7.5)
    """Initial camera position (in m). Default is (7.5, 7.5, 7.5)."""

    lookat: tuple[float, float, float] = (0.0, 0.0, 0.0)
    """Initial camera target position (in m). Default is (0.0, 0.0, 0.0)."""

    cam_prim_path: str = "/OmniverseKit_Persp"
    """The camera prim path to record images from. Default is "/OmniverseKit_Persp"."""

    resolution: tuple[int, int] = (1280, 720)
    """The resolution (width, height) of the camera. Default is (1280, 720)."""

    origin_type: Literal["world", "env", "asset_root", "asset_body"] = "world"
    """The frame in which the camera position (eye) and target (lookat) are defined. Default is "world"."""

    env_index: int = 0
    """The environment index for frame origin. Default is 0."""

    asset_name: str | None = None
    """The asset name in the interactive scene for the frame origin. Default is None."""

    body_name: str | None = None
    """The name of the body in :attr:`asset_name` for the frame origin. Default is None."""

    def __post_init__(self) -> None:
        # Warn only when the user configured a non-default field so that bare ``ViewerCfg()``
        # usage (e.g. in task configs that haven't been migrated yet) stays silent.
        #
        # @configclass stores mutable defaults (tuples, lists) via default_factory rather than
        # default, so we must check both to obtain the canonical default value.
        differing = []
        for f in fields(self):
            if not f.init:
                continue
            if f.default is not MISSING:
                default = f.default
            elif f.default_factory is not MISSING:  # type: ignore[misc]
                default = f.default_factory()  # type: ignore[misc]
            else:
                continue
            if not _viewer_cfg_field_matches_default(getattr(self, f.name), default):
                differing.append(f.name)
        if differing:
            warnings.warn(
                "ViewerCfg is deprecated and will be removed in a future release. "
                "Use KitVisualizerCfg added to SimulationCfg.visualizer_cfgs instead.",
                DeprecationWarning,
                stacklevel=2,
            )

要录制视频,将 VideoRecorderCfg 添加到环境配置。它会在环境步进时,从可视化器或相机传感器录制 mp4 片段,因此无需包装器或 render_mode ,且会自动启动可视化器所需的运行时。

例如,以下代码每隔 1500 步,从 Kit 可视化器录制 Isaac-Reach-Franka 环境的一段 200 步视频,保存到 videos/train 目录。其他录制源和片段选项见 录制视频 。

import gymnasium as gym

from isaaclab_visualizers.kit import KitVisualizerCfg

from isaaclab.app import launch_simulation
from isaaclab.envs.utils.video_recorder_cfg import VideoRecorderCfg

# record from a Kit visualizer with this camera pose
env_cfg.sim.visualizer_cfgs = KitVisualizerCfg(eye=(1.0, 1.0, 1.0), lookat=(0.0, 0.0, 0.0))
env_cfg.video_recorders = [
    VideoRecorderCfg(source="viz:kit", output_dir="videos/train", video_length=200, video_interval=1500)
]
# select the Kit visualizer, as ``--viz kit`` does; the config above only sets its camera pose
with launch_simulation(env_cfg, {"visualizer": "kit"}):
    env = gym.make(task_name, cfg=env_cfg)

学习框架的包装器#

每个学习框架都有自己的 API 与环境交互。例如, Stable-Baselines3 库使用 gym.Env 接口与环境进行交互。然而,像 RL-Games , RSL-RL 或 SKRL 这样的库使用自己的 API 来与学习环境进行交互。由于并非所有情况均适用,我们不会将 ManagerBasedRLEnv 和 DirectRLEnv 类建立在任何特定学习框架的环境定义基础上。相反,我们实现包装器来使其与学习框架的环境定义兼容。

使用 Stable-Baselines3 与 RL 任务环境的示例 :

from isaaclab_rl.sb3 import Sb3VecEnvWrapper

# create isaac-env instance
env = gym.make(task_name, cfg=env_cfg)
# wrap around environment for stable baselines
env = Sb3VecEnvWrapper(env)

Stable-Baselines3 要求连续动作具有有限边界。如果环境的动作空间无界, Sb3VecEnvWrapper 会向 Stable-Baselines3 提供归一化的 [-1, 1] 边界,同时保持底层环境不变。如果策略使用其他有限动作范围,应设置 action_bounds 。

小心

在应用所有其他包装器后,应在最后使用相应学习框架的包装器包装环境。这是因为学习框架的包装器会修改环境 API 的解释,这可能不再与 gymnasium.Env 兼容。

添加新的包装器#

所有新的包装器都应添加到 isaaclab_rl 模块。它们应该检查底层环境是否是 isaaclab.envs.ManagerBasedRLEnv 或 DirectRLEnv 类的实例,然后再应用该包装器。这可以通过使用 unwrapped() 属性来完成。

我们在这个模块中包括了一组包装器,供您作为实现自己包装器的参考。如果您实现了一个新的包装器,请考虑通过开放一个拉取请求将其贡献给框架。