0 前言
官方教程:https://isaac-sim.github.io/IsaacLab/main/source/tutorials/03_envs/create_manager_base_env.html
Isaacsim+Isaaclab安装:https://blog.csdn.net/m0_47719040/article/details/146389391?spm=1001.2014.3001.5502
上一节中我们介绍了一个scene.InteractiveScene
场景类,它提供了一个便捷的接口,用于在模拟中生成prim并进行管理。本节将研究如何在Isaaclab中创建环境,所谓环境实际上就是汇集了模拟的不同方面,如场景、观测和动作空间、重置事件等。环境将仿真的多个要素(场景、观测空间、动作空间、重置事件等)统一封装,形成标准化接口,通过环境可以使不同的应用场景的创建变的更简单化、模块化。
在Isaaclab中基于管理器的环境被分为两个类来实现,一下是两类区别
类名 | 适用场景 | 核心功能差异 | 典型用途示例 |
---|---|---|---|
envs.ManagerBasedEnv | 传统机器人控制 | 不包含奖励机制和终止条件 | 机械臂轨迹跟踪、无人机定点控制 |
envs.ManagerBasedRLEnv | 强化学习任务 | 包含奖励计算、终止判断、课程学习系统 | DRL算法训练、自适应控制策略开发 |
在本教程中,我们将研究envs.ManagerBasedEnv
类及其对应的envs.ManagerBasedEnvCfg
配置类。我们将使用之前的 cartpole
环境来介绍创建envs.ManagerBasedEnv
新环境时使用的不同组件。
教程对应的脚本为create_cartpole_base_env.py
在scripts/tutorials/03_envs
目录下。
运行该程序:
- 进入安装 isaac lab 时创建的conda虚拟环境
- 在该环境下进入 isaac sim文件夹中运行
source setup_conda_env.sh
- 终端中输入
python scripts/tutorials/03_envs/create_cartpole_base_env.py --num_envs 32
运行你的代码。
1 场景设置
创建新环境的第一步是配置其场景。对于 cartpole 环境,我们将使用上一个教程中的场景,官方省略了这部分的详解。
在查看原始python文件可以发现场景是通过下述代码引入的,该包的实际地址在/IsaacLab/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py
并非我们上一节中自己创建的:
from isaaclab_tasks.manager_based.classic.cartpole.cartpole_env_cfg import CartpoleSceneCfg
因此首先注释上述代码,并加入两行新代码:
# from isaaclab_tasks.manager_based.classic.cartpole.cartpole_env_cfg import CartpoleSceneCfg
import sys
sys.path.append("scripts/tutorials/02_scene")
from create_scene import CartpoleSceneCfg
运行后会发现有报错,注意绿色框部分,翻译过来就是提醒我们,你是不是启动了多余的Isaacsim APP:
回忆一下之前的代码就明白,我们需要在上一节的代码中注释掉一部分:
# import argparse
# from isaaclab.app import AppLauncher
# # add argparse arguments
# parser = argparse.ArgumentParser(description="Tutorial on using the interactive scene interface.")
# parser.add_argument("--num_envs", type=int, default=2, help="Number of environments to spawn.")
# # append AppLauncher cli args
# AppLauncher.add_app_launcher_args(parser)
# # parse the arguments
# args_cli = parser.parse_args()
# # launch omniverse app
# app_launcher = AppLauncher(args_cli)
# simulation_app = app_launcher.app
同时,由于键值有一点小小的变化,调整CartpoleSceneCfg
类:
@configclass
class CartpoleSceneCfg(InteractiveSceneCfg):
"""Configuration for a cart-pole scene."""
# ground plane
ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
# lights
dome_light = AssetBaseCfg(
prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
)
# articulation
#cartpole: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
robot: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
最后,再次运行没有问题。
2 定义动作
对于动作的控制实际上分为两种,一种是直接控制,一种是管理器控制:
- 直接控制:使用assets.Articulation.set_joint_effort_target()直接向关节施加力。(之前几节中的方法)
- 缺点:代码耦合度高,难以扩展复杂控制逻辑。
- 管理控制:使用managers.ActionManager统一管理动作,通过组合多个ActionTerm实现模块化控制。
- 优点:代码结构清晰,支持多组件独立控制(如机械臂和夹爪分开控制)。
ActionManager 是一个“动作管理中心”,负责协调多个动作项(ActionTerm)。当用户输入动作(如一个力值)时,ActionManager 会将动作分发给所有注册的ActionTerm,每个Term根据自己的逻辑处理动作(如施加力、设置位置等)。
- 每个ActionTerm是独立的动作控制单元,负责具体的物理交互。例如:
- JointEffortActionTerm:通过力/力矩控制关节。
- JointPositionActionTerm:通过目标位置控制关节。
- GripperActionTerm:控制夹爪的开合。
- Cartpole 示例:目标是控制推车的力以平衡杆子,因此需定义一个
CartForceActionTerm
- 绑定到推车的关节。
- 将用户输入的动作值(标量力)转换为关节的力。
- 通过
set_joint_effort_target()
或其他底层方法实现控制。
@configclass
class ActionsCfg:
"""Action specifications for the environment."""
#定义当前环境的动作空间结构和控制参数。此处只定义了一个动作项 joint_efforts,表示通过关节力控制推车。
joint_efforts = mdp.JointEffortActionCfg(asset_name="robot", joint_names=["slider_to_cart"], scale=5.0)
# asset_name="robot":指定要控制的机器人资产名称(在场景中定义的推车模型名)
# joint_names=["slider_to_cart"]:目标关节名(推车与底座之间的滑动关节)
# scale=5.0:动作缩放系数:将输入的动作值(如 [-1, 1])映射为实际力值(-5.0 到 5.0)
当环境初始化时,框架会根据 ActionsCfg 中的配置自动创建对应的 ActionTerm(如 JointEffortActionTerm)并注册到 ActionManager。等效的手动代码如下:
# 自动生成的等效代码
from omni.isaac.orbit.managers import ActionManager
from omni.isaac.orbit.managers.action_terms import JointEffortActionTerm
action_manager = ActionManager()
# 根据配置创建 ActionTerm
cart_term = JointEffortActionTerm(
asset_name="robot",
joint_names=["slider_to_cart"],
scale=5.0
)
action_manager.add_term("joint_efforts", cart_term) # "joint_efforts" 是键名
3 定义观测
- 观察(Observations)与场景状态(Scene State)的区别:
示例:在 Cartpole 中,场景状态包含推车和杆的全部动力学信息,而观察可能仅包含推车位置、速度和杆的倾斜角。- 场景状态:环境的完整物理状态,包括所有物体的位置、速度、关节角度等,可能包含冗余或不可观测的信息(如内部传感器噪声)。
- 观察:智能体(Agent)实际能感知到的状态子集,经过选择性处理和加工(如去噪、归一化、特征提取)。
ObservationManager 的核心作用:管理环境中所有观察项的生成与组合,负责将原始场景状态转换为智能体可用的观察。对于 ObservationManager
来说实际上就是,输入:场景的原始物理状态;输出:结构化观察数据(如张量)。
与动作管理器类似,观察管理器可以包含多个观察项。
- ObservationGroup(观察组):一组具有相同维度的观察项的集合,用于定义不同用途的观察空间。
- “policy”:供强化学习策略使用的主要观察空间。
- “critic”:供值函数(Critic)使用的额外观察信息。
- “safety”:用于安全监控的专用观察(如碰撞检测)。
- ObservationTerm(观察项):单个观察信号的生成规则,例如关节角度、末端执行器位置、图像像素等。
- func:计算该观察项的函数或可调用类(核心参数)。
- noise:添加的噪声模型(如高斯噪声)。
- clip:观察值的裁剪范围(如 [-1, 1])。
- scale:观察值的缩放系数。
@configclass
class ObservationsCfg:
"""Observation specifications for the environment."""
'''
ObservationsCfg 类
目的:
定义环境中所有观察组的配置。此处仅包含一个名为 policy 的观察组,符合 Isaac Lab 的默认要求。
policy: PolicyCfg = PolicyCfg():将嵌套的 PolicyCfg 类实例化为 policy 属性,作为主要的观察组。
'''
@configclass
class PolicyCfg(ObsGroup):
"""Observations for policy group."""
'''
PolicyCfg 类(继承自 ObsGroup)
joint_pos_rel = ObsTerm(func=mdp.joint_pos_rel):定义一个观察项,计算关节相对位置。
joint_vel_rel = ObsTerm(func=mdp.joint_vel_rel):定义另一个观察项,计算关节相对速度。
ObsTerm:等价于 managers.ObservationTermCfg,封装单个观察项的配置。
func 参数:指定计算该观察项的函数或类(如 mdp.joint_pos_rel)。
'''
# 观察项定义
joint_pos_rel = ObsTerm(func=mdp.joint_pos_rel)
joint_vel_rel = ObsTerm(func=mdp.joint_vel_rel)
# 后初始化设置
def __post_init__(self) -> None:
self.enable_corruption = False
self.concatenate_terms = True
# 将 PolicyCfg 实例化为 policy 属性
policy: PolicyCfg = PolicyCfg()
4 定义事件
至此,我们已经为 cartpole 环境定义了场景、动作和观察。所有这些组件的总体思路是定义配置类,然后将它们传递给相应的管理器。事件管理器也是如此。
- EventManager 的核心作用:管理仿真过程中的动态事件,如随机化物理参数、重置场景状态、修改视觉属性等。
- 类比前述管理器:
管理器 | 管理对象 | 触发机制 |
---|---|---|
ActionManager | 动作项(ActionTerm) | 每一步(step() ) |
ObservationManager | 观察项(ObservationTerm) | 每一步(step() ) |
EventManager | 事件项(EventTerm) | 按模式(mode )触发(启动、重置、间隔) |
- 事件模式(Modes):Isaac Lab 内置三种事件触发模式
- “startup”:
- 触发时机:仅在环境初始化时执行一次。
- 典型用途:初始化耗时操作(如加载复杂模型、预计算物理参数)。
- 示例:随机化杆的质量(因修改质量需要重新构建物理引擎的内部结构,开销较大)。
- “reset”:
- 触发时机:每次环境重置时执行(如 episode 结束后的 reset())。
- 典型用途:重置初始状态、随机化目标位置、更换纹理等。
- 示例:随机化推车和杆的初始关节位置与速度。
- “interval”:
- 触发时机:每隔固定步数(如每 100 步)执行一次。
- 典型用途:动态难度调整、周期性扰动(如模拟环境变化)。
- “startup”:
在本例中,我们定义了一些事件,用于在启动时随机化杆的质量。由于此操作开销较大,我们不想在每次重置时都执行此操作,因此只需执行一次。我们还创建了一个事件,用于在每次重置时随机化推杆和杆的初始关节状态。
@configclass
class EventCfg:
"""Configuration for events."""
# 在环境初始化时(仅一次),随机增加杆(pole)的质量,以创建不同的物理动态特性。
add_pole_mass = EventTerm(
func=mdp.randomize_rigid_body_mass,#预定义的函数,用于随机化刚体质量。
mode="startup",# 启动时执行
params={
"asset_cfg": SceneEntityCfg("robot", body_names=["pole"]),
#指定目标实体:场景中名为 "robot" 的资产下,刚体名称为 "pole"。
"mass_distribution_params": (0.1, 0.5),
#质量随机分布参数:生成 Uniform(0.1, 0.5) 的随机值,并叠加到原有质量上。
"operation": "add",
#操作模式为“叠加”而非“覆盖”("add" 表示增加质量,"set" 表示直接设置)。
},
)
# on reset
reset_cart_position = EventTerm(
func=mdp.reset_joints_by_offset,#预定义的函数,通过偏移量重置关节状态。
mode="reset",#重置时执行
params={
"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]),
# 目标关节:推车的滑动关节(slider_to_cart)。
"position_range": (-1.0, 1.0),
# 推车初始位置的随机范围(单位:米)。
"velocity_range": (-0.1, 0.1),
# 推车初始速度的随机范围(单位:米/秒)。
},
)
reset_pole_position = EventTerm(
func=mdp.reset_joints_by_offset,
mode="reset",# 重置时执行
params={
"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]),
# 目标关节:杆的旋转关节(cart_to_pole)。
"position_range": (-0.125 * math.pi, 0.125 * math.pi),
# 杆初始角度的随机范围(约 -22.5° 到 22.5°)。
"velocity_range": (-0.01 * math.pi, 0.01 * math.pi),
# 杆初始角速度的随机范围(约 -1.8°/秒 到 1.8°/秒)。
},
)
5 把所有东西联系起来
定义了场景和管理器配置后,通过 envs.ManagerBasedEnvCfg
类整合环境配置。
ManagerBasedEnvCfg 是环境的总配置类,该类接收场景、动作、观察和事件配置及envs.ManagerBasedEnvCfg.sim
定义模拟参数(例如时间步长、重力等)的参数,可以通过__post_init__()
来修改模拟参数,该方法在配置初始化后调用。
@configclass
class CartpoleEnvCfg(ManagerBasedEnvCfg):
# ManagerBasedEnvCfg:基类,Isaac Lab 中基于管理器环境的通用配置模板,包含场景、动作、观察、事件等模块的配置槽位。
"""Configuration for the cartpole environment."""
# 场景配置
scene = CartpoleSceneCfg(num_envs=1024, env_spacing=2.5)
# Cartpole 场景的专用配置类,定义推车、杆子、轨道等物理实体的初始化参数。上一节中的知识
# num_envs=1024:并行环境实例数量(1024 个环境同时仿真,用于强化学习的高效批量训练)。
# env_spacing=2.5:环境之间的间隔距离(单位:米),避免不同环境中的推车碰撞。
# 基础模块配置
observations = ObservationsCfg() # 注入观察配置类,定义智能体可观测的状态(如推车位置、杆子角度)。
actions = ActionsCfg() # 注入动作配置类,指定如何将策略输出的动作转换为关节力。
events = EventCfg() # 注入事件配置类,定义随机化质量、初始状态等动态事件。
# 后初始化设置 (__post_init__)
def __post_init__(self):
"""Post initialization."""
# 查看器设置
self.viewer.eye = [4.5, 0.0, 6.0] # 摄像机位置 (X, Y, Z)
self.viewer.lookat = [0.0, 0.0, 2.0] # 摄像机焦点 (X, Y, Z)
# 步进设置
self.decimation = 4 # 每隔 4 个仿真步执行一次环境步
# 仿真参数
self.sim.dt = 0.005 # 仿真步长 5ms,对应 200Hz
6 运行模拟
基本步骤其实跟之前的类似的,只不过把所有的东西都整合到了env中。具体内容如下:
对于ManagerBasedEnv
官方文档做了更详细的说明,传送门放在下方:
https://isaac-sim.github.io/IsaacLab/main/source/api/lab/isaaclab.envs.html#isaaclab.envs.ManagerBasedEnv
def main():
"""Main function."""
# 解析命令行参数,整合环境配置
env_cfg = CartpoleEnvCfg()
env_cfg.scene.num_envs = args_cli.num_envs # 从命令行参数设置并行环境数
# 根据环境配置初始化环境(实例化)
env = ManagerBasedEnv(cfg=env_cfg)
# 仿真循环
count = 0
while simulation_app.is_running():# 检查仿真应用是否在运行(如 Isaac Sim 窗口未关闭)
with torch.inference_mode():# 禁用梯度计算,提升性能
# 定期重置环境
if count % 300 == 0:
count = 0
env.reset() # 重置所有并行环境,实际上就是EventCfg中指定好的内容
print("-" * 80)
print("[INFO]: Resetting environment...")
# 生成随机动作
joint_efforts = torch.randn_like(env.action_manager.action)
# 执行环境步进
obs, _ = env.step(joint_efforts) # obs 是观察值字典,_ 占位奖励和终止标志(此处未使用)
# 打印第一个环境的杆关节观察值(假设为倾斜角)
print("[Env 0]: Pole joint: ", obs["policy"][0][1].item())
# 更新计数器
count += 1
# 关闭环境,释放资源
env.close()