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() 调用速率决定