ISAACLAB 实战:

基于Isaac Sim仿真平台论文代码复现过程前期的学习过程,内容参考Isaac Lab的官方文档,包括利用Isaac Lab创建自己的强化学习环境,并利用RSL_RL库中现成的PPO算法进行模型训练与评估

前置:

Isaac Lab则是一项构建在Isaac Sim上层的用于机器人学习的统一灵活的仿真技术框架。它提供了许多现成的环境、传感器和训练任务等,能够极大简化尤其是强化学习(Reinforcement Learning, RL)这样机器人领域研究的工作。
参见官方文档:官方指导
在这里插入图片描述

一、熟悉和创建自己的项目:

1.创建项目

先配置好环境,然后创建项目路径,在项目路径下输入命令行:

ps: issaclab命令需要自己配置sh文件

isaaclab --new

在这里插入图片描述
选择内部外部项目以及RL_libraries还有RL_al
在这里插入图片描述

这样isaaclab就帮我们创建好项目了,可以看看项目结构:
在这里插入图片描述

项目结构如下:

在这里插入图片描述

2.安装项目:

在自己项目根目录下

python -m pip install -e source/isaac_lab_tutorial #这里是自己项目名字

可以顺便查看一下项目中可用的任务:

python scripts/list_envs.py

在这里插入图片描述

什么是任务?

在Isaac Lab中,任务被定义为:

“特定agent(机器人)的执行动作(actions)和其特定的观测空间(observations)所构成的的环境”

这个环境给agent提供了当前的观测空间,并随时间通过更新仿真环境(simulation)来处理agent执行的动作。

3.执行任务:
python scripts/skrl/train.py --task=Template-Isaac-Lab-Tutorial-Direct-v0

在这里插入图片描述

二、自行设计实战:

1. 环境配置
cartpole_env_cfg.py或
isaaclab_tutorial_env_cfg.py

实现强化学习环境配置类

@configclass
class IsaacLabTutorialEnvCfg(DirectRLEnvCfg):

作用是告诉 Isaac Lab:这个 RL 环境怎么仿真、用哪个机器人、创建多少个并行环境、物理步长是多少。

参数解读:
sim: SimulationCfg = SimulationCfg(dt=1 / 120, render_interval=2)
  • dt=1/120:物理仿真步长是 1/120 秒
  • render_interval=2:每 2 个物理步渲染一次
robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace(
    prim_path="/World/envs/env_.*/Robot"
)

配置机器人:

  • CARTPOLE_CFG 是 Isaac Lab 资产库里已有的 Cartpole 小车倒立摆配置
  • .replace(...) 是复制配置并修改其中的字段
  • prim_path="/World/envs/env_.*/Robot" 表示每个并行环境里的机器人路径
scene: InteractiveSceneCfg = InteractiveSceneCfg(
    num_envs=4096,
    env_spacing=4.0,
    replicate_physics=True
)

配置场景:

  • num_envs=4096:一次并行创建 4096 个环境,用来加速 RL 训练
  • env_spacing=4.0:每个环境之间间隔 4 米,避免互相干扰
  • replicate_physics=True:复制物理场景,提高并行仿真效率

完整代码如下:

# cartpole_env_cfg.py

import math
import torch
import isaaclab.sim as sim_utils
from isaaclab.assets import Articulation, ArticulationCfg
from isaaclab.envs import DirectRLEnv, DirectRLEnvCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.sim import SimulationCfg
from isaaclab.utils import configclass
from isaaclab_assets.robots.cartpole import CARTPOLE_CFG

@configclass
class IsaacLabTutorialEnvCfg(DirectRLEnvCfg):
    # RL
    episode_length_s = 5.0
    decimation = 2
    action_space = 1
    observation_space = 4
    state_space = 0

    # simulation
    sim: SimulationCfg = SimulationCfg(
        dt=1 / 120,
        render_interval=decimation,
    )

    # robot
    robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace(
        prim_path="/World/envs/env_.*/Robot"
    )

    # scene
    scene: InteractiveSceneCfg = InteractiveSceneCfg(
        num_envs=4096,
        env_spacing=4.0,
        replicate_physics=True,
    )

    # cartpole settings
    cart_dof_name = "slider_to_cart"
    pole_dof_name = "cart_to_pole"

    action_scale = 100.0
    max_cart_pos = 3.0
    max_pole_angle = math.pi / 2
2.定义robot版环境

注意这里文件名是:

cartpole_env.py或
isaac_lab_tutorial_env.py

IsaacLabTutorialEnv 继承自Lab基类DirectRLEnv,这个环境使用 IsaacLabTutorialEnvCfg 作为配置,继而会调用父类 DirectRLEnv 的初始化逻辑,比如:

  • 创建仿真上下文
  • 创建场景
  • 分配 buffer
  • 设置环境数量

完整代码和注释如下:

# cartpole_env.py
class IsaacLabTutorialEnv(DirectRLEnv):
    cfg: IsaacLabTutorialEnvCfg #这个环境使用 IsaacLabTutorialEnvCfg 作为配置

    def __init__(self, cfg: IsaacLabTutorialEnvCfg, render_mode: str | None = None, **kwargs):
        super().__init__(cfg, render_mode, **kwargs)

        self.actions = torch.zeros(self.num_envs, 1, device=self.device)
		# 读取关节
        self.cart_dof_idx, _ = self.cartpole.find_joints(self.cfg.cart_dof_name) #平移关节id
        self.pole_dof_idx, _ = self.cartpole.find_joints(self.cfg.pole_dof_name) #旋转关节id

    def _setup_scene(self):
        self.cartpole = Articulation(self.cfg.robot_cfg) #根据robot_cfg来创建机器人对象
        self.scene.articulations["cartpole"] = self.cartpole #机器人注册到 Isaac Lab 的 scene 里

        self.scene.clone_environments(copy_from_source=False) #复制多个并行环境

        light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75))
        light_cfg.func("/World/Light", light_cfg) #添加灯光渲染

    def _pre_physics_step(self, actions: torch.Tensor): #做出action前要做什么(接收算法结果)
        self.actions = actions.clone().clamp(-1.0, 1.0) #保存动作、限制动作范围

    def _apply_action(self):
        force = self.actions * self.cfg.action_scale
        # 动作施加
        self.cartpole.set_joint_effort_target(force, joint_ids=self.cart_dof_idx) #设置关节的目标作用力  joint_ids为单个关节编号

    def _get_observations(self) -> dict:
        joint_pos = self.cartpole.data.joint_pos # [batch, num_dof]
        joint_vel = self.cartpole.data.joint_vel

        cart_pos = joint_pos[:, self.cart_dof_idx[0]].unsqueeze(-1) # 小车平移关节水平位置
        cart_vel = joint_vel[:, self.cart_dof_idx[0]].unsqueeze(-1) # 小车速度
        pole_angle = joint_pos[:, self.pole_dof_idx[0]].unsqueeze(-1) # 摆杆倾角
        pole_vel = joint_vel[:, self.pole_dof_idx[0]].unsqueeze(-1) # 摆杆角速度

        obs = torch.cat(
            (
                cart_pos,
                cart_vel,
                pole_angle,
                pole_vel,
            ),
            dim=-1,
        )

        return {"policy": obs}  # shape : [num_envs, observation_dim]

    def _get_rewards(self) -> torch.Tensor:
        joint_pos = self.cartpole.data.joint_pos
        joint_vel = self.cartpole.data.joint_vel

        cart_pos = joint_pos[:, self.cart_dof_idx[0]]
        cart_vel = joint_vel[:, self.cart_dof_idx[0]]
        pole_angle = joint_pos[:, self.pole_dof_idx[0]]
        pole_vel = joint_vel[:, self.pole_dof_idx[0]]

        alive_reward = 1.0
        pole_angle_penalty = pole_angle.square()
        cart_pos_penalty = 0.01 * cart_pos.square()
        cart_vel_penalty = 0.001 * cart_vel.square()
        pole_vel_penalty = 0.005 * pole_vel.square()
        action_penalty = 0.001 * self.actions.squeeze(-1).square()

        reward = (
            alive_reward
            - pole_angle_penalty
            - cart_pos_penalty
            - cart_vel_penalty
            - pole_vel_penalty
            - action_penalty
        )

        return reward  # shape: [num_envs] 每个并行环境一个奖励值

    def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]:
        joint_pos = self.cartpole.data.joint_pos

        cart_pos = joint_pos[:, self.cart_dof_idx[0]]
        pole_angle = joint_pos[:, self.pole_dof_idx[0]]

        time_out = self.episode_length_buf >= self.max_episode_length - 1 #达到最大步数结束

        out_of_bounds = torch.logical_or(
            torch.abs(cart_pos) > self.cfg.max_cart_pos,
            torch.abs(pole_angle) > self.cfg.max_pole_angle,
        ) # 因为失败而结束

        return out_of_bounds, time_out

    def _reset_idx(self, env_ids: torch.Tensor | None):
        if env_ids is None:
            env_ids = self.cartpole._ALL_INDICES

        super()._reset_idx(env_ids)

        joint_pos = self.cartpole.data.default_joint_pos[env_ids].clone()
        joint_vel = self.cartpole.data.default_joint_vel[env_ids].clone()

        joint_pos[:, self.pole_dof_idx[0]] += torch.empty(
            len(env_ids), device=self.device
        ).uniform_(-0.125, 0.125)

        default_root_state = self.cartpole.data.default_root_state[env_ids].clone()
        default_root_state[:, :3] += self.scene.env_origins[env_ids]

        self.cartpole.write_root_pose_to_sim(default_root_state[:, :7], env_ids)
        self.cartpole.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids)
        self.cartpole.write_joint_state_to_sim(joint_pos, joint_vel, None, env_ids)

定义了一个 Isaac Lab 直接强化学习环境,告诉框架怎么创建机器人场景、怎么接收动作、怎么控制机器人、怎么返回观测、怎么计算奖励、怎么判断结束以及怎么重置环境

3.环境设计
1)定义机器人
jetbot.py

代码如下:

JETBOT_CONFIG = ArticulationCfg(
    spawn=sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/NVIDIA/Jetbot/jetbot.usd"),
    actuators={"wheel_acts": ImplicitActuatorCfg(joint_names_expr=[".*"], damping=None, stiffness=None)},
)
2)定义训练模拟并管理克隆

代码如下:

from isaac_lab_tutorial.robots.jetbot import JETBOT_CONFIG
@configclass
class IsaacLabTutorialEnvCfg(DirectRLEnvCfg):
    # env
    decimation = 2
    episode_length_s = 5.0
    # - spaces definition
    action_space = 2
    observation_space = 3
    state_space = 0
    # simulation
    sim: SimulationCfg = SimulationCfg(dt=1 / 120, render_interval=decimation)
    # robot(s)
    robot_cfg: ArticulationCfg = JETBOT_CONFIG.replace(prim_path="/World/envs/env_.*/Robot")
    # scene
    scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=100, env_spacing=4.0, replicate_physics=True)
    dof_names = ["left_wheel_joint", "right_wheel_joint"] #关节名称
3)将代理的动作应用到机器人上
class IsaacLabTutorialEnv(DirectRLEnv):
    cfg: IsaacLabTutorialEnvCfg
    def __init__(self, cfg: IsaacLabTutorialEnvCfg, render_mode: str | None = None, **kwargs):
        super().__init__(cfg, render_mode, **kwargs)

        self.dof_idx, _ = self.robot.find_joints(self.cfg.dof_names)

    def _setup_scene(self):
        self.robot = Articulation(self.cfg.robot_cfg) #获取机器人
        # add ground plane 负责碰撞、物理支撑
        spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg())
        # clone and replicate 批量克隆并行环境
        self.scene.clone_environments(copy_from_source=False)
        # add articulation to scene robot注册到场景管理
        self.scene.articulations["robot"] = self.robot
        # add lights 全局灯光
        light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75))
        light_cfg.func("/World/Light", light_cfg)
    
    def _pre_physics_step(self, actions: torch.Tensor) -> None:
    self.actions = actions.clone() #接收动作

    def _apply_action(self) -> None:
        self.robot.set_joint_velocity_target(self.actions, joint_ids=self.dof_idx) #执行动作

当我们谈论像机器人这样的场景实体时,我们可以广泛地谈论机器人,作为存在于每个场景中的实体,也可以描述一个具体、单一的克隆体 舞台上的机器人。

4)计算并返回奖励和观察结果
def _get_observations(self) -> dict: #每一轮自动交互
    self.velocity = self.robot.data.root_com_lin_vel_b #获取线速度 [num_envs,3]
    observations = {"policy": self.velocity}
    return observations # 基座三轴速度作为唯一观测

def _get_rewards(self) -> torch.Tensor:
    total_reward = torch.linalg.norm(self.velocity, dim=-1, keepdim=True) #计算三轴速度的二范数
    return total_reward

对于每个场景的克隆,我们需要计算一个奖励值,并返回为形状的张量。作为占位符,我们将把奖励设为 喷气机器人在机体框架中的线速度大小。有了这个奖励和观察空间,代理应学会随机确定方向,向前或向后驾驶喷气机器人 训练刚开始不久。[num_envs, 1]

5)管理重置和终端状态
def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]:
    time_out = self.episode_length_buf >= self.max_episode_length - 1 #最大时长
    return False, time_out

def _reset_idx(self, env_ids: Sequence[int] | None):
    if env_ids is None:
        env_ids = self.robot._ALL_INDICES
    super()._reset_idx(env_ids)

    default_root_state = self.robot.data.default_root_state[env_ids]
    default_root_state[:, :3] += self.scene.env_origins[env_ids]

    self.robot.write_root_state_to_sim(default_root_state, env_ids)

终止和重置分成两部分处理。首先是方法,其目标很简单,就是标记哪些环境需要重置以及为什么。 传统上,在强化学习中,“发作”以两种方式结束:要么代理达到终结状态,要么发作达到最大持续时间。

4.注册task

isaac_lab_tutorial/source/isaac_lab_tutorial/isaac_lab_tutorial/tasks/direct/isaac_lab_tutorial/init.py

# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
import gymnasium as gym
from . import agents
##
# Register Gym environments.
##

gym.register(
    id="Template-Isaac-Lab-Tutorial-Direct-v0",
    entry_point=f"{__name__}.isaac_lab_tutorial_env:IsaacLabTutorialEnv",
    disable_env_checker=True,
    kwargs={
        "env_cfg_entry_point": f"{__name__}.isaac_lab_tutorial_env_cfg:IsaacLabTutorialEnvCfg",
        "skrl_amp_cfg_entry_point": f"{agents.__name__}:skrl_amp_cfg.yaml",
        "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml",
    },
)
5.执行运行命令开始训练

这里task写上面注册号的id名

python scripts/skrl/train.py --task=Template-Isaac-Lab-Tutorial-Direct-v0
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐