MotrixSim 仓库分析与 go2.py 详解 🔍 仓库分析 特征分析 特征 分析 核心引擎代码 ❌ 未公开。motrixsim 核心通过 pip install motrixsim 从 PyPI 安装 Python 绑定 ❌ 未公开。底层使用 Rust 实现,只暴露 Python API 示例代码 ✅ 完整。包含 39+ 个 Python 示例文件 API 文档 ✅ 完整。可以从 https://motrixsim.readthedocs.io 访问 资源文件 ✅ 完整。包含机器人模型、场景、策略网络等
仓库结构 1 2 3 4 5 6 7 8 9 10 11 12 13 motrixsim-docs/ ├── docs/ # 文档源码 │ └── source/ # 包含 API 参考、教程等 ├── examples/ # ⭐ 示例代码 (39+ 个 Python 文件) │ ├── go1.py # 四足机器人 Go1 示例 │ ├── go2.py # 四足机器人 Go2 示例 │ ├── g1_keyboard_control.py # 人形机器人 G1 键盘控制 │ ├── robotic_arm.py # 机械臂示例 │ └── assets/ # 模型和资源文件 ├── legged_gym/ # 腿式机器人强化学习环境 ├── pyproject.toml # 依赖配置 ├── README.md # 项目说明 └── LICENSE # Apache 2.0 许可证
核心物理引擎代码未公开 motrixsim 的核心实现(物理求解器、碰撞检测、渲染引擎等)是闭源的。 这个仓库只提供:使用说明、API 示例、机器人模型文件。
📖 go2.py 详细解读 GitHub : examples/go2.py
功能概述 这个脚本演示了一个四足机器人 Go2 在物理仿真环境中自主行走的完整流程:
flowchart LR
A[加载场景模型] --> B[创建物理数据]
B --> C[加载神经网络策略]
C --> D[仿真循环]
D --> E{机器人摔倒?}
E -->|是| F[重置场景]
E -->|否| G[收集观测数据]
F --> D
G --> H[神经网络推理]
H --> I[应用动作到关节]
I --> J[物理引擎步进]
J --> K[渲染显示]
K --> D 逐段代码解析 1️⃣ 导入模块 1 2 3 4 5 6 7 8 9 10 import timeimport randomfrom collections import dequeimport numpy as npimport onnxruntime as ortfrom scipy.spatial.transform import Rotationfrom motrixsim import SceneData, SceneModel, load_model, stepfrom motrixsim.render import CaptureTask, RenderApp
模块 用途 time用于帧率控制 (time.sleep) random生成随机运动目标 deque双端队列,用于管理截图任务 numpy数值计算(向量、矩阵操作) onnxruntime运行神经网络模型(ONNX 格式) scipy.spatial.transform.Rotation四元数与旋转矩阵的转换 motrixsim.SceneData存储仿真的动态状态(位置、速度、力) motrixsim.SceneModel场景的静态描述(几何、物理参数) motrixsim.load_model从 MJCF/XML 文件加载场景 motrixsim.step执行一次物理仿真步进 motrixsim.render.RenderApp可视化渲染窗口 motrixsim.render.CaptureTask异步截图任务
2️⃣ 全局参数定义 1 2 3 4 default_joint_pos = np.array([0.1 , 0.9 , -1.8 , -0.1 , 0.9 , -1.8 , 0.1 , 0.9 , -1.8 , -0.1 , 0.9 , -1.8 ]) action_scale = 0.5 lin_vel_scale = 1.0 ang_vel_scale = 1.5
参数 说明 default_joint_pos机器人 12 个关节 的默认角度(弧度)。Go2 有 4 条腿,每条腿 3 个关节(髋/大腿/小腿) action_scale神经网络输出的动作缩放因子(0.5 表示输出值减半) lin_vel_scale线速度目标缩放因子(Go2 是 1.0,比 Go1 的 0.7 更高) ang_vel_scale角速度目标缩放因子
Go2 躯干名称:"base",Go1 是 "trunk" Go2 第一个执行器:"FL_hip",Go1 是 "FR_hip" Go2 线速度缩放 1.0,Go1 是 0.7 关节顺序 :[FL_hip, FL_thigh, FL_calf, FR_hip, FR_thigh, FR_calf, RL_hip, RL_thigh, RL_calf, RR_hip, RR_thigh, RR_calf] - FL = Front Left(前左腿) - FR = Front Right(前右腿) - RL = Rear Left(后左腿) - RR = Rear Right(后右腿)
3️⃣ 观测数据收集函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def compute_observations (last_actions, target_action, model: SceneModel, data: SceneData ): body_index = model.get_body_index("base" ) body = model.get_body(body_index) linear_vel = model.get_sensor_value("local_linvel" , data) gyro = model.get_sensor_value("gyro" , data) pose = body.get_pose(data) inv_rotation = Rotation.from_quat(pose[3 :7 ]).inv() gravity = inv_rotation.apply(np.array([0.0 , 0.0 , -1.0 ])) dof_pos = body.get_joint_dof_pos(data) dof_vel = body.get_joint_dof_vel(data) obs = np.hstack([ linear_vel, gyro, gravity, dof_pos - default_joint_pos, dof_vel, last_actions, target_action ]) return obs
这是神经网络的"眼睛" - 收集机器人当前状态作为输入。
观测量 维度 说明 线速度 3 机器人身体在本地坐标系的速度 (x, y, z) 角速度 3 机器人绕三个轴的旋转速度(陀螺仪读数) 重力方向 3 重力在机器人本体坐标系中的方向,反映倾斜程度 关节位置差 12 当前关节角度与默认姿态的偏差 关节速度 12 各关节的角速度 上一帧动作 12 上一步施加的控制命令 目标速度 3 期望的线速度 (x, y) 和角速度 (yaw) 总计 48 神经网络输入维度
这让神经网络了解控制的连续性,避免输出剧烈抖动的动作。这是强化学习中常用的技巧。
使用 np.hstack() 一次性拼接数组,比 Go1 的 obs.extend() 方式更高效。
4️⃣ 目标更新函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def update_target (goback, body_position: np.ndarray ): target_action = [0 , 0 ] if goback: v = -body_position[:2 ] norm = v / np.linalg.norm(v) target_action = [norm[0 ] * lin_vel_scale, norm[1 ] * , 0 ] else : x = random.random() * 4.0 - 2.0 y = random.random() * 4.0 - 2.0 rot = random.random() * 4.0 - 2.0 v = np.array([x, y]) norm = v / np.linalg.norm(v) target_action = [norm[0 ] * lin_vel_scale, norm[1 ] * lin_vel_scale, rot * ang_vel_scale] return target_action
这个函数生成机器人的运动目标 :
随机漫游模式 (goback=False):生成随机方向和转向速度返回原点模式 (goback=True):计算指向原点的方向,直线返回返回的 target_action 包含 3 个值:[vx, vy, yaw_rate](前后速度、左右速度、转向速度)
5️⃣ 动作应用函数 1 2 3 4 5 6 7 def apply_actions (actions, model: SceneModel, data: SceneData ): start_actuator_index = model.get_actuator_index("FL_hip" ) for index, act in enumerate (actions): actuator_index = start_actuator_index + index ctrl = act * action_scale + default_joint_pos[index] actuator = model.get_actuator(actuator_index) actuator.set_ctrl(data, ctrl)
这是神经网络的"手" - 将决策转化为关节控制。
控制流程 :
神经网络输出 12 个动作值(范围约 -1 到 1) 每个动作乘以 action_scale(0.5)进行缩放 加上默认关节角度 default_joint_pos,得到目标关节角度 通过 actuator.set_ctrl() 设置关节电机的目标位置 计算示例 :
1 2 3 神经网络输出: act = 0.5 缩放后: 0.5 * 0.5 = 0.25 加上默认值: 0.25 + 0.1 = 0.35 (弧度)
6️⃣ 摔倒检测函数 1 2 3 4 5 6 7 def is_fall (model: SceneModel, data: SceneData ): pose = model.get_link("base" ).get_pose(data) rotation = Rotation.from_quat(pose[3 :7 ]) rotated_z_axis = rotation.apply(np.array([0.0 , 0.0 , 1.0 ])) thr = 0.3 dot = np.dot(rotated_z_axis, np.array([0.0 , 0.0 , 1.0 ])) return dot < thr
判断机器人是否摔倒的逻辑:
获取机器人躯干(base)的姿态四元数 [x, y, z, w] 将四元数转换为旋转对象 计算机器人的"上方向"(本地 Z 轴)在世界坐标系中的朝向 与世界坐标系的 Z 轴(垂直向上)做点积 如果点积 < 0.3,说明机器人已经严重倾斜/翻倒 点积值 含义 1.0 完全直立 0.7 倾斜约 45° 0.3 倾斜约 72°(触发重置) 0.0 完全侧翻 90° -1.0 完全翻转(背朝下)
7️⃣ 主函数详解 初始化渲染和加载模型 1 2 3 4 5 6 7 8 def main (): with RenderApp() as render: render.opt.set_left_panel_vis(True ) path = "examples/assets/go2/scene_flat.xml" model = load_model(path)
scene_flat.xml 包含: - Go2 机器人模型(引用 go2_mjx.xml) - 平坦地面 - 光照设置 - 物理参数
相机配置 1 2 3 4 5 6 7 8 9 10 11 12 cameras = model.cameras cameras[0 ].set_render_target("image" , 320 , 240 ) cameras[1 ].set_render_target("image" , 640 , 480 ) cameras[1 ].depth_only = True cameras[1 ].set_near_far(0.1 , 1 ) preview_cameras = [None , *cameras[2 :]] preview_camera_idx = 0
创建物理数据和加载神经网络 1 2 3 4 5 6 7 8 9 10 render.launch(model) data = SceneData(model) session = ort.InferenceSession("examples/assets/go2/go2_policy.onnx" , providers=["CPUExecutionProvider" ]) input_name = session.get_inputs()[0 ].name output_name = session.get_outputs()[0 ].name
神经网络规格: - 输入:48 维浮点数向量 - 输出:12 维浮点数向量(12 个关节的控制命令)
控制变量初始化 1 2 3 4 5 6 7 8 9 last_actions = [0 ] * 12 n_infer_interval = 5 n_set_tartget_interval = 750 go_back = False nsteps = 0 target_action = [0.5 , 0 , 0 ] capture_tasks = deque() capture_index = 0
Go2 的 n_infer_interval = 5,比 Go1 的 10 更频繁,意味着控制频率更高。
主仿真循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 while True : for _ in range (4 ): step(model, data) if is_fall(model, data): data = SceneData(model) nsteps += 1 if nsteps % n_infer_interval == 0 : if nsteps % n_set_tartget_interval == 0 : body_pose = model.get_body(model.get_body_index("base" )).get_pose(data) target_action = update_target(go_back, body_pose[:3 ]) go_back = not go_back obs = compute_observations(last_actions, target_action, model, data) input_data = np.array(obs).reshape(1 , -1 ).astype(np.float32) outputs = session.run([output_name], {input_name: input_data}) actions = outputs[0 ][0 ] apply_actions(actions, model, data) last_actions = actions
时序分析 : - 每帧执行 4 次物理步进(提高仿真精度) - 每 5 个物理步进执行一次神经网络推理(控制频率 = 仿真频率 / 5) - 每 750 步(约几秒钟)切换一次运动目标
截图功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 if render.input .is_key_just_pressed("space" ): rcam = render.get_camera(0 ) capture_tasks.append((capture_index, rcam.capture())) capture_index += 1 render.sync(data) time.sleep(1 /60. ) while len (capture_tasks) > 0 : idx, task = capture_tasks[0 ] if task.state != "pending" : capture_tasks.popleft() img = task.take_image() os.makedirs("shot" , exist_ok=True ) img.save_to_disk(f"shot/capture_{idx} .png" ) else : break
相机切换 1 2 3 4 5 6 7 8 9 if render.input .is_key_just_pressed("right" ): preview_camera_idx = (preview_camera_idx + 1 ) % len (preview_cameras) render.set_main_camera(preview_cameras[preview_camera_idx]) if render.input .is_key_just_pressed("left" ): preview_camera_idx = (preview_camera_idx + len (preview_cameras) - 1 ) % len (preview_cameras) render.set_main_camera(preview_cameras[preview_camera_idx])
📖 go2_keyboard_control.py 详细解读 GitHub : examples/go2_keyboard_control.py
功能概述 通过键盘实时控制 Go2 机器人的移动方向和速度。与 go2.py 的主要区别是使用面向对象设计 和高级 API 。
flowchart TB
subgraph 初始化
A[加载模型] --> B[创建 OnnxController]
B --> C[启动渲染循环]
end
subgraph "渲染循环 (60 FPS)"
D[读取键盘输入] --> E[更新 command 向量]
E --> F[get_control 物理步进]
F --> G[神经网络推理]
G --> H[应用动作]
H --> I[渲染同步]
end
C --> D
I --> D 全局参数 1 2 3 4 default_joint_pos = np.array([0.1 , 0.9 , -1.8 , -0.1 , 0.9 , -1.8 , 0.1 , 0.9 , -1.8 , -0.1 , 0.9 , -1.8 ]) action_scale = 0.5 lin_vel_scale = 2.0 ang_vel_scale = 3.0
键盘控制版本的速度更快,因为手动控制需要更灵敏的响应。
OnnxController 类详解 这是与 go2.py 最大的区别 —— 使用面向对象设计 封装所有控制逻辑:
构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class OnnxController : def __init__ ( self, model: SceneModel, policy_path: str , default_angles: np.ndarray, ctrl_dt: float , action_scale: float = 0.5 , ): self ._model = model self ._data = SceneData(self ._model) self ._policy = ort.InferenceSession(policy_path, providers=["CPUExecutionProvider" ]) self ._input_name = self ._policy.get_inputs()[0 ].name self ._output_name = self ._policy.get_outputs()[0 ].name self .command = np.zeros(3 , dtype=np.float32) self ._action_scale = action_scale self ._default_angles = default_angles.copy() self ._last_action = np.zeros_like(default_angles, dtype=np.float32) self ._counter = 0 self ._n_substeps = int (round (ctrl_dt / self ._model.options.timestep))
属性 说明 command速度指令向量 [前后, 左右, 转向],键盘输入直接修改此属性 _n_substeps每次神经网络推理之间的物理步进次数 _last_action上一帧的动作输出,用于观测
控制频率计算示例 :
1 2 3 4 ctrl_dt = 0.02 秒 (20ms, 即 50Hz) timestep = 0.002 秒 (默认物理步长) n_substeps = 0.02 / 0.002 = 10 即:每 10 次物理步进执行一次神经网络推理
观测收集方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def get_obs (self, model: SceneModel, data: SceneData, command ): body = model.get_body(model.get_body_index("base" )) linear_vel = model.get_sensor_value("local_linvel" , data) gyro = model.get_sensor_value("gyro" , data) pose = body.get_pose(data) inv_rotation = Rotation.from_quat(pose[3 :7 ]).inv() gravity = inv_rotation.apply(np.array([0.0 , 0.0 , -1.0 ])) dof_pos = body.get_joint_dof_pos(data) dof_vel = body.get_joint_dof_vel(data) obs = np.hstack([ linear_vel, gyro, gravity, dof_pos - self ._default_angles, dof_vel, self ._last_action, command ]) return obs.astype(np.float32)
核心控制方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def get_control (self ): self ._counter += 1 step(self ._model, self ._data) if self .is_fall(self ._model, self ._data): self ._data = SceneData(self ._model) self .command = np.zeros(3 , dtype=np.float32) if self ._counter % self ._n_substeps == 0 : obs = self .get_obs(self ._model, self ._data, self .command) outputs = self ._policy.run([self ._output_name], {self ._input_name: obs.reshape(1 , -1 )}) actions = outputs[0 ][0 ] self ._last_action = actions.copy() self .apply_actions(actions, self ._model, self ._data)
这个方法每调用一次执行一个物理步进 ,并在适当的时机执行神经网络推理。
主函数详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def main (): with RenderApp() as render: render.opt.set_left_panel_vis(True ) path = "examples/assets/go2/scene_flat.xml" model = load_model(path) render.launch(model) policy = OnnxController( model, policy_path="examples/assets/go2/go2_policy.onnx" , ctrl_dt=0.02 , default_angles=default_joint_pos, action_scale=action_scale, )
键盘输入处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 input = render.input def render_step (): if input .is_key_pressed("up" ) or input .is_key_pressed("w" ): policy.command[0 ] = 1.0 * lin_vel_scale elif input .is_key_pressed("down" ) or input .is_key_pressed("s" ): policy.command[0 ] = -1.0 * lin_vel_scale else : policy.command[0 ] = 0.0 if input .is_key_pressed("left" ): policy.command[1 ] = 0.5 * lin_vel_scale elif input .is_key_pressed("right" ): policy.command[1 ] = -0.5 * lin_vel_scale else : policy.command[1 ] = 0.0 if input .is_key_pressed("a" ): policy.command[2 ] = 2.0 * ang_vel_scale elif input .is_key_pressed("d" ): policy.command[2 ] = -2.0 * ang_vel_scale else : policy.command[2 ] = 0.0 render.sync(policy.data)
🎮 完整键盘控制映射表 按键 功能 command[0] command[1] command[2] W / ↑前进 2.0 0 0 S / ↓后退 -2.0 0 0 ←左平移 0 1.0 0 →右平移 0 -1.0 0 A左转 0 0 6.0 D右转 0 0 -6.0
render_loop 高级 API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 print ("Keyboard Controls:" )print ("- Press W / Up Arrow to move forward" )print ("- Press S / Down Arrow to move backward" )print ("- Press Left Arrow to move left" )print ("- Press Right Arrow to move right" ) print ("- Press A to rotate left" )print ("- Press D to rotate right" )run.render_loop( model.options.timestep, 60 , policy.get_control, render_step )
run.render_loop 的工作原理 :
根据目标帧率计算每帧需要执行多少次物理步进 每个物理步进调用 policy.get_control() 每帧结束调用 render_step() 处理输入和渲染 自动处理时间同步,保持稳定帧率 这比手动写 while True 循环更简洁、更可靠。
🔄 go2.py vs go2_keyboard_control.py 完整对比 特性 go2.py go2_keyboard_control.py 控制模式 自动随机漫游 键盘手动控制 代码风格 函数式(独立函数) 面向对象 (OnnxController 类) lin_vel_scale 1.0 2.0 (更快) ang_vel_scale 1.5 3.0 (更快) n_infer_interval 5 步 由 ctrl_dt 计算 主循环 手动 while True run.render_loop() 高级 API帧率控制 time.sleep(1/60)render_loop 自动管理 相机截图 ✅ 支持 ❌ 无 相机切换 ✅ 支持 ❌ 无 代码行数 ~207 行 ~178 行
📁 相关文件 🧠 神经网络策略说明 1 2 3 4 5 6 7 8 9 10 11 12 go2_policy.onnx ├── 输入: 48 维向量 │ ├── 线速度 (3) │ ├── 角速度 (3) │ ├── 重力方向 (3) │ ├── 关节位置差 (12) │ ├── 关节速度 (12) │ ├── 上一帧动作 (12) │ └── 目标速度 (3) │ └── 输出: 12 维向量 └── 12 个关节的控制命令
这个神经网络是通过强化学习 (Reinforcement Learning) 训练得到的,让机器人学会: - 保持平衡 - 按照给定速度行走 - 适应不同地形