SimToReal 概述
AKA-Sim2Real 是一个基于前视视角的自动驾驶模拟到真实(Sim2Real)系统,旨在通过模拟器采集人类驾驶数据,训练 ACT(Action Chunking Transformer)模型,并将训练好的策略迁移到真实小车上运行。
系统架构
┌─────────────────────────────────────────────────────────┐
│ AKA-Sim2Real │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ 模拟器/真车 │───▶│ 数据采集模块 │───▶│ 数据集 │ │
│ │ (Sim/Real) │ │ Episode API │ │ output/ │ │
│ └─────────────┘ └──────────────┘ │ dataset/ │ │
│ │ └────┬─────┘ │
│ │ │ │
│ │ ┌──────────────┐ ┌────▼─────┐ │
│ │ │ ACT 模型推理 │◀───│ 模型训练 │ │
│ └──────────▶│ Inference │ │Training │ │
│ │ Runtime │ └──────────┘ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
系统由三个核心部分组成:
| 模块 | 说明 |
|---|---|
| 模拟器 / 真车接口 | 提供前视视角仿真环境,支持键盘手动控制与自动推理两种模式 |
| 数据采集 | 记录图像帧 + 车辆状态 + 动作,导出为结构化数据集 |
| ACT 训练与推理 | 基于 Action Chunking Transformer 进行模仿学习,支持 CVAE 与时序集成 |
技术栈
| 层级 | 技术 |
|---|---|
| 后端框架 | FastAPI + Socket.IO (Python) |
| 深度学习 | PyTorch + ResNet18 + Transformer |
| 前端 | React + TypeScript + Socket.IO Client |
文档目录
SimToReal 概述
AKA-Sim2Real 是一个基于前视视角的自动驾驶模拟到真实(Sim2Real)系统,旨在通过模拟器采集人类驾驶数据,训练 ACT(Action Chunking Transformer)模型,并将训练好的策略迁移到真实小车上运行。
系统架构
┌─────────────────────────────────────────────────────────┐
│ AKA-Sim2Real │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ 模拟器/真车 │───▶│ 数据采集模块 │───▶│ 数据集 │ │
│ │ (Sim/Real) │ │ Episode API │ │ output/ │ │
│ └─────────────┘ └──────────────┘ │ dataset/ │ │
│ │ └────┬─────┘ │
│ │ │ │
│ │ ┌──────────────┐ ┌────▼─────┐ │
│ │ │ ACT 模型推理 │◀───│ 模型训练 │ │
│ └──────────▶│ Inference │ │Training │ │
│ │ Runtime │ └──────────┘ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
系统由三个核心部分组成:
| 模块 | 说明 |
|---|---|
| 模拟器 / 真车接口 | 提供前视视角仿真环境,支持键盘手动控制与自动推理两种模式 |
| 数据采集 | 记录图像帧 + 车辆状态 + 动作,导出为结构化数据集 |
| ACT 训练与推理 | 基于 Action Chunking Transformer 进行模仿学习,支持 CVAE 与时序集成 |
技术栈
| 层级 | 技术 |
|---|---|
| 后端框架 | FastAPI + Socket.IO (Python) |
| 深度学习 | PyTorch + ResNet18 + Transformer |
| 前端 | React + TypeScript + Socket.IO Client |
文档目录
快速开始
本节介绍如何在本地搭建并运行 AKA-Sim2Real 系统。
环境要求
| 依赖 | 版本建议 |
|---|---|
| Python | 3.10+ |
| Node.js | 18+ |
| PyTorch | 2.0+(支持 CUDA/MPS/CPU) |
1. 克隆仓库
git clone <仓库地址>
cd AKA-Sim2Real
2. 安装后端依赖
cd backend
pip install -r requirements.txt
提示:推荐使用 conda 或 venv 创建独立 Python 环境,避免依赖冲突。
3. 安装前端依赖
cd ui
npm install
4. 启动后端服务
cd backend
python main.py
后端将运行在 http://localhost:8000
启动成功后,终端会输出类似以下信息:
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
注意:后端启动时会尝试自动加载
output/train/model.pt,如果模型文件不存在,则以无模型状态运行(推理功能不可用,但数据采集和训练功能正常)。
5. 启动前端服务
新开一个终端窗口:
cd ui
npm run dev
前端将运行在 http://localhost:5173
6. 访问界面
在浏览器中打开 http://localhost:5173,可以看到:
- Sim 页面:模拟器视角,用于数据采集与模拟推理
- Real 页面:真实小车接口视图
目录结构说明
AKA-Sim2Real/
├── backend/ # Python 后端(FastAPI + Socket.IO)
│ ├── main.py # 服务入口
│ ├── api/ # REST API 路由
│ ├── sio_handlers/ # Socket.IO 事件处理
│ └── services/ # 核心业务逻辑(ACT、训练、采集)
├── ui/ # React 前端
├── policies/ # ACT 模型定义与训练脚本
├── output/ # 运行时输出目录
│ ├── dataset/ # 采集的数据集
│ └── train/ # 训练输出(model.pt)
└── tests/act/ # ACT 最小回归测试
验证安装
运行 ACT 单元测试,验证核心链路正常:
python3 backend/run_act_checks.py
全部通过则说明 ACT 主链路(模型定义、推理、数据导出)运行正常。
下一步
数据采集
数据采集是 Sim2Real 流程的第一步。通过在模拟器中手动驾驶小车,系统会同步记录摄像头图像、车辆状态和控制动作,最终导出为结构化数据集供 ACT 模型训练使用。
采集的数据格式
每个时间步采集以下数据:
| 字段 | 类型 | 说明 |
|---|---|---|
image | RGB 图像帧 | 模拟器前视摄像头画面 |
state | [vel_left, vel_right] | 左右轮速度(当前车辆状态) |
action | [vel_left, vel_right] | 左右轮控制指令 |
状态维度和动作维度均为 2,分别对应左轮速度和右轮速度。
数据采集步骤
1. 启动系统
确保后端和前端均已启动(参见快速开始),在浏览器中打开 Sim 页面。
2. 控制小车
使用键盘控制小车行驶:
| 按键 | 动作 |
|---|---|
W / ↑ | 前进 |
S / ↓ | 后退 |
A / ← | 左转 |
D / → | 右转 |
3. 开始录制
点击界面上的 "开始采集" 按钮,系统进入录制模式,此时每个控制帧的图像、状态和动作均会被记录。
4. 停止录制
完成一段演示后,点击 "停止采集" 按钮。数据会自动导出到:
output/dataset/
数据集结构
导出后的数据集组织结构如下:
output/dataset/
├── episode_0/
│ ├── images/ # 每帧图像(PNG 格式)
│ ├── states.npy # 状态序列 [N, 2]
│ └── actions.npy # 动作序列 [N, 2]
├── episode_1/
│ └── ...
└── stats.json # 数据集统计信息(均值、标准差)
stats.json 保存了状态和动作的归一化统计量,推理时也需要用到:
{
"state_mean": [0.0, 0.0],
"state_std": [1.0, 1.0],
"action_mean": [0.0, 0.0],
"action_std": [1.0, 1.0]
}
采集建议
- 数量:建议至少采集 100 个样本(帧)才能开始训练,样本越多模型越稳定
- 多样性:涵盖直行、左转、右转等多种驾驶场景,提升模型泛化能力
- 一致性:每段演示应保持合理的驾驶行为,避免急停急转等极端操作
REST API(高级用法)
数据采集功能也通过 REST API 暴露,可用于自动化脚本:
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /api/episode/start | 开始采集 |
POST | /api/episode/stop | 停止采集并导出 |
GET | /api/episode/list | 列出所有 episode |
DELETE | /api/episode/{id} | 删除指定 episode |
下一步
数据采集完成后,继续进行 模型训练。
模型训练
采集到足够的演示数据后,即可训练 ACT(Action Chunking Transformer)模型。训练过程由后端的训练编排器(Training Orchestrator)自动完成,无需手动干预。
训练前提
- 已完成数据采集,数据保存在
output/dataset/ - 数据集中至少有 100 个样本
启动训练
在前端界面点击 "开始训练" 按钮,或通过 REST API 触发:
curl -X POST http://localhost:8000/api/training/start
训练流程详解
训练器(backend/services/training/orchestrator.py)执行以下步骤:
数据集加载
│
▼
构建 ACT 配置
│
▼
初始化模型(ACTModel)
│
▼
训练循环(L1 Loss + KL Loss)
│
▼
保存模型检查点
output/train/model.pt(含 CVAE 潜变量统计)
损失函数
ACT 使用两个损失项联合训练:
| 损失 | 公式 | 说明 |
|---|---|---|
| 重建损失 | L1(predicted_actions, gt_actions) | 动作预测的主要监督信号 |
| KL 散度 | KL(q(z|obs,action) ∥ p(z|obs)) | CVAE 正则项,权重为 kl_weight=0.1 |
总损失:Loss = L1_loss + kl_weight × KL_loss
默认超参数
| 参数 | 默认值 | 说明 |
|---|---|---|
state_dim | 2 | 状态维度(左右轮速) |
action_dim | 2 | 动作维度(左右轮指令) |
action_chunk_size | 8 | 每次预测的动作步数 |
hidden_dim | 512 | Transformer 隐层维度 |
num_attention_heads | 8 | 多头注意力头数 |
num_encoder_layers | 4 | Transformer 编码器层数 |
num_decoder_layers | 4 | Transformer 解码器层数 |
dim_feedforward | 3200 | 前馈网络中间维度 |
kl_weight | 0.1 | KL 散度损失权重 |
latent_dim | 32 | CVAE 潜变量维度 |
use_cvae | True | 是否启用 CVAE |
训练输出
训练完成后,模型保存在:
output/train/
├── model.pt # 完整模型检查点(含 CVAE 潜变量统计)
└── final_model.pt # 最终模型(如启用了 CVAE 则包含 latent stats)
检查点内容:
{
"model_state_dict": ..., # 模型权重
"config": { ... }, # ACT 配置字典
"latent_mean": ..., # CVAE 潜变量均值(用于推理时采样)
"latent_std": ..., # CVAE 潜变量标准差
}
查看训练状态
通过 REST API 查询训练进度:
# 获取训练状态
curl http://localhost:8000/api/training/status
# 停止训练
curl -X POST http://localhost:8000/api/training/stop
单独运行训练脚本(高级)
如需在命令行直接运行训练(不通过 Web 界面):
python policies/models/act/train_act.py \
--dataset_dir output/dataset \
--output_dir output/train
下一步
训练完成后,继续进行 模型推理,让小车自主行驶。
模型推理
模型推理阶段,系统使用训练好的 ACT 模型替代人工操控,实现小车自主驾驶。系统同时支持**模拟器(Sim)和真实小车(Real)**两种模式。
推理前提
- 已完成模型训练,
output/train/model.pt存在 - 后端和前端均已启动
加载模型
方式一:自动加载(推荐)
后端启动时会自动尝试加载 output/train/model.pt,无需手动操作。可通过以下接口确认模型状态:
curl http://localhost:8000/health
返回示例:
{
"model_loaded": true,
"device": "mps"
}
方式二:前端手动加载
在界面上点击 "加载模型" 按钮,系统会加载最新的模型检查点。
方式三:REST API
curl -X POST http://localhost:8000/api/inference/load
启动自动推理
前端操作
点击 "自动推理" 按钮,系统进入自动驾驶模式:
- 摄像头每帧捕获当前图像
- 图像与车辆状态输入 ACT 模型
- 模型输出动作块(action chunk,8 个时间步)
- 取第一个动作执行,循环往复
Socket.IO 事件
推理通过 Socket.IO 实时传输控制指令:
| 事件 | 方向 | 说明 |
|---|---|---|
start_inference | 客户端 → 服务端 | 启动自动推理 |
stop_inference | 客户端 → 服务端 | 停止自动推理 |
inference_action | 服务端 → 客户端 | 下发控制动作 |
推理流程详解
摄像头图像 ──┐
├──▶ ACTInferenceRuntime.infer()
车辆状态 ──┘ │
├── 图像预处理(ResNet18 归一化)
├── 状态归一化
├── Transformer 编码解码
├── 输出 action chunk [8, 2]
├── (可选)时序集成(Temporal Ensembling)
└── 动作反归一化 → 发送给小车
图像预处理:图像统一缩放并按 ImageNet 均值/方差归一化后送入 ResNet18 视觉编码器。
状态归一化:使用数据集中保存的 state_mean 和 state_std 进行 Z-score 归一化。
动作反归一化:输出的动作使用 action_mean 和 action_std 还原为真实控制量。
时序集成(Temporal Ensembling)
时序集成是 ACT 论文中提出的平滑技术,通过对多个历史 action chunk 的加权融合来减少控制抖动。
启用方式(设置环境变量):
export ACT_TEMPORAL_ENSEMBLING=1
python backend/main.py
衰减权重由 temporal_ensembling_weight 配置参数控制(默认 0.5),值越小融合越平滑。
注意:当前默认关闭时序集成,适合大多数场景。如控制输出存在抖动,可尝试开启。
Sim 与 Real 模式
系统同时支持两个 Socket.IO 命名空间:
| 命名空间 | 用途 |
|---|---|
/sim | 模拟器推理,控制虚拟小车 |
/real | 真实小车推理,通过硬件接口控制实体小车 |
两个命名空间使用相同的推理逻辑,仅底层执行器(模拟器控制器 vs 真实硬件驱动)不同。
推理 REST API
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /health | 查询模型加载状态 |
POST | /api/inference/load | 加载模型 |
POST | /api/inference/reset | 重置推理上下文(清空时序集成缓存) |
推理调试
查看后端日志中的推理输出,确认模型正常运行:
INFO: 加载 ACT 模型...
INFO: 状态归一化: mean=[0.0, 0.0], std=[1.0, 1.0]
INFO: 动作归一化: mean=[0.0, 0.0], std=[1.0, 1.0]
INFO: ACT 模型加载完成,使用设备: mps
若模型未加载时触发推理,系统会返回零动作 [0.0, 0.0] 并打印警告日志,而非抛出异常,保证系统鲁棒性。
ACT 模型架构
ACT(Action Chunking Transformer)是一种基于 Transformer 的模仿学习策略,通过预测**动作块(action chunk)**而非单步动作来实现平滑、稳定的控制输出。
整体架构图
┌──────────────────────────────────────┐
│ ACTModel │
│ │
图像输入 │ ┌──────────────┐ │
(H×W×3) ─────────▶│ │ RGBEncoder │──▶ 视觉特征 (D) │
│ │ (ResNet18) │ │
│ └──────────────┘ │ │
│ │ │
状态输入 │ ┌──────────────┐ │ │
[vel_l, vel_r] ──▶│ │ StateEncoder │──▶ 状态特征 (D) │
│ │ (MLP) │ │ │
│ └──────────────┘ │ │
│ ▼ │
┌──────────┐ │ ┌────────────────┐ │
│ CVAE │──────▶│ │ Transformer │ │
│ (训练时) │ z │ │ Encoder-Decoder│ │
└──────────┘ │ └────────┬───────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ 动作预测头 │ │
│ │ (Linear) │ │
│ └────────────────┘ │
│ │ │
└────────────────────────┼─────────────┘
│
▼
action_chunk [chunk_size, action_dim]
(默认 8 步 × 2 维)
核心组件
1. RGBEncoder(视觉编码器)
- 骨干网络:ResNet18(ImageNet 预训练)
- 输入:归一化后的 RGB 图像
- 输出:展平的视觉特征向量,投影到
hidden_dim(默认 512) - 图像预处理:resize → ImageNet 均值/方差归一化
2. StateEncoder(状态编码器)
- 结构:简单 MLP(多层感知机)
- 输入:归一化后的车辆状态
[vel_left, vel_right](维度 2) - 输出:状态特征向量(维度
hidden_dim)
3. CVAE(条件变分自编码器)
CVAE 在训练阶段引入潜变量 z,为动作预测提供多模态表达能力。
训练时:
encoder: (观测, 动作序列) → (μ, σ) → z ~ N(μ, σ²)
推理时:
z ~ N(latent_mean, latent_std) # 从训练分布中采样
或 z = 0 # 使用零向量(确定性推理)
KL 散度损失约束潜变量分布接近标准正态分布:
KL Loss = KL(N(μ, σ²) ∥ N(0, I))
4. Transformer Encoder-Decoder
| 参数 | 默认值 |
|---|---|
hidden_dim | 512 |
num_attention_heads | 8 |
num_encoder_layers | 4 |
num_decoder_layers | 4 |
dim_feedforward | 3200 |
- 编码器:将视觉特征 + 状态特征 + CVAE 潜变量拼接,经多层自注意力编码为上下文表示
- 解码器:以可学习的动作查询(action queries)为输入,通过交叉注意力从编码器上下文中提取信息,输出
action_chunk_size个动作向量
5. 动作预测头
线性层将 Transformer 解码器的输出投影到动作空间(维度 action_dim=2),输出归一化后的动作块。
动作块(Action Chunking)
ACT 的核心创新之一是不预测单步动作,而是一次性预测 action_chunk_size 步的动作序列(默认 8 步)。
优势:
- 缓解复合误差(compound error)
- 提升长序列动作的时间一致性
- 减少控制器与环境之间的高频交互需求
执行策略:推理时每次只执行 action chunk 的第一步动作(或使用时序集成),下一帧重新预测。
时序集成(Temporal Ensembling)
时序集成通过加权融合多个历史 action chunk 的预测,进一步平滑控制输出。
当前动作 = α × 当前 chunk[0] + (1-α) × 历史融合动作
权重衰减系数 α(temporal_ensembling_weight,默认 0.5)控制历史信息的保留程度:
- 值越大:跟随最新预测,响应速度快
- 值越小:保留更多历史,控制更平滑
代码层次结构
policies/models/act/
├── modeling_act.py # ACTModel、RGBEncoder、StateEncoder、CVAE、Transformer 层
├── configuration_act.py # ACTConfig 数据类
├── defaults.py # DEFAULT_ACT_CONFIG 与 build_act_config()
└── train_act.py # 独立训练脚本
backend/services/inference/
├── checkpoint.py # 检查点加载、模型实例化、统计量读取
├── preprocess.py # 图像预处理、状态/动作归一化与反归一化
├── execution.py # TemporalEnsemblingPolicy(时序集成)
└── runtime.py # ACTInferenceRuntime(推理运行时装配)
训练损失汇总
| 损失项 | 权重 | 说明 |
|---|---|---|
| L1 重建损失 | 1.0 | 预测动作块与真实动作的 L1 误差 |
| KL 散度 | 0.1(kl_weight) | CVAE 潜变量正则化 |
总损失 = L1_loss + 0.1 × KL_loss
参考资料
- ACT 原论文:Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware
- ResNet 论文:Deep Residual Learning for Image Recognition
- Transformer 原论文:Attention Is All You Need
UI 文档
本节介绍 AKA-Sim2Real 前端的架构、页面和组件。
页面概览
技术栈
- 框架: React 19 + TypeScript
- 路由: React Router DOM 7
- 状态管理: Zustand
- 样式: Tailwind CSS 4
- HTTP 客户端: Ky
- 实时通信: Socket.IO Client
- 构建工具: Vite 7
目录结构
ui/src/
├── main.tsx # 应用入口
├── App.tsx # 根组件,路由配置
├── index.css # Tailwind 入口
├── api/
│ ├── api.ts # REST API 客户端
│ ├── socket.ts # Socket.IO 客户端工厂
│ └── realCar.ts # 真实小车 HTTP API
├── models/
│ └── types.ts # 共享 TypeScript 类型
├── stores/
│ └── simCarStore.ts # Zustand 状态管理
└── pages/
├── SimPage/ # 模拟器页面
├── RealPage/ # 真实小车页面
└── NotFound.tsx # 404 页面
核心概念
页面详解
SimPage
路由: /
模拟器主页面,用于数据采集与模型推理测试。
布局结构
┌─────────────────────────────────────────────────────────────┐
│ SimPage │
├───────────────────────────────┬─────────────────────────────┤
│ │ RightPanel │
│ TopDownView │ ┌─────────────────────┐ │
│ (俯视画布) │ │ FirstPersonView │ │
│ │ │ (第一视角) │ │
│ [小车站 + 障碍物 + 地图] │ └─────────────────────┘ │
│ │ ┌─────────────────────┐ │
│ │ │ LogConsole │ │
│ │ │ (日志控制台) │ │
├───────────────────────────────┴─────────────────────────────┤
│ TrainingControl + InferenceControl │
└─────────────────────────────────────────────────────────────┘
核心功能
- 键盘控制: WASD/方向键控制小车运动
- 数据采集: 录制驾驶演示数据
- 训练控制: 启动/停止模型训练
- 推理测试: 加载模型进行模拟推理
- 实时日志: 显示后端运行日志
Socket.IO 事件
| 事件名 | 方向 | 说明 |
|---|---|---|
action | emit | 发送动作指令 |
car_state_update | listen | 接收小车状态更新 |
collect_data | emit | 发送采集数据 |
training_progress | listen | 接收训练进度 |
act_infer_result | listen | 接收推理结果 |
RealPage
路由: /real
真实小车控制页面,用于连接和控制物理机器人。
布局结构
┌─────────────────────────────────────────────────────────────┐
│ RealPage │
├─────────────────────────────────┬─────────────────────────────┤
│ │ RealRightPanel │
│ RealCameraView │ ┌─────────────────────┐ │
│ (浏览器摄像头) │ │ Car IP 配置 │ │
│ │ │ 连接状态显示 │ │
│ │ │ 电机状态显示 │ │
├─────────────────────────────────┴─────────────────────────────┤
│ 控制面板 │
│ [连接] [前进] [后退] [左转] [右转] [停止] │
└─────────────────────────────────────────────────────────────┘
核心功能
- 摄像头访问: 使用浏览器 API 获取小车摄像头画面
- IP 配置: 设置小车 IP 地址
- 电机控制: HTTP 请求控制小车电机
- 状态监控: 显示心跳和电机状态
API 通信
真实小车通过 HTTP API 直接通信:
// 发送心跳
carHeartbeat(carIP)
// 获取电机状态
motorStatusAt(carIP)
// 直接控制电机
motorDirect(carIP, leftVel, rightVel)
// 小车整体控制
carControl(carIP, action)
组件详解
SimPage 组件
TopDownView
俯视画布组件,展示小车、障碍物和地图。
文件: pages/SimPage/TopDownView.tsx
功能:
- Canvas 绑定的 2D 地图渲染
- 键盘事件监听(WASD/方向键)
- 小车位置和角度显示
- 障碍物绘制
状态依赖: 监听 simCarStore 获取小车状态
RightPanel
右侧面板容器,包含第一视角视图和日志控制台。
文件: pages/SimPage/RightPanel.tsx
子组件:
- FirstPersonView(第一视角渲染)
- LogConsole(日志显示)
InferenceControl
推理控制面板。
文件: pages/SimPage/InferenceControl.tsx
功能:
- 加载训练好的模型
- 单步推理测试
- 自动推理模式
TrainingControl
训练和数据采集控制面板。
文件: pages/SimPage/TrainingControl.tsx
功能:
- Episode 管理(开始/结束/保存)
- 数据采集开关
- FPS 配置
- 训练启动/停止
LogConsole
实时日志显示组件。
文件: pages/SimPage/LogConsole.tsx
功能:
- Socket.IO
log_message事件监听 - 自动滚动到底部
- 日志级别过滤(可选)
RealPage 组件
RealCameraView
浏览器摄像头访问组件。
文件: pages/RealPage/RealCameraView.tsx
功能:
navigator.mediaDevices.getUserMedia获取视频流- 设备选择下拉框
- 视频预览显示
RealRightPanel
真实小车右侧状态面板。
文件: pages/RealPage/RealRightPanel.tsx
功能:
- 小车 IP 输入
- 连接状态指示
- 电机状态显示
共享组件
actionMapping
键盘按键到动作的映射工具。
文件: pages/SimPage/actionMapping.ts
映射关系:
| 按键 | 动作 |
|---|---|
| W / ↑ | forward |
| S / ↓ | backward |
| A / ← | turn_left |
| D / → | turn_right |
| Q | forward_left |
| E | forward_right |
| Z | backward_left |
| C | backward_right |
| Space | stop |
状态管理
Zustand Store
使用 Zustand 进行轻量级状态管理。
simCarStore
文件: stores/simCarStore.ts
管理模拟器小车状态。
interface CarState {
x: number; // 小车 X 坐标
y: number; // 小车 Y 坐标
angle: number; // 小车角度 (弧度)
vel_left: number; // 左轮速度
vel_right: number; // 右轮速度
}
const initialState: CarState = {
x: 400,
y: 300,
angle: -Math.PI / 2,
vel_left: 0,
vel_right: 0,
};
Actions
| Action | 说明 |
|---|---|
setCarState(state) | 更新小车状态 |
resetCarState() | 重置为初始状态 |
页面级 State
除全局 Store 外,各页面使用 useState 管理本地状态。
SimPage
| State | 类型 | 说明 |
|---|---|---|
isRecording | boolean | 是否正在录制 |
episodeCount | number | Episode 数量 |
trainingStatus | string | 训练状态 |
inferenceResult | object | 推理结果 |
RealPage
| State | 类型 | 说明 |
|---|---|---|
carIP | string | 小车 IP 地址 |
isConnected | boolean | 连接状态 |
cameraDevices | MediaDeviceInfo[] | 摄像头设备列表 |
motorStatus | object | 电机状态 |
API 通信
REST API
使用 Ky HTTP 客户端与后端通信。
基础配置: api/api.ts
const api = ky.create({
prefixUrl: '/api',
headers: { 'Content-Type': 'application/json' },
});
端点列表
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | dataset/collect | 采集训练图片数据 |
| POST | train | 启动模型训练 |
| POST | train/stop | 停止训练 |
| POST | act/load_trained | 加载训练好的模型 |
Socket.IO
实时双向通信,用于模拟器状态同步和日志推送。
Socket 工厂
文件: api/socket.ts
createSocket(namespace: string): Socket
创建命名空间的 Socket 实例:
| 实例 | 命名空间 | 用途 |
|---|---|---|
simSocket | /sim | 模拟器状态同步 |
realSocket | /real | 真实小车控制 |
事件列表
Emit 事件 (前端 → 后端)
| 事件 | 数据格式 | 说明 |
|---|---|---|
action | { action: string } | 发送动作指令 |
reset_car_state | - | 重置小车状态 |
get_car_state | - | 请求当前状态 |
collect_data | { image: string, state: object } | 采集数据 |
start_episode | { episodeId: number } | 开始 Episode |
end_episode | { episodeId: number } | 结束 Episode |
finalize_episode | { episodeId: number } | 保存 Episode |
act_infer | { image: string, state: object } | 推理请求 |
Listen 事件 (后端 → 前端)
| 事件 | 数据格式 | 说明 |
|---|---|---|
connected | - | 连接成功 |
car_state_update | CarState | 小车状态更新 |
collection_count | { count: number } | 采集数量 |
episode_info | EpisodeInfo | Episode 信息 |
training_progress | { epoch: number, loss: number } | 训练进度 |
act_infer_result | { action: string } | 推理结果 |
log_message | { level: string, message: string } | 日志消息 |
真实小车 HTTP API
文件: api/realCar.ts
直接向小车 IP 发送 HTTP 请求。
接口列表
| 函数 | HTTP 方法 | 路径 | 说明 |
|---|---|---|---|
carHeartbeat | GET | /api/heartbeat | 发送心跳 |
motorStatusAt | GET | /api/motor/status | 获取电机状态 |
motorDirect | POST | /api/motor/direct | 直接控制电机 |
carControl | POST | /api/car/control | 小车整体控制 |
carTimeSync | GET | /api/time/sync | 时间同步 |
使用示例
import { carControl, motorDirect } from '@/api/realCar';
// 控制小车前进
await carControl(carIP, 'forward');
// 直接控制电机速度
await motorDirect(carIP, 100, 100);
MuJoCo
MuJoCo(Multi-Joint dynamics with Contact)是一个高效的物理仿真引擎,广泛用于机器人学和强化学习研究。
学习资源
- MuJoCo 官方教程 — 推荐的学习指南
文档
安装指南
本指南旨在帮助不同系统的开发者快速搭建 MuJoCo 虚拟仿真环境,重点针对 Linux (Debian/WSL) 进行了优化。
如果使用强化学习,推荐安装如下pip包
# 提供强化学习(RL)的标准化环境接口
pip install gymnasium
# 录制视频或制作 GIF 动图
pip install imageio
# 处理庞大的矩阵和数组数学运算
pip install numpy
# 深度学习框架
pip install torch
Linux 安装 (Debian/WSL)
- 安装miniconda
打开 Linux 系统终端,依次运行以下命令进行静默安装:
# 1. 创建 miniconda 安装文件夹
mkdir -p ~/miniconda3
# 2. 从官方源下载最新的 Linux 安装包 (大概 140MB,稍微等进度条跑完)
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
# 3. 执行静默安装 (-b 代表后台静默,-u 代表更新,-p 指定绝对路径)
bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
# 4. 安装完后,把刚才下载的安装包删掉,节约空间!(可选)
rm ~/miniconda3/miniconda.sh
# 5. 初始化 conda,让你的终端认识 conda 命令
~/miniconda3/bin/conda init bash
- 激活并初始化 Conda
为了让刚才的配置立刻生效,需要刷新当前终端
source ~/.bashrc
验证成功标志:终端命令行最左边出现 (base) 前缀!
- 接受协议与创建Python环境
重要提示:由于新版 Conda 的合规要求,第一次下载包前需要接受服务条款,否则会触发报错。

请执行以下命令接受 Anaconda 官方服务条款
conda tos accept

创建一个独立的 Python 3.10 环境:
# 创建一个名叫 mujoco_env 的环境,并指定 python 版本为 3.10
conda create -n mujoco_env python=3.10 -y
- 激活环境
# 激活进入专属环境
conda activate mujoco_env
- 安装 MuJoCo
# 安装 MuJoCo 引擎
pip install mujoco
- 代码测试
创建 test_mujoco.py 文件
import mujoco
import mujoco.viewer
model = mujoco.MjModel.from_xml_string("""
<mujoco>
<worldbody>
<body>
<geom type="box" size=".1 .1 .1" rgba="1 0 0 1"/>
</body>
</worldbody>
</mujoco>
""")
data = mujoco.MjData(model)
with mujoco.viewer.launch_passive(model, data) as viewer:
while viewer.is_running():
mujoco.mj_step(model, data)
viewer.sync()
- 通过python运行
python test_mujoco.py
- 若弹出包含红色方块的 3D 软件窗口,则表示安装成功

温馨提示:建议第4步下载速度慢,可配置清华镜像源:
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
Macos 安装
- 安装miniconda
要安装miniconda,请按照官方安装指南
- 创建干净的 Python 环境
conda create -n mujoco python=3.10
conda activate mujoco
- 安装 MuJoCo
pip install mujoco
- 用代码测试
创建 test_mujoco.py 文件
import mujoco
import mujoco.viewer
model = mujoco.MjModel.from_xml_string("""
<mujoco>
<worldbody>
<body>
<geom type="sphere" size="0.1"/>
</body>
</worldbody>
</mujoco>
""")
data = mujoco.MjData(model)
with mujoco.viewer.launch_passive(model, data) as viewer:
while viewer.is_running():
mujoco.mj_step(model, data)
viewer.sync()
- 通过mjpython运行
mjpython test_mujoco.py
- 弹出软件窗口为安装成功

Windows 安装
Windows 用户有两种选择:快速查看或 Python 开发环境(推荐两者都做)
A. 快速查看 (免配置)
- 下载并解压 MuJoCo点击下载MuJoCo 3.5.0 Windows 压缩包
sha256校验:mujoco-3.5.0-windows-x86_64.zip.sha256 877d0dfbceac3de90a874c41e0f20c568d104e8ca19de955c0482e3b63832519
- 解压后双击
bin/simulate.exe

- 双击上图的
simulate.exe后,会同时打开两个窗口
注意:这里的shell窗口不要关闭(最小化即可)

- 鼠标拖拽,
mujoco-3.5.0-windows-x86_64/model/humanoid/humanoid.xml,下的文件到MuJoCo窗口,即可查看效果

B. 开发环境 (推荐使用PyCharm + Miniconda使用)
- 安装环境
安装 Miniconda:前往 官方下载
安装 PyCharm:前往 官方下载
- 创建环境:
在Anaconda Powershell Prompt 或 Anaconda Prompt 中执行以下命令:
conda create -n mujoco_env python=3.10 -y
conda activate mujoco_env
- 安装库:
pip install mujoco
pip install gymnasium
pip install imageio
- 在PyCharm环境下运行代码测试
创建 test_mujoco.py 文件
import mujoco
import mujoco.viewer
model = mujoco.MjModel.from_xml_string("""
<mujoco>
<worldbody>
<body>
<geom type="box" size=".1 .1 .1" rgba="1 1 1 1"/>
</body>
</worldbody>
</mujoco>
""")
data = mujoco.MjData(model)
with mujoco.viewer.launch_passive(model, data) as viewer:
while viewer.is_running():
mujoco.mj_step(model, data)
viewer.sync()
- 在PyCharm环境下运行
python test_mujoco.py,弹出软件窗口为安装成功

No.1 第一个 MuJoCo 仿真
本节通过一个最小示例,帮助你快速上手 MuJoCo XML 建模与 Python 仿真脚本。
文件说明
本节的示例文件位于 mujoco/No_1/ 目录下:
mujoco/No_1/
├── hello.xml # MuJoCo XML 场景描述文件
└── no_1.py # Python 仿真脚本
hello.xml 详解
完整内容
<mujoco>
<!-- 全局仿真选项:重力加速度 -->
<option gravity="0 0 -9.81"/>
<!-- 编译器设置:角度单位使用弧度 -->
<compiler angle="radian"/>
<!-- 视觉渲染设置 -->
<visual>
<headlight ambient="0.1 0.1 0.1"/>
</visual>
<!-- 资产定义:可复用的材质 -->
<asset>
<material name="white" rgba="1 1 1 1"/>
</asset>
<!-- worldbody: 物理世界中的所有物体 -->
<worldbody>
<!-- 场景光源 -->
<light diffuse=".5 .5 .5" pos="0 0 3" dir="0 0 -1"/>
<!-- 地面(红色平面) -->
<geom type="plane" size="1 1 0.5" rgba=".9 0 0 1"/>
<!-- 物体 1:白色盒子,z=1 -->
<body pos="0 0 1" euler="0 0 0">
<joint type="free"/>
<inertial pos="0 0 0" mass="1" diaginertia="0.01 0.01 0.01"/>
<geom type="box" size=".1 .2 .3" material="white"/>
</body>
<!-- 物体 2:青色盒子,z=2,pitch=90°(侧立) -->
<body pos="0 0 2" euler="0 90 0">
<joint type="free"/>
<inertial pos="0 0 0" mass="1" diaginertia="0.01 0.01 0.01"/>
<geom type="box" size=".1 .2 .3" rgba="0 .9 .9 1"/>
</body>
<!-- 物体 3:灰色球体,z=3 -->
<body pos="0 0 3" euler="0 0 0">
<joint type="free"/>
<inertial pos="0 0 0" mass="1" diaginertia="0.01 0.01 0.01"/>
<geom type="sphere" size=".1" rgba=".5 .5 .5 1"/>
</body>
</worldbody>
</mujoco>
元素说明
<option>
全局仿真选项。
gravity="0 0 -9.81":标准重力加速度 9.81 m/s²(向下)
<compiler>
编译器设置。
angle="radian":角度单位使用弧度(默认是角度),这样 euler 值可以直接写数值如90表示 90°
<visual>
渲染视觉设置。
headlight:场景主光源的亮度
<asset>
可复用的资产定义。
material:定义可复用的材质(name="white",rgba 白色),在 geom 中通过material="white"引用
<worldbody>
物理世界的根容器,包含所有物体。
<light>
场景光源。
diffuse:漫反射颜色(灰白色)pos:光源位置(上方 3 米)dir:光照方向(沿 -z,即向下)
<geom> 地面
type="plane":平面size="1 1 0.5":半尺寸(x=1, y=1, z=0.5 → 全尺寸 2×2×1 米)rgba=".9 0 0 1":红色
<body>
刚体,可内嵌关节、惯性、几何体。
| 属性 | 说明 |
|---|---|
pos | 初始位置(世界坐标系) |
euler | 初始欧拉角(roll pitch yaw,弧度) |
三个刚体的配置:
| 物体 | pos | euler | geom 类型 | 颜色 |
|---|---|---|---|---|
| 物体 1 | z=1 | 0 0 0(水平) | box | 白色(material) |
| 物体 2 | z=2 | 0 90 0(侧立) | box | 青色 |
| 物体 3 | z=3 | 0 0 0 | sphere | 灰色 |
<joint>
关节,连接 body 与父级(或世界)。
type="free":自由关节,物体不受任何约束,可在 6 个自由度上自由运动(平移 + 旋转)
<inertial>
刚体的质量分布特性。
| 属性 | 说明 |
|---|---|
pos | 质心在 body 本地坐标系中的位置 |
mass | 质量(kg) |
diaginertia | 惯性张量的三个主对角元素(Ixx, Iyy, Izz) |
提示:如果不手动指定
inertial,MuJoCo 会根据 geom 的形状和默认密度自动计算。
<geom> 物体几何体
type:形状类型,支持box、sphere、cylinder、capsule、plane、ellipsoid等size:半尺寸向量- box:
size="0.1 0.2 0.3"→ 全尺寸 0.2×0.4×0.6 米 - sphere:
size="0.1"→ 半径 0.1 米
- box:
rgba:颜色 + 透明度(RGBA,各通道 0~1)material:引用<asset>中定义的材质(优先于 rgba)
no_1.py 详解
import mujoco
import mujoco.viewer
# 1. 从 XML 文件加载模型
model = mujoco.MjModel.from_xml_path('hello.xml')
# 2. 创建仿真数据容器
data = mujoco.MjData(model)
# 3. 启动交互式查看器
with mujoco.viewer.launch_passive(model, data) as viewer:
# 4. 主循环:每帧推进一次仿真
while viewer.is_running():
mujoco.mj_step(model, data) # 执行一步仿真(默认 dt=0.002s)
viewer.sync() # 同步查看器显示
| 步骤 | 说明 |
|---|---|
MjModel.from_xml_path() | 解析 XML 构建物理模型 |
MjData | 存储仿真运行时数据(位置、速度、力等) |
mj_step() | 推进一个仿真时间步 |
viewer.sync() | 将仿真状态同步到可视化窗口 |
运行方法
在 mujoco/No_1/ 目录下执行:
mjpython no_1.py
运行效果:三个物体同时从不同高度自由下落,落到红色地面上后弹起/静止。
常见错误
1. ValueError: XML Error: Schema violation: unrecognized element
原因:XML 中有拼写错误的标签名,如 <gemo>、<intertial>、<muhoco>。
解决:检查并修正标签拼写,确认是 <geom>、<inertial>、<mujoco>。
2. 物体没有出现或直接穿透地面
原因:inertial 未指定且 geom 没有定义时,MuJoCo 可能使用了零质量。
解决:确保每个 body 下都有 <inertial mass="..."/> 或让 geom 的密度足够大。
3. mjpython: command not found
原因:mujoco 包未正确安装,或 mjpython 不在 PATH 中。
解决:
pip install mujoco
# 或直接用 python 运行
python no_1.py
No.2 交互式仿真与鼠标控制
本节介绍如何使用 GLFW 窗口创建交互式 3D 仿真,包括鼠标视角控制和控制器回调。
文件说明
本节的示例文件位于 mujoco/No_2/temp_mjcpy/ 目录下:
mujoco/No_2/temp_mjcpy/
├── ball.xml # MuJoCo XML 场景描述文件
├── projectile.py # 基础交互式仿真脚本
└── template_mujoco.py # 带详细注释的模板脚本
ball.xml 详解
一个简单的弹球场景:
<mujoco>
<worldbody>
<!-- 场景光源 -->
<light diffuse=".5 .5 .5" pos="0 0 3" dir="0 0 -1"/>
<!-- 红色平面地面 -->
<geom type="plane" size="10 1 0.1" rgba=".9 0 0 1"/>
<!-- 绿色小球,初始位置 z=1 -->
<body pos="0 0 1">
<joint type="free"/>
<geom type="sphere" size=".1" rgba="0 .9 0 1"/>
</body>
</worldbody>
</mujoco>
| 元素 | 说明 |
|---|---|
plane | 平面地面,半尺寸 10×1×0.1 |
sphere | 绿色小球,半径 0.1 |
template_mujoco.py 详解
核心结构
import mujoco as mj
from mujoco.glfw import glfw
import numpy as np
import os
# 1. 模型加载
model = mj.MjModel.from_xml_path(xml_path)
data = mj.MjData(model)
cam = mj.MjvCamera() # 视角相机
opt = mj.MjvOption() # 可视化选项
# 2. GLFW 窗口初始化
glfw.init()
window = glfw.create_window(1200, 900, "Demo", None, None)
glfw.make_context_current(window)
glfw.swap_interval(1)
# 3. 可视化数据结构
scene = mj.MjvScene(model, maxgeom=10000)
context = mj.MjrContext(model, mj.mjtFontScale.mjFONTSCALE_150.value)
# 4. 注册回调函数
glfw.set_key_callback(window, keyboard)
glfw.set_cursor_pos_callback(window, mouse_move)
glfw.set_mouse_button_callback(window, mouse_button)
glfw.set_scroll_callback(window, scroll)
mj.set_mjcb_control(controller)
# 5. 主循环
while not glfw.window_should_close(window):
mj.mj_step(model, data)
# 渲染...
glfw.poll_events()
glfw.terminate()
回调函数
controller
每步仿真前调用的控制回调,可写入外力:
def controller(model, data):
"""控制回调,每 mj_step 前自动调用"""
pass
在 projectile.py 中的示例实现了空气阻力:
def controller(model, data):
vx, vy, vz = data.qvel[0], data.qvel[1], data.qvel[2]
v = np.sqrt(vx**2 + vy**2 + vz**2)
c = 1.0 # 阻力系数
data.qfrc_applied[0] = -c * v * vx
data.qfrc_applied[1] = -c * v * vy
data.qfrc_applied[2] = -c * v * vz
keyboard
键盘事件处理:
def keyboard(window, key, scancode, act, mods):
if act == glfw.PRESS and key == glfw.KEY_BACKSPACE:
mj.mj_resetData(model, data)
mj.mj_forward(model, data)
| 按键 | 动作 |
|---|---|
| Backspace | 重置仿真到初始状态 |
mouse_move
鼠标拖动改变视角:
| 组合 | 动作 |
|---|---|
| 左键拖动 | 旋转视角 |
| 右键拖动 | 移动视角 |
| 中键拖动 | 缩放 |
| Shift + 拖动 | 切换交互模式 |
scroll
滚轮缩放视角。
交互操作
视角控制
| 操作 | 功能 |
|---|---|
| 左键拖动 | 旋转视角(水平/垂直) |
| 右键拖动 | 移动视角 |
| 滚轮 | 缩放 |
| Shift + 拖动 | 切换模式 |
键盘
| 按键 | 功能 |
|---|---|
| Backspace | 重置仿真 |
初始条件设置
# 设置小球初始位置
data.qpos[2] = 0.1
# 设置小球初速度 (vx=2, vy=0, vz=5)
data.qvel[0] = 2.0
data.qvel[2] = 5.0
# 设置相机视角
cam.azimuth = 90.0
cam.distance = 8.0
cam.elevation = -45.0
运行方法
在 mujoco/No_2/temp_mjcpy/ 目录下执行:
python projectile.py
# 或带注释模板
python template_mujoco.py
运行效果:绿色弹球以初速度 (2, 0, 5) 抛出,受重力下落并受空气阻力影响。
与 No.1 的区别
| 特性 | No.1 (Passive) | No.2 (GLFW) |
|---|---|---|
| 窗口 | mujoco.viewer 被动窗口 | GLFW 主动创建窗口 |
| 视角控制 | 受限 | 鼠标自由控制 |
| 控制回调 | 无 | mj.set_mjcb_control |
| 渲染控制 | 自动同步 | 手动调用 mjr_render |
| 帧率控制 | 自动 | 手动控制循环频率 |