使用任务空间控制器#

在以前的教程中,我们使用关节空间控制器来控制机器人。然而,在许多情况下,使用任务空间控制器更直观。例如,如果我们想对机器人进行远程操作,更容易指定期望的末端执行器姿势,而不是期望的关节位置。

在本教程中,我们将学习如何使用任务空间控制器来控制机器人。我们将使用 controllers.DifferentialIKController 类来跟踪期望的末端执行器姿势指令。

代码#

该教程对应于 source/standalone/tutorials/05_controllers 目录中的 run_diff_ik.py 脚本。

run_diff_ik.py 的代码
  1# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
  2# All rights reserved.
  3#
  4# SPDX-License-Identifier: BSD-3-Clause
  5
  6"""
  7This script demonstrates how to use the differential inverse kinematics controller with the simulator.
  8
  9The differential IK controller can be configured in different modes. It uses the Jacobians computed by
 10PhysX. This helps perform parallelized computation of the inverse kinematics.
 11
 12.. code-block:: bash
 13
 14    # Usage
 15    ./isaaclab.sh -p source/standalone/tutorials/05_controllers/ik_control.py
 16
 17"""
 18
 19"""Launch Isaac Sim Simulator first."""
 20
 21import argparse
 22
 23from omni.isaac.lab.app import AppLauncher
 24
 25# add argparse arguments
 26parser = argparse.ArgumentParser(description="Tutorial on using the differential IK controller.")
 27parser.add_argument("--robot", type=str, default="franka_panda", help="Name of the robot.")
 28parser.add_argument("--num_envs", type=int, default=128, help="Number of environments to spawn.")
 29# append AppLauncher cli args
 30AppLauncher.add_app_launcher_args(parser)
 31# parse the arguments
 32args_cli = parser.parse_args()
 33
 34# launch omniverse app
 35app_launcher = AppLauncher(args_cli)
 36simulation_app = app_launcher.app
 37
 38"""Rest everything follows."""
 39
 40import torch
 41
 42import omni.isaac.lab.sim as sim_utils
 43from omni.isaac.lab.assets import AssetBaseCfg
 44from omni.isaac.lab.controllers import DifferentialIKController, DifferentialIKControllerCfg
 45from omni.isaac.lab.managers import SceneEntityCfg
 46from omni.isaac.lab.markers import VisualizationMarkers
 47from omni.isaac.lab.markers.config import FRAME_MARKER_CFG
 48from omni.isaac.lab.scene import InteractiveScene, InteractiveSceneCfg
 49from omni.isaac.lab.utils import configclass
 50from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR
 51from omni.isaac.lab.utils.math import subtract_frame_transforms
 52
 53##
 54# Pre-defined configs
 55##
 56from omni.isaac.lab_assets import FRANKA_PANDA_HIGH_PD_CFG, UR10_CFG  # isort:skip
 57
 58
 59@configclass
 60class TableTopSceneCfg(InteractiveSceneCfg):
 61    """Configuration for a cart-pole scene."""
 62
 63    # ground plane
 64    ground = AssetBaseCfg(
 65        prim_path="/World/defaultGroundPlane",
 66        spawn=sim_utils.GroundPlaneCfg(),
 67        init_state=AssetBaseCfg.InitialStateCfg(pos=(0.0, 0.0, -1.05)),
 68    )
 69
 70    # lights
 71    dome_light = AssetBaseCfg(
 72        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
 73    )
 74
 75    # mount
 76    table = AssetBaseCfg(
 77        prim_path="{ENV_REGEX_NS}/Table",
 78        spawn=sim_utils.UsdFileCfg(
 79            usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/Stand/stand_instanceable.usd", scale=(2.0, 2.0, 2.0)
 80        ),
 81    )
 82
 83    # articulation
 84    if args_cli.robot == "franka_panda":
 85        robot = FRANKA_PANDA_HIGH_PD_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
 86    elif args_cli.robot == "ur10":
 87        robot = UR10_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
 88    else:
 89        raise ValueError(f"Robot {args_cli.robot} is not supported. Valid: franka_panda, ur10")
 90
 91
 92def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
 93    """Runs the simulation loop."""
 94    # Extract scene entities
 95    # note: we only do this here for readability.
 96    robot = scene["robot"]
 97
 98    # Create controller
 99    diff_ik_cfg = DifferentialIKControllerCfg(command_type="pose", use_relative_mode=False, ik_method="dls")
100    diff_ik_controller = DifferentialIKController(diff_ik_cfg, num_envs=scene.num_envs, device=sim.device)
101
102    # Markers
103    frame_marker_cfg = FRAME_MARKER_CFG.copy()
104    frame_marker_cfg.markers["frame"].scale = (0.1, 0.1, 0.1)
105    ee_marker = VisualizationMarkers(frame_marker_cfg.replace(prim_path="/Visuals/ee_current"))
106    goal_marker = VisualizationMarkers(frame_marker_cfg.replace(prim_path="/Visuals/ee_goal"))
107
108    # Define goals for the arm
109    ee_goals = [
110        [0.5, 0.5, 0.7, 0.707, 0, 0.707, 0],
111        [0.5, -0.4, 0.6, 0.707, 0.707, 0.0, 0.0],
112        [0.5, 0, 0.5, 0.0, 1.0, 0.0, 0.0],
113    ]
114    ee_goals = torch.tensor(ee_goals, device=sim.device)
115    # Track the given command
116    current_goal_idx = 0
117    # Create buffers to store actions
118    ik_commands = torch.zeros(scene.num_envs, diff_ik_controller.action_dim, device=robot.device)
119    ik_commands[:] = ee_goals[current_goal_idx]
120
121    # Specify robot-specific parameters
122    if args_cli.robot == "franka_panda":
123        robot_entity_cfg = SceneEntityCfg("robot", joint_names=["panda_joint.*"], body_names=["panda_hand"])
124    elif args_cli.robot == "ur10":
125        robot_entity_cfg = SceneEntityCfg("robot", joint_names=[".*"], body_names=["ee_link"])
126    else:
127        raise ValueError(f"Robot {args_cli.robot} is not supported. Valid: franka_panda, ur10")
128    # Resolving the scene entities
129    robot_entity_cfg.resolve(scene)
130    # Obtain the frame index of the end-effector
131    # For a fixed base robot, the frame index is one less than the body index. This is because
132    # the root body is not included in the returned Jacobians.
133    if robot.is_fixed_base:
134        ee_jacobi_idx = robot_entity_cfg.body_ids[0] - 1
135    else:
136        ee_jacobi_idx = robot_entity_cfg.body_ids[0]
137
138    # Define simulation stepping
139    sim_dt = sim.get_physics_dt()
140    count = 0
141    # Simulation loop
142    while simulation_app.is_running():
143        # reset
144        if count % 150 == 0:
145            # reset time
146            count = 0
147            # reset joint state
148            joint_pos = robot.data.default_joint_pos.clone()
149            joint_vel = robot.data.default_joint_vel.clone()
150            robot.write_joint_state_to_sim(joint_pos, joint_vel)
151            robot.reset()
152            # reset actions
153            ik_commands[:] = ee_goals[current_goal_idx]
154            joint_pos_des = joint_pos[:, robot_entity_cfg.joint_ids].clone()
155            # reset controller
156            diff_ik_controller.reset()
157            diff_ik_controller.set_command(ik_commands)
158            # change goal
159            current_goal_idx = (current_goal_idx + 1) % len(ee_goals)
160        else:
161            # obtain quantities from simulation
162            jacobian = robot.root_physx_view.get_jacobians()[:, ee_jacobi_idx, :, robot_entity_cfg.joint_ids]
163            ee_pose_w = robot.data.body_state_w[:, robot_entity_cfg.body_ids[0], 0:7]
164            root_pose_w = robot.data.root_state_w[:, 0:7]
165            joint_pos = robot.data.joint_pos[:, robot_entity_cfg.joint_ids]
166            # compute frame in root frame
167            ee_pos_b, ee_quat_b = subtract_frame_transforms(
168                root_pose_w[:, 0:3], root_pose_w[:, 3:7], ee_pose_w[:, 0:3], ee_pose_w[:, 3:7]
169            )
170            # compute the joint commands
171            joint_pos_des = diff_ik_controller.compute(ee_pos_b, ee_quat_b, jacobian, joint_pos)
172
173        # apply actions
174        robot.set_joint_position_target(joint_pos_des, joint_ids=robot_entity_cfg.joint_ids)
175        scene.write_data_to_sim()
176        # perform step
177        sim.step()
178        # update sim-time
179        count += 1
180        # update buffers
181        scene.update(sim_dt)
182
183        # obtain quantities from simulation
184        ee_pose_w = robot.data.body_state_w[:, robot_entity_cfg.body_ids[0], 0:7]
185        # update marker positions
186        ee_marker.visualize(ee_pose_w[:, 0:3], ee_pose_w[:, 3:7])
187        goal_marker.visualize(ik_commands[:, 0:3] + scene.env_origins, ik_commands[:, 3:7])
188
189
190def main():
191    """Main function."""
192    # Load kit helper
193    sim_cfg = sim_utils.SimulationCfg(dt=0.01)
194    sim = sim_utils.SimulationContext(sim_cfg)
195    # Set main camera
196    sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0])
197    # Design scene
198    scene_cfg = TableTopSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
199    scene = InteractiveScene(scene_cfg)
200    # Play the simulator
201    sim.reset()
202    # Now we are ready!
203    print("[INFO]: Setup complete...")
204    # Run the simulator
205    run_simulator(sim, scene)
206
207
208if __name__ == "__main__":
209    # run the main function
210    main()
211    # close sim app
212    simulation_app.close()

代码解释#

在使用任何任务空间控制器时,确保所提供的量在正确的框架中是很重要的。当并行化环境实例时,它们都存在于同一个独特的模拟世界框架中。然而,通常,我们希望每个环境本身都有自己的本地框架。这可以通过 scene.InteractiveScene.env_origins 属性访问。

在我们的 API 中,我们使用以下符号来表示框架:

  • 模拟世界框架(表示为 w ),这是整个模拟的框架。

  • 本地环境框架(表示为 e ),这是本地环境的框架。

  • 机器人的基础框架(表示为 b ),这是机器人基座链接的框架。

由于资产实例不 “认识” 本地环境框架,它们返回其状态在模拟世界框架中。因此,我们需要将获取的量转换为本地环境框架。这是通过从获取的量中减去本地环境原点来完成的。

创建一个 IK 控制器#

DifferentialIKController 类计算机器人达到期望末端执行器姿势所需的关节位置。所包含的实现以批量格式执行计算,并使用 PyTorch 操作。支持不同类型的逆运动学求解器,包括阻尼最小二乘法和伪逆方法。这些求解器可以使用 ik_method 参数进行指定。此外,该控制器可以处理相对和绝对姿势命令。

在本教程中,我们将使用阻尼最小二乘法来计算期望的关节位置。此外,由于我们希望跟踪期望的末端执行器姿势,我们将使用绝对姿势命令模式。

    # Create controller
    diff_ik_cfg = DifferentialIKControllerCfg(command_type="pose", use_relative_mode=False, ik_method="dls")
    diff_ik_controller = DifferentialIKController(diff_ik_cfg, num_envs=scene.num_envs, device=sim.device)

获取机器人的关节和体指数#

IK 控制器的实现是一个仅计算类。因此,它期望用户提供有关机器人的必要信息。这包括机器人的关节位置、当前末端执行器姿势和雅可比矩阵。

虽然属性 assets.ArticulationData.joint_pos 提供了关节位置,但我们只想要机器人手臂的关节位置,而不是夹具。类似地,虽然属性 assets.ArticulationData.body_state_w 提供了所有机器人身体的状态,但我们只想要机器人末端执行器的状态。因此,我们需要对这些数组进行索引以获取所需的量。

为此,关节类提供了方法 find_joints()find_bodies() 。这些方法接受关节和体的名称,并返回它们对应的索引。

虽然您可以直接使用这些方法来获取索引,但我们建议使用 SceneEntityCfg 类解析索引。此类在各个 API 中用于从场景实体中提取某些信息。内部调用上述方法以获取索引。但是,它还执行一些额外的检查以确保提供的名称是有效的。因此,使用此类是一个更安全的选择。

    # Specify robot-specific parameters
    if args_cli.robot == "franka_panda":
        robot_entity_cfg = SceneEntityCfg("robot", joint_names=["panda_joint.*"], body_names=["panda_hand"])
    elif args_cli.robot == "ur10":
        robot_entity_cfg = SceneEntityCfg("robot", joint_names=[".*"], body_names=["ee_link"])
    else:
        raise ValueError(f"Robot {args_cli.robot} is not supported. Valid: franka_panda, ur10")
    # Resolving the scene entities
    robot_entity_cfg.resolve(scene)
    # Obtain the frame index of the end-effector
    # For a fixed base robot, the frame index is one less than the body index. This is because
    # the root body is not included in the returned Jacobians.
    if robot.is_fixed_base:
        ee_jacobi_idx = robot_entity_cfg.body_ids[0] - 1
    else:
        ee_jacobi_idx = robot_entity_cfg.body_ids[0]

计算机器人命令#

IK 控制器将设置所需命令和计算所需关节位置的操作分开。这样做是为了允许用户以不同的频率运行 IK 控制器而非机器人的控制频率。

set_command() 方法将期望的末端执行器姿势作为单个批量数组输入。姿势在机器人的基座框架中指定。

            # reset controller
            diff_ik_controller.reset()
            diff_ik_controller.set_command(ik_commands)

然后,我们可以使用 compute() 方法计算所需的关节位置。该方法接受当前末端执行器姿势(在基座框架中)、雅可比矩阵和当前关节位置为输入。我们从机器人数据中读取雅可比矩阵,该数据使用其从物理引擎计算的价值。

            # obtain quantities from simulation
            jacobian = robot.root_physx_view.get_jacobians()[:, ee_jacobi_idx, :, robot_entity_cfg.joint_ids]
            ee_pose_w = robot.data.body_state_w[:, robot_entity_cfg.body_ids[0], 0:7]
            root_pose_w = robot.data.root_state_w[:, 0:7]
            joint_pos = robot.data.joint_pos[:, robot_entity_cfg.joint_ids]
            # compute frame in root frame
            ee_pos_b, ee_quat_b = subtract_frame_transforms(
                root_pose_w[:, 0:3], root_pose_w[:, 3:7], ee_pose_w[:, 0:3], ee_pose_w[:, 3:7]
            )
            # compute the joint commands
            joint_pos_des = diff_ik_controller.compute(ee_pos_b, ee_quat_b, jacobian, joint_pos)

计算得到的关节位置目标然后可以应用在机器人上,就像在以前的教程中所做的那样。

        # apply actions
        robot.set_joint_position_target(joint_pos_des, joint_ids=robot_entity_cfg.joint_ids)
        scene.write_data_to_sim()

代码执行#

现在,我们已经查看了代码,请运行脚本并查看结果:

./isaaclab.sh -p source/standalone/tutorials/05_controllers/run_diff_ik.py --robot franka_panda --num_envs 128

该脚本将启动一个具有 128 个机器人的模拟。使用 IK 控制器来控制这些机器人。当前和期望的末端执行器姿势应该使用框架标记显示。当机器人达到期望姿势时,命令应该循环到脚本中指定的下一个姿势。

run_diff_ik.py 的结果

要停止模拟,您可以关闭窗口,或在终端中按下 Ctrl+C