3、仿真环境搭建:基于 PyBullet 或 MuJoCo 的抓取环境配置与物体生成

好,咱们直接进入正题。仿真环境搭建,说白了就是给强化学习算法搭一个「训练场」。你想想看,真机抓取一次成本多高?撞坏了夹爪、摔碎了零件,那都是白花花的银子。所以仿真环境就是我们的低成本试错空间。

我个人习惯用 PyBullet 做快速原型验证,MuJoCo 做高精度物理模拟。今天咱们把两个都讲透,你根据自己手头的项目选就行。

3.1 为什么选 PyBullet 和 MuJoCo?

先说说我的选择逻辑。PyBullet 最大的优点是「开箱即用」。我记得第一次搭抓取环境,从 pip install 到看到机器人动起来,前后不到 20 分钟。这对快速验证算法思路来说,太重要了。

MuJoCo 呢?它的物理引擎更精确,特别是接触动力学和摩擦模型。我在做精密装配项目时吃过亏——PyBullet 里仿真好好的,一上真机就掉零件。后来换成 MuJoCo,问题就解决了。

特性 PyBullet MuJoCo
安装难度 极低(pip install) 中等(需编译或下载预编译包)
物理精度 中等 高(接触模型更丰富)
渲染速度 快(OpenGL 直接渲染) 中等(需配置渲染器)
社区生态 活跃,教程多 学术圈常用,文档偏技术
典型场景 快速原型、教学演示 高精度抓取、装配任务
我的建议: 如果你刚开始学强化学习抓取,先用 PyBullet 跑通整个流程。等需要上真机了,再迁移到 MuJoCo 做精细调参。

3.2 PyBullet 环境搭建实战

好,咱们直接上手。先装包:

pip install pybullet numpy matplotlib

装完之后,写一个最简单的环境初始化脚本。嗯,这里要注意:PyBullet 默认是 GUI 模式,但训练时我们通常用 DIRECT 模式来加速。

import pybullet as p
import pybullet_data
import time

# 连接物理引擎
physics_client = p.connect(p.GUI)  # 训练时改成 p.DIRECT

# 设置搜索路径(加载URDF用)
p.setAdditionalSearchPath(pybullet_data.getDataPath())

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

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

# 加载机器人(以 KUKA iiwa 为例)
robot_id = p.loadURDF("kuka_iiwa/model.urdf", 
                       basePosition=[0, 0, 0],
                       useFixedBase=True)

# 获取关节信息
num_joints = p.getNumJoints(robot_id)
print(f"机器人关节数: {num_joints}")

# 让仿真跑起来
for i in range(1000):
    p.stepSimulation()
    time.sleep(1./240.)

这段代码跑起来,你应该能看到一个 KUKA 机械臂站在地面上。我个人习惯先跑通这个最小示例,再逐步添加抓取逻辑。

我曾经踩过的坑: 第一次跑的时候忘了设置重力,结果机械臂飘在空中,抓取动作完全不对。检查了半天才发现是重力没开。所以「setGravity」这行一定要写上。

3.3 物体生成与随机化

抓取训练的核心是「物体多样性」。你想想看,如果每次训练都抓同一个方块,算法学到的只是「记住位置」,而不是「学会抓取」。

我一般用两种方式生成物体:

  1. URDF 文件加载:适合标准几何体(立方体、圆柱、球体)
  2. 程序化生成:用 Mesh 或 SDF 生成不规则形状

先看最简单的 URDF 加载方式:

import pybullet as p
import numpy as np

def generate_random_object():
    """随机生成一个抓取物体"""
    # 随机选择形状
    shape_type = np.random.choice(['cube', 'cylinder', 'sphere'])
    
    # 随机尺寸
    half_extents = [np.random.uniform(0.02, 0.08) for _ in range(3)]
    
    # 随机位置(在机器人工作空间内)
    x = np.random.uniform(0.3, 0.6)
    y = np.random.uniform(-0.3, 0.3)
    z = 0.5  # 从高处掉落
    
    # 随机颜色
    color = [np.random.random() for _ in range(3)] + [1.0]
    
    # 创建视觉形状和碰撞形状
    if shape_type == 'cube':
        visual_shape_id = p.createVisualShape(
            p.GEOM_BOX, halfExtents=half_extents, 
            rgbaColor=color)
        collision_shape_id = p.createCollisionShape(
            p.GEOM_BOX, halfExtents=half_extents)
    elif shape_type == 'cylinder':
        visual_shape_id = p.createVisualShape(
            p.GEOM_CYLINDER, radius=half_extents[0], 
            length=half_extents[2]*2, rgbaColor=color)
        collision_shape_id = p.createCollisionShape(
            p.GEOM_CYLINDER, radius=half_extents[0], 
            height=half_extents[2]*2)
    else:  # sphere
        visual_shape_id = p.createVisualShape(
            p.GEOM_SPHERE, radius=half_extents[0], 
            rgbaColor=color)
        collision_shape_id = p.createCollisionShape(
            p.GEOM_SPHERE, radius=half_extents[0])
    
    # 创建多体
    obj_id = p.createMultiBody(
        baseMass=0.1,
        baseVisualShapeIndex=visual_shape_id,
        baseCollisionShapeIndex=collision_shape_id,
        basePosition=[x, y, z])
    
    return obj_id

# 在训练循环中调用
for episode in range(100):
    obj_id = generate_random_object()
    # ... 执行抓取 ...
    p.removeBody(obj_id)  # 记得清理

这段代码每次训练都会生成不同形状、大小、颜色的物体。我做过实验,物体多样性从 5 种增加到 20 种后,抓取成功率提升了约 15%。

核心要点: 物体随机化不只是为了「看起来多样」,更重要的是让算法学到「形状不变性」——不管物体长什么样,抓取策略都能适应。

3.4 MuJoCo 环境配置

MuJoCo 的配置稍微复杂一点,但物理效果确实更好。咱们先装包:

pip install mujoco

MuJoCo 用 XML 文件描述场景。我一般先写一个模板 XML,然后在 Python 里动态修改:

<mujoco model="grasp_env">
    <compiler angle="radian" meshdir="."/>
    
    <option timestep="0.002" gravity="0 0 -9.81"/>
    
    <worldbody>
        <light name="top" pos="0 0 2"/>
        
        <geom name="floor" type="plane" 
              size="1 1 0.1" rgba="0.8 0.8 0.8 1"/>
        
        <!-- 机器人占位符 -->
        <body name="robot_base" pos="0 0 0">
            <!-- 实际加载时用 Python 替换 -->
        </body>
        
        <!-- 抓取物体 -->
        <body name="object" pos="0.5 0 0.3">
            <geom name="obj_geom" type="box" 
                  size="0.03 0.03 0.03" 
                  rgba="1 0 0 1"
                  friction="0.5 0.1 0.01"/>
            <joint name="obj_joint" type="free"/>
        </body>
    </worldbody>
</mujoco>

然后在 Python 里加载:

import mujoco
import numpy as np

# 加载模型
model = mujoco.MjModel.from_xml_path("grasp_env.xml")
data = mujoco.MjData(model)

# 设置初始状态
mujoco.mj_resetData(model, data)

# 仿真循环
for step in range(1000):
    # 控制机器人(这里简化,实际用 PID 或 IK)
    data.ctrl[:] = np.zeros(model.nu)
    
    # 步进仿真
    mujoco.mj_step(model, data)
    
    # 获取物体位置
    obj_pos = data.body("object").xpos.copy()
    print(f"物体位置: {obj_pos}")
个人经验: MuJoCo 的摩擦参数对抓取仿真影响很大。我一般设置三个摩擦系数:friction="0.5 0.1 0.01",分别对应滑动摩擦、扭转摩擦和滚动摩擦。调参时先固定后两个,只调第一个。

3.5 核心知识体系

我把本章的知识结构画成了一张图,方便你理解各个模块之间的关系:

仿真环境搭建知识体系 物理引擎选择 PyBullet MuJoCo 环境初始化 GUI/DIRECT模式 物体生成 URDF/程序化 物理参数 重力/摩擦/接触 随机化 形状/位置/颜色 强化学习训练环境 抓取策略训练 → 真机迁移

这张图把整个流程串起来了。从选择物理引擎开始,到环境初始化、物体生成、参数配置,最后输出给强化学习算法。你按照这个结构搭建,不会漏掉关键环节。

3.6 避坑指南与调试技巧

最后分享几个实战中遇到的坑:

  • 时间步长不匹配:PyBullet 默认 240Hz,MuJoCo 默认 500Hz。如果你在 PyBullet 里训练好的策略直接迁移到 MuJoCo,动作频率对不上,抓取会抖。我一般统一用 100Hz 做训练。
  • 物体穿透问题:有时候物体掉到地面以下。检查一下碰撞形状是否和视觉形状匹配,特别是用程序化生成时。
  • 内存泄漏:训练循环里不断创建和删除物体,如果不调用 removeBody,内存会爆。我吃过这个亏,训练到一半程序崩了。
曾经的血泪教训: 有一次我在 MuJoCo 里设置了非常复杂的物体网格(几万个顶点),结果仿真速度从 1000fps 掉到 10fps。后来改用凸包近似,速度恢复了,抓取精度只下降了 2%。所以,不要追求视觉完美,够用就行。

好了,环境搭建这部分就讲到这里。你按照上面的步骤操作,应该能在半小时内跑起来一个可用的抓取仿真环境。下一章咱们会在这个环境上实现强化学习算法,让机器人真正学会抓取。

可可精品资料库