Skip to content
清晨的一缕阳光
返回

仿真工具使用

仿真工具使用

仿真是在虚拟环境中测试和验证机器人算法的关键技术。人形机器人作为复杂系统,在实机测试前需要大量仿真验证。本文深入解析 MuJoCo、PyBullet、Isaac Sim、Gazebo 等主流仿真平台的使用方法与适用场景。

一、仿真平台对比

1.1 主流平台

平台开发商物理引擎价格特点适用场景
MuJoCoGoogle自研免费快速、精确研究、控制
PyBulletCMUBullet免费易用、Python教育、RL
Isaac SimNVIDIAPhysX免费(学术)GPU 加速、逼真大规模训练
GazeboOpen RoboticsODE/Bullet免费ROS 集成ROS 开发
WebotsCyberboticsODE免费易用、跨平台教育、研究
DrakeMIT自研免费严谨、形式化控制理论

1.2 选择建议

仿真平台选择指南

研究用途:
├── 控制算法:MuJoCo、Drake
├── 强化学习:PyBullet、Isaac Sim
└── 论文发表:MuJoCo(引用率高)

工程开发:
├── ROS 集成:Gazebo
├── 视觉算法:Isaac Sim
└── 快速原型:PyBullet

教育用途:
├── 入门:Webots、PyBullet
└── 进阶:MuJoCo、Isaac Sim

企业应用:
├── 大规模训练:Isaac Sim(GPU 加速)
├── 数字孪生:Isaac Sim
└── 快速迭代:PyBullet

二、MuJoCo

2.1 安装

# 1. 安装 MuJoCo(Google 收购后免费)
pip install mujoco

# 2. 安装 Python 绑定
pip install mujoco-python

# 3. 验证
python -c "import mujoco; print(mujoco.__version__)"

2.2 基本使用

import mujoco
import mujoco_viewer
import numpy as np

# 1. 加载模型
model = mujoco.MjModel.from_xml_path('humanoid.xml')
data = mujoco.MjData(model)

# 2. 创建可视化
viewer = mujoco_viewer.MujocoViewer(model, data)

# 3. 仿真循环
for _ in range(1000):
    # 控制输入
    data.ctrl = np.random.randn(model.nu) * 0.1
    
    # 仿真步
    mujoco.mj_step(model, data)
    
    # 渲染
    viewer.render()

# 4. 关闭
viewer.close()

2.3 人形机器人模型

<!-- humanoid.xml -->

<mujoco model="humanoid">
  
  <!-- 选项 -->
  <option timestep="0.002" gravity="0 0 -9.81"/>
  
  <!-- 世界 -->
  <worldbody>
    <!-- 地面 -->
    <geom name="floor" type="plane" size="10 10 0.1" rgba="0.5 0.5 0.5 1"/>
    
    <!-- 人形机器人 -->
    <body name="torso" pos="0 0 1.5">
      <!-- 躯干 -->
      <geom name="torso" type="box" size="0.15 0.2 0.4" rgba="0.8 0.5 0.5 1"/>
      
      <!-- 左腿 -->
      <body name="left_thigh" pos="0 -0.15 -0.4">
        <joint name="left_hip_pitch" axis="0 1 0" range="-0.5 2.0"/>
        <joint name="left_hip_roll" axis="1 0 0" range="-0.5 0.5"/>
        <geom name="left_thigh" type="capsule" size="0.08" fromto="0 0 0 0 0 -0.4"/>
        
        <body name="left_shin" pos="0 0 -0.4">
          <joint name="left_knee" axis="0 1 0" range="0 2.5"/>
          <geom name="left_shin" type="capsule" size="0.06" fromto="0 0 0 0 0 -0.4"/>
          
          <body name="left_foot" pos="0 0 -0.4">
            <joint name="left_ankle" axis="0 1 0" range="-0.5 0.5"/>
            <geom name="left_foot" type="box" size="0.1 0.05 0.02"/>
          </body>
        </body>
      </body>
      
      <!-- 右腿(对称) -->
      <body name="right_thigh" pos="0 0.15 -0.4">
        <!-- ... -->
      </body>
      
      <!-- 手臂 -->
      <body name="left_arm" pos="0.15 -0.25 0.3">
        <!-- ... -->
      </body>
      
      <body name="right_arm" pos="0.15 0.25 0.3">
        <!-- ... -->
      </body>
    </body>
  </worldbody>
  
  <!-- 执行器 -->
  <actuator>
    <motor name="left_hip_pitch" joint="left_hip_pitch" ctrlrange="-100 100"/>
    <motor name="left_hip_roll" joint="left_hip_roll" ctrlrange="-50 50"/>
    <motor name="left_knee" joint="left_knee" ctrlrange="-150 150"/>
    <!-- ... -->
  </actuator>
  
</mujoco>

2.4 强化学习训练

import gymnasium as gym
from stable_baselines3 import PPO

# 1. 创建环境
env = gym.make('Humanoid-v4')  # MuJoCo 人形机器人环境

# 2. 创建 PPO 智能体
model = PPO(
    'MlpPolicy',
    env,
    verbose=1,
    tensorboard_log='./ppo_humanoid_tensorboard/'
)

# 3. 训练
model.learn(total_timesteps=10_000_000)

# 4. 保存
model.save('ppo_humanoid')

# 5. 测试
obs = env.reset()
for _ in range(1000):
    action, _states = model.predict(obs, deterministic=True)
    obs, rewards, dones, info = env.step(action)
    env.render()

三、PyBullet

3.1 安装

pip install pybullet
pip install gymnasium[bullet]

3.2 基本使用

import pybullet as p
import pybullet_data
import time

# 1. 连接
p.connect(p.GUI)  # 或 p.DIRECT(无界面)

# 2. 添加数据路径
p.setAdditionalSearchPath(pybullet_data.getDataPath())

# 3. 设置重力
p.setGravity(0, 0, -9.81)

# 4. 加载地面
plane_id = p.loadURDF("plane.urdf")

# 5. 加载人形机器人
robot_id = p.loadURDF(
    "humanoid.urdf",
    basePosition=[0, 0, 1.5],
    useFixedBase=False
)

# 6. 获取关节信息
num_joints = p.getNumJoints(robot_id)
for i in range(num_joints):
    info = p.getJointInfo(robot_id, i)
    print(f"Joint {i}: {info[1].decode()}")

# 7. 仿真循环
for _ in range(10000):
    # 控制关节
    p.setJointMotorControl2(
        robot_id,
        jointIndex=0,
        controlMode=p.POSITION_CONTROL,
        targetPosition=0.5
    )
    
    # 仿真步
    p.stepSimulation()
    
    # 延迟(实时)
    time.sleep(1./240.)

# 8. 断开
p.disconnect()

3.3 Gym 环境

import gymnasium as gym

# 创建环境
env = gym.make('HumanoidBulletEnv-v0')

# 重置
obs = env.reset()

# 仿真
for _ in range(1000):
    action = env.action_space.sample()
    obs, reward, done, truncated, info = env.step(action)
    env.render()

env.close()

3.4 自定义环境

import pybullet as p
import pybullet_data
import numpy as np
import gymnasium as gym
from gymnasium import spaces

class HumanoidEnv(gym.Env):
    """
    自定义人形机器人环境
    """
    def __init__(self):
        super().__init__()
        
        # 连接(无界面,用于训练)
        self.client = p.connect(p.DIRECT)
        
        # 加载模型
        p.setAdditionalSearchPath(pybullet_data.getDataPath())
        p.loadURDF("plane.urdf")
        self.robot = p.loadURDF(
            "humanoid.urdf",
            basePosition=[0, 0, 1.5]
        )
        
        # 动作空间
        num_joints = p.getNumJoints(self.robot)
        self.action_space = spaces.Box(
            low=-1, high=1,
            shape=(num_joints,),
            dtype=np.float32
        )
        
        # 观测空间
        self.observation_space = spaces.Box(
            low=-np.inf, high=np.inf,
            shape=(num_joints * 3,),  # 位置 + 速度
            dtype=np.float32
        )
    
    def reset(self):
        """
        重置环境
        """
        p.resetSimulation()
        
        # 重置机器人位置
        p.resetBasePositionAndOrientation(
            self.robot,
            [0, 0, 1.5],
            [0, 0, 0, 1]
        )
        
        # 重置关节
        for i in range(p.getNumJoints(self.robot)):
            p.resetJointState(self.robot, i, 0, 0)
        
        # 获取观测
        obs = self._get_observation()
        
        return obs, {}
    
    def step(self, action):
        """
        执行动作
        """
        # 应用控制
        for i, torque in enumerate(action):
            p.setJointMotorControl2(
                self.robot,
                i,
                p.TORQUE_CONTROL,
                force=torque * 10  # 缩放
            )
        
        # 仿真步
        for _ in range(10):  # 多步仿真
            p.stepSimulation()
        
        # 获取观测
        obs = self._get_observation()
        
        # 计算奖励(前进距离)
        pos, _ = p.getBasePositionAndOrientation(self.robot)
        reward = pos[0]  # x 方向前进
        
        # 检查终止
        done = pos[2] < 0.5  # 摔倒
        
        truncated = False
        
        info = {'position': pos}
        
        return obs, reward, done, truncated, info
    
    def _get_observation(self):
        """
        获取观测
        """
        obs = []
        for i in range(p.getNumJoints(self.robot)):
            state = p.getJointState(self.robot, i)
            obs.extend([state[0], state[1]])  # 位置 + 速度
        
        return np.array(obs, dtype=np.float32)
    
    def render(self):
        """
        渲染(切换到 GUI)
        """
        p.disconnect(self.client)
        self.client = p.connect(p.GUI)
        # 重新加载...
    
    def close(self):
        """
        关闭
        """
        p.disconnect()

四、NVIDIA Isaac Sim

4.1 安装

# 系统要求
# - NVIDIA RTX GPU(8GB+ VRAM)
# - Ubuntu 20.04/22.04
# - CUDA 11.4+

# 1. 注册 NVIDIA 开发者账号
# https://developer.nvidia.com/

# 2. 下载 Isaac Sim
# https://developer.nvidia.com/isaac-sim

# 3. 安装(Omniverse)
# 按照官方安装指南

# 4. 验证
./isaac-sim.sh

4.2 Python API

from omni.isaac.kit import SimulationApp

# 1. 启动仿真
simulation_app = SimulationApp({"headless": False})

# 2. 导入 Isaac Sim 模块
from omni.isaac.core import World
from omni.isaac.core.robots import Robot
from omni.isaac.core.utils.stage import add_reference_to_stage

# 3. 创建世界
world = World(
    stage_units_in_meters=1.0,
    rendering_dt=1.0/60.0,
    backend="torch"
)

# 4. 添加机器人
robot = Robot(
    prim_path="/World/humanoid",
    name="humanoid",
    translation=[0, 0, 1.5]
)

# 5. 添加地面
world.scene.add_default_ground_plane()

# 6. 初始化
world.reset()

# 7. 仿真循环
for i in range(1000):
    # 获取观测
    positions, velocities = robot.get_joint_positions(), robot.get_joint_velocities()
    
    # 控制
    target_positions = [0.5] * robot.num_joints
    robot.set_joint_positions(target_positions)
    
    # 仿真步
    world.step(render=True)

# 8. 关闭
simulation_app.close()

4.3 强化学习

from omni.isaac.rl import RLEnv
from omni.isaac.core.tasks import BaseTask

class HumanoidTask(BaseTask):
    """
    人形机器人 RL 任务
    """
    def __init__(self):
        super().__init__(name="humanoid_walk")
    
    def set_up_scene(self, scene):
        super().set_up_scene(scene)
        
        # 添加机器人
        self.humanoid = scene.add(
            Robot(
                prim_path="/World/humanoid",
                name="humanoid"
            )
        )
    
    def get_observations(self):
        return {
            "joint_positions": self.humanoid.get_joint_positions(),
            "joint_velocities": self.humanoid.get_joint_velocities(),
        }
    
    def calculate_metrics(self):
        # 奖励:前进距离
        pos = self.humanoid.get_world_pose()[0]
        reward = pos[0]
        
        return {"reward": reward}
    
    def is_done(self):
        # 终止:摔倒
        pos = self.humanoid.get_world_pose()[0]
        return pos[2] < 0.5

# 使用 SKRL 或 RSL-RL 训练

4.4 Domain Randomization

from omni.isaac.core.utils.prims import get_prim_at_path
from pxr import UsdGeom
import numpy as np

class DomainRandomization:
    """
    域随机化
    """
    def __init__(self):
        self.ranges = {
            'mass': (0.9, 1.1),
            'friction': (0.5, 1.5),
            'gravity': (9.7, 9.9),
        }
    
    def randomize(self):
        """
        随机化环境参数
        """
        # 质量随机化
        for link in self.get_links():
            mass_prim = get_prim_at_path(link)
            mass_attr = UsdGeom.MassProperties(mass_prim)
            mass_attr.GetDensityAttr().Set(
                np.random.uniform(*self.ranges['mass'])
            )
        
        # 摩擦随机化
        for contact_prim in self.get_contact_surfaces():
            # 设置摩擦系数
            pass
        
        # 重力随机化
        p.setGravity(
            0, 0, 
            -np.random.uniform(*self.ranges['gravity'])
        )

五、Gazebo

5.1 安装(ROS2 Humble)

# Gazebo 已随 ROS2 安装
sudo apt install ros-humble-gazebo-ros-pkgs

# 验证
gz sim --version

5.2 基本使用

# 1. 启动 Gazebo
gz sim shapes.sdf

# 2. 启动带 ROS2 桥接
gz sim humanoid.world --ros2

# 3. 从 ROS2 启动
ros2 launch gazebo_ros gz_sim.launch.py \
  gz_args:="humanoid.world"

5.3 URDF 转 SDF

# URDF → SDF
gz sdf -p humanoid.urdf > humanoid.sdf

# 或使用 ROS2 工具
ros2 run urdf_to_gazebo urdf_to_gazebo \
  --urdf humanoid.urdf \
  --output humanoid.sdf

5.4 ROS2 集成

#!/usr/bin/env python3
# Gazebo + ROS2 控制节点

import rclpy
from rclpy.node import Node
from gazebo_msgs.msg import ModelState
from gazebo_msgs.srv import SetModelState

class GazeboController(Node):
    """
    Gazebo 机器人控制
    """
    def __init__(self):
        super().__init__('gazebo_controller')
        
        # 创建服务客户端
        self.client = self.create_client(
            SetModelState,
            '/set_model_state'
        )
    
    def set_robot_pose(self, x, y, z):
        """
        设置机器人位姿
        """
        request = SetModelState.Request()
        request.model_state.model_name = 'humanoid'
        request.model_state.pose.position.x = x
        request.model_state.pose.position.y = y
        request.model_state.pose.position.z = z
        
        future = self.client.call_async(request)
        rclpy.spin_until_future_complete(self, future)

六、Drake

6.1 安装

# Ubuntu
sudo apt install lsb-release wget
wget https://drake-apt.csail.mit.edu/drake.asc
wget https://drake-apt.csail.mit.edu/drake.list
sudo mv drake.asc /etc/apt/trusted.gpg.d/
sudo mv drake.list /etc/apt/sources.list.d/
sudo apt update
sudo apt install drake

# Python
pip install drake

6.2 基本使用

from pydrake.all import (
    DiagramBuilder,
    Simulator,
    Parser,
    MeshcatVisualizer
)

# 1. 创建图
builder = DiagramBuilder()

# 2. 加载模型
plant, scene_graph = builder.AddSystem(
    MultibodyPlant(time_step=0.0)
)
Parser(plant).AddModelFromFile('humanoid.urdf')
plant.Finalize()

# 3. 添加可视化
visualizer = MeshcatVisualizer.AddToBuilder(
    builder, scene_graph
)

# 4. 构建图
diagram = builder.Build()

# 5. 创建仿真器
simulator = Simulator(diagram)
simulator.set_target_realtime_rate(1.0)

# 6. 运行仿真
simulator.AdvanceTo(10.0)

七、总结

7.1 平台对比

平台优点缺点最佳场景
MuJoCo快速、精确、RL 友好图形一般控制研究、RL
PyBullet易用、免费、Python精度一般教育、快速原型
Isaac SimGPU 加速、逼真、功能强安装复杂、资源需求高大规模训练、数字孪生
GazeboROS 集成好、免费慢、精度一般ROS 开发
Drake严谨、形式化验证学习曲线陡控制理论验证

7.2 推荐组合

推荐仿真组合

学术研究:
├── 控制算法:MuJoCo
├── 强化学习:MuJoCo + Gym
└── 论文验证:Drake

工程开发:
├── ROS 集成:Gazebo
├── 视觉算法:Isaac Sim
└── 快速测试:PyBullet

企业应用:
├── 大规模训练:Isaac Sim
├── 数字孪生:Isaac Sim
└── 快速迭代:PyBullet

7.3 学习路径

仿真学习路径

入门(1-2 周):
├── PyBullet 基础
├── 加载 URDF
└── 简单控制

进阶(1 月):
├── MuJoCo 深入
├── 强化学习集成
└── 自定义环境

专业(3 月+):
├── Isaac Sim 大规模训练
├── Domain Randomization
└── Sim2Real 迁移

仿真是人形机器人开发的加速器。选择合适的仿真平台,可以在虚拟环境中快速迭代算法,降低实机测试风险。建议从 PyBullet 入门,逐步过渡到 MuJoCo 或 Isaac Sim,最终实现 Sim2Real 迁移。


参考资料


分享这篇文章到:

上一篇文章
Redis 哨兵集群实战
下一篇文章
AI 应用性能优化实战案例