跳转至

CoppeliaSim Lua 脚本与强化学习

引言

CoppeliaSim 的一大特点是「脚本内嵌于场景」:每个对象都可以挂载 Lua 子脚本(Child Script),在仿真回调中直接读写关节、传感器与信号,无需外部进程即可实现完整的控制逻辑。这种设计使场景本身就是可运行的程序,但也要求开发者理解其回调时序与非阻塞约定。另一方面,当 CoppeliaSim 作为强化学习环境使用时,需要把仿真切换到步进模式并包装为 Gymnasium 接口,才能与主流训练框架对接。本页面介绍这两部分内容。

强化学习应用

CoppeliaSim 作为 Gymnasium 环境

CoppeliaSim 通过 Python ZeroMQ API 提供步进控制能力,天然适合作为强化学习环境的后端仿真器。以下展示如何将 CoppeliaSim 封装为 Gymnasium 兼容接口:

import gymnasium as gym
import numpy as np
import coppeliasim_zmqremoteapi_client as zmq_client


class CoppeliaSimEnv(gym.Env):
    """将 CoppeliaSim 仿真封装为 Gymnasium 标准环境"""

    def __init__(self):
        super().__init__()
        self.client = zmq_client.RemoteAPIClient()
        self.sim = self.client.require('sim')
        self.client.setStepping(True)

        # 定义动作空间:关节速度(6维连续)
        self.action_space = gym.spaces.Box(
            low=-1.0, high=1.0, shape=(6,), dtype=np.float32
        )
        # 定义观测空间:关节角度 + 末端位姿(6 + 7 = 13维)
        self.observation_space = gym.spaces.Box(
            low=-np.inf, high=np.inf, shape=(13,), dtype=np.float32
        )

        # 获取关节句柄
        self.joint_handles = [
            self.sim.getObject(f'/robot/joint{i}') for i in range(1, 7)
        ]
        self.tip_handle = self.sim.getObject('/robot/tip')
        self.target_handle = self.sim.getObject('/robot/target')

    def reset(self, seed=None, options=None):
        self.sim.stopSimulation()
        self.sim.startSimulation()
        obs = self._get_obs()
        return obs, {}

    def step(self, action):
        # 执行动作:设置关节速度
        for i, h in enumerate(self.joint_handles):
            self.sim.setJointTargetVelocity(h, float(action[i]))

        # 推进仿真一步
        self.client.step()

        obs = self._get_obs()
        reward = self._compute_reward()
        terminated = self._is_done()
        return obs, reward, terminated, False, {}

    def _get_obs(self):
        joint_angles = np.array([
            self.sim.getJointPosition(h) for h in self.joint_handles
        ], dtype=np.float32)
        tip_pose = np.array(
            self.sim.getObjectPose(self.tip_handle, self.sim.handle_world),
            dtype=np.float32
        )
        return np.concatenate([joint_angles, tip_pose])

    def _compute_reward(self):
        tip_pos = self.sim.getObjectPosition(
            self.tip_handle, self.sim.handle_world
        )
        target_pos = self.sim.getObjectPosition(
            self.target_handle, self.sim.handle_world
        )
        dist = np.linalg.norm(np.array(tip_pos) - np.array(target_pos))
        return -dist  # 奖励为负距离

    def _is_done(self):
        tip_pos = self.sim.getObjectPosition(
            self.tip_handle, self.sim.handle_world
        )
        target_pos = self.sim.getObjectPosition(
            self.target_handle, self.sim.handle_world
        )
        dist = np.linalg.norm(np.array(tip_pos) - np.array(target_pos))
        return dist < 0.02  # 距离目标 2cm 以内视为成功

    def close(self):
        self.sim.stopSimulation()

无头仿真(Headless Simulation)

在服务器或无显示器的环境中进行强化学习训练时,需要以无头模式运行 CoppeliaSim:

# 方法一:使用虚拟显示(Xvfb)
Xvfb :99 -screen 0 1024x768x24 &
export DISPLAY=:99

# 启动 CoppeliaSim(无 GUI 渲染,但保留物理仿真)
./coppeliaSim.sh -h scene.ttt &

# 然后运行 Python 训练脚本
python3 train_rl.py
# 方法二:直接使用无头模式参数
./coppeliaSim.sh -h -s scene.ttt
# -h: headless 模式,不创建 GUI 窗口
# -s: 自动加载指定场景

与其他仿真器的对比(强化学习场景)

特性 CoppeliaSim Gazebo PyBullet
物理精度 高(多引擎可选) 中(偏快速)
Python API 完整度 高(覆盖全部仿真功能)
步进控制 原生支持 支持 原生支持
无头模式 支持(需 Xvfb 或 -h 参数) 支持 原生支持(无 GUI 依赖)
仿真速度 中(与实时绑定) 快(可超实时)
模型格式 URDF、SDF、VRML SDF、URDF URDF
授权 Edu 免费,Pro 收费 完全开源 完全开源
适合场景 复杂传感器仿真、工业机械臂 ROS 集成、移动机器人 快速原型、接触丰富任务

Lua 脚本基础

CoppeliaSim 的内置脚本语言是 Lua,每个仿真对象都可以附加 Lua 子脚本(Child Script)来控制其行为。

脚本类型

脚本类型 运行方式 适用场景
非线程子脚本(Non-threaded) 在主仿真循环中同步调用 绝大多数控制逻辑
线程子脚本(Threaded) 在独立线程中运行 需要 sleep 或阻塞等待的场景
主脚本(Main Script) 管理整个仿真生命周期 高级用户定制仿真循环

常用 Lua API 函数

-- 对象操作
handle = sim.getObject('/robot')                          -- 获取对象句柄
pos = sim.getObjectPosition(handle, sim.handle_world)    -- 获取位置 [x, y, z]
sim.setObjectPosition(handle, pos, sim.handle_world)     -- 设置位置

-- 关节操作
angle = sim.getJointPosition(joint_handle)               -- 读取关节角度(弧度)
sim.setJointTargetPosition(joint_handle, math.pi / 2)   -- 设置目标位置
sim.setJointTargetVelocity(joint_handle, 1.0)           -- 设置目标速度(rad/s)

-- 传感器读取
result, dist = sim.readProximitySensor(sensor_handle)   -- 读取接近传感器
result, img, resX, resY = sim.getVisionSensorImg(cam_handle)  -- 读取相机图像

-- 仿真时间
t = sim.getSimulationTime()                              -- 获取当前仿真时间(秒)

简单差速移动机器人 Lua 控制示例

-- 非线程子脚本:附加在移动机器人对象上
function sysCall_init()
    -- 获取左右轮电机句柄
    left_motor  = sim.getObject('./LeftMotor')
    right_motor = sim.getObject('./RightMotor')

    -- 获取接近传感器句柄(用于障碍物检测)
    front_sensor = sim.getObject('./FrontSensor')

    -- 初始速度设置(rad/s)
    max_speed = 3.0
    start_time = sim.getSimulationTime()
end

function sysCall_actuation()
    -- 读取前方接近传感器
    local detected, dist = sim.readProximitySensor(front_sensor)

    if detected and dist < 0.5 then
        -- 检测到障碍物,原地左转
        sim.setJointTargetVelocity(left_motor,  -max_speed)
        sim.setJointTargetVelocity(right_motor,  max_speed)
    else
        -- 直行
        sim.setJointTargetVelocity(left_motor,  max_speed)
        sim.setJointTargetVelocity(right_motor, max_speed)
    end
end

function sysCall_sensing()
    -- 此回调在每个仿真步的感知阶段执行
    -- 可在此记录传感器数据用于后续分析
end

function sysCall_cleanup()
    -- 仿真结束时的清理工作
    sim.setJointTargetVelocity(left_motor,  0)
    sim.setJointTargetVelocity(right_motor, 0)
end

性能优化

在进行大规模强化学习训练或长时间仿真时,CoppeliaSim 的性能优化至关重要。

减少碰撞网格的多边形数量

物理引擎进行碰撞检测时使用专用的简化网格(Collision Mesh),而非渲染网格(Visual Mesh)。应将碰撞网格的多边形数量控制在最小必要范围:

  • 在场景编辑器中,为每个对象的碰撞形状选择"凸包(Convex Hull)"或"包围盒(Bounding Box)"而非精确网格
  • 对于机器人连杆,通常使用圆柱体或长方体近似碰撞形状即可满足仿真需求

物理引擎选择

物理引擎 速度 精度 适用场景
ODE 移动机器人、一般场景(默认推荐)
Bullet 柔体、软体仿真
Newton 需要高精度动力学的场景
Vortex 最高 工业级精密仿真(需授权)
-- 在 Lua 脚本中切换物理引擎
sim.setInt32Parameter(sim.intparam_dynamic_engine, sim.physics_ode)
-- 可选值: sim.physics_ode / sim.physics_bullet / sim.physics_newton / sim.physics_vortex

无头训练时关闭渲染

在强化学习训练中,渲染是主要的性能瓶颈之一。在无头模式下,默认渲染会被跳过,但若使用视觉传感器仿真相机观测,仍会触发渲染。可按需降低视觉传感器的分辨率:

-- 在 Lua 脚本中降低视觉传感器分辨率
local cam_handle = sim.getObject('./Camera')
sim.setVisionSensorResolution(cam_handle, 64, 64)  -- 训练时使用低分辨率

加速仿真(实时系数)

CoppeliaSim 默认以实时速度运行,可以在场景设置中调整仿真时间步长和实时系数:

-- 设置仿真时间步长(秒)
sim.setFloatParameter(sim.floatparam_simulation_time_step, 0.05)

-- 在步进模式下,仿真速度不受实时限制,完全由 Python 的 client.step() 调用速率决定

参考资料