Unitree RL Gym — Legged Gym 项目深度技术解析
本文是对宇树科技(Unitree)开源足式机器人强化学习训练框架 legged_gym 的全面技术拆解,涵盖项目架构、核心算法、数学公式、奖励工程与使用实践。
一、项目概述
1.1 背景与定位
legged_gym 是宇树科技基于 NVIDIA Isaac Gym GPU 并行物理仿真引擎构建的足式机器人强化学习(RL)训练框架。它专为四足与双足机器人的端到端步态控制策略训练而设计,采用 PPO(Proximal Policy Optimization) 算法,在单个 GPU 上可同时并行仿真数千个环境(默认 4096 个),极大地加速了策略收敛。
1.2 核心特性
| 多机器人支持 | GO2(四足12DoF)、G1(双足12DoF)、H1(双足10DoF)、H1_2(双足12DoF) |
| GPU 并行仿真 | 基于 Isaac Gym,4096 环境并行,物理计算完全在 GPU 上执行 |
| 非对称 Actor-Critic | Actor 仅接收本体可观测信息;Critic 接收包含本体线速度的特权观测,提升样本效率 |
| LSTM 循环策略 | G1/H1/H1_2 使用时序策略网络,利用历史观测信息 |
| 域随机化(Domain Randomization) | 摩擦力、基座质量、随机推力扰动,增强策略泛化能力 |
| 观测噪声注入 | 模拟真实传感器噪声,提升部署鲁棒性 |
| 程序化地形生成 | 支持斜坡、楼梯、离散障碍、踏步石、缝隙、坑洞等 7 种地形,支持课程学习 |
二、项目目录结构
legged_gym/
├── __init__.py # 包初始化,定义根目录常量
├── envs/
│ ├── __init__.py # 环境注册(注册所有机器人任务)
│ ├── base/
│ │ ├── base_config.py # 配置基类(递归实例化机制)
│ │ ├── base_task.py # 任务基类(仿真生命周期管理)
│ │ ├── legged_robot.py # 足式机器人核心环境(奖励/观测/控制)
│ │ └── legged_robot_config.py # 足式机器人默认配置(含PPO配置)
│ ├── g1/
│ │ ├── g1_config.py # G1 双足机器人配置
│ │ └── g1_env.py # G1 双足机器人环境(步态相位+足部追踪)
│ ├── go2/
│ │ └── go2_config.py # GO2 四足机器人配置(使用通用环境类)
│ ├── h1/
│ │ ├── h1_config.py # H1 双足机器人配置
│ │ └── h1_env.py # H1 双足机器人环境
│ └── h1_2/
│ ├── h1_2_config.py # H1_2 双足机器人配置
│ └── h1_2_env.py # H1_2 双足机器人环境
├── scripts/
│ ├── train.py # 训练入口脚本
│ └── play.py # 推理/测试入口脚本
└── utils/
├── __init__.py # 工具模块导出
├── helpers.py # 通用辅助函数(配置转换/参数解析/JIT导出)
├── isaacgym_utils.py # Isaac Gym 工具(四元数→欧拉角)
├── logger.py # 训练日志记录器
├── math.py # 数学工具(角度归一化/偏航旋转)
├── task_registry.py # 任务注册表(环境创建/算法创建)
└── terrain.py # 程序化地形生成器
三、核心功能模块解析
3.1 配置系统(base_config.py)
设计理念:声明式配置
传统配置需要为每个类手写 __init__ 方法。BaseConfig 采用递归实例化机制:子类只需以嵌套 class 方式声明参数,__init__ 自动将所有类类型属性转换为实例对象。
class MyCfg(BaseConfig):
class env:
num_envs = 4096
class control:
class stiffness:
joint_a = 10.0
cfg = MyCfg() # 自动得到 cfg.env.num_envs, cfg.control.stiffness.joint_a
核心算法:递归实例化
def init_member_classes(obj):
for key in dir(obj):
if key == "__class__": continue
var = getattr(obj, key)
if inspect.isclass(var):
i_var = var() # 实例化
setattr(obj, key, i_var)
init_member_classes(i_var) # 递归处理嵌套
3.2 任务基类(base_task.py)
BaseTask 封装 Isaac Gym 仿真的核心生命周期:
子类必须实现的抽象接口:
- reset_idx(env_ids):重置指定环境
- step(actions):执行一步仿真并返回 (obs, privileged_obs, reward, done, info)
3.3 足式机器人核心环境(legged_robot.py)
这是项目最核心、代码量最大的文件(约 600 行),包含完整的仿真-训练循环。
3.3.1 仿真步进流程
策略网络输出 actions
↓
[step()] 动作裁剪到 [-clip_actions, clip_actions]
↓
循环 decimation 次(默认 4 次):
├─ _compute_torques(actions) → PD 控制器计算关节力矩
├─ gym.set_dof_actuation_force_tensor() → 施加力矩
├─ gym.simulate() → 推进物理仿真一步
└─ gym.refresh_dof_state_tensor() → 刷新关节状态
↓
[post_physics_step()] 后处理:
├─ 刷新根状态和接触力张量
├─ 提取基座位置/姿态/速度(世界坐标→机体坐标)
├─ 计算重力投影向量(用于姿态感知)
├─ 检测终止条件(碰撞/倾覆/超时)
├─ 计算奖励
├─ 重置已终止环境
├─ 域随机化:随机推力
└─ 构建观测向量
3.3.2 PD 控制器
支持三种控制模式:
P 模式(位置控制,默认):
τ=Kp⋅(θtarget−θ)−Kd⋅θ˙
\\tau = K_p \\cdot (\\theta_{target} – \\theta) – K_d \\cdot \\dot{\\theta}
τ=Kp⋅(θtarget−θ)−Kd⋅θ˙
其中:
- θtarget=action_scale⋅a+θdefault\\theta_{target} = \\text{action\\_scale} \\cdot a + \\theta_{default}θtarget=action_scale⋅a+θdefault
- KpK_pKp:刚度(stiffness)
- KdK_dKd:阻尼(damping)
V 模式(速度控制):
τ=Kp⋅(θ˙target−θ˙)−Kd⋅Δθ˙Δt
\\tau = K_p \\cdot (\\dot{\\theta}_{target} – \\dot{\\theta}) – K_d \\cdot \\frac{\\Delta \\dot{\\theta}}{\\Delta t}
τ=Kp⋅(θ˙target−θ˙)−Kd⋅ΔtΔθ˙
T 模式(力矩控制):
τ=action_scale⋅a
\\tau = \\text{action\\_scale} \\cdot a
τ=action_scale⋅a
最终力矩裁剪到关节力矩限制范围内:
τ=clip(τ,−τlimit,τlimit)
\\tau = \\text{clip}(\\tau, -\\tau_{limit}, \\tau_{limit})
τ=clip(τ,−τlimit,τlimit)
3.3.3 观测空间构建
默认观测向量(以 GO2 为例,48 维):
o=[vbodysv,ωbodysω,gproj,csc,(q−qdefault)sq,q˙sq˙,aprev]
o = \\left[ \\frac{v_{body}}{s_v}, \\frac{\\omega_{body}}{s_{\\omega}}, g_{proj}, \\frac{c}{s_c}, \\frac{(q – q_{default})}{s_q}, \\frac{\\dot{q}}{s_{\\dot{q}}}, a_{prev} \\right]
o=[svvbody,sωωbody,gproj,scc,sq(q−qdefault),sq˙q˙,aprev]
其中:
- vbody∈R3v_{body} \\in \\mathbb{R}^3vbody∈R3:机体坐标系线速度
- ωbody∈R3\\omega_{body} \\in \\mathbb{R}^3ωbody∈R3:机体坐标系角速度
- gproj∈R3g_{proj} \\in \\mathbb{R}^3gproj∈R3:重力向量在机体坐标系的投影
- c∈R3c \\in \\mathbb{R}^3c∈R3:速度指令(x 线速度, y 线速度, yaw 角速度)
- q∈RNdofq \\in \\mathbb{R}^{N_{dof}}q∈RNdof:关节位置
- q˙∈RNdof\\dot{q} \\in \\mathbb{R}^{N_{dof}}q˙∈RNdof:关节速度
- aprev∈RNacta_{prev} \\in \\mathbb{R}^{N_{act}}aprev∈RNact:上一步动作
- sss 为各维度的观测缩放系数
若启用噪声:
onoisy=o+(2⋅U(0,1)−1)⊙σ
o_{noisy} = o + (2 \\cdot U(0,1) – 1) \\odot \\sigma
onoisy=o+(2⋅U(0,1)−1)⊙σ
其中 U(0,1)U(0,1)U(0,1) 为均匀分布,σ\\sigmaσ 为噪声缩放向量,⊙\\odot⊙ 为逐元素乘法。
非对称观测(G1/H1/H1_2):
- Actor 观测:不含线速度 vbodyv_{body}vbody(部署时无法直接测量)
- Critic 特权观测:包含线速度 vbodyv_{body}vbody,用于更准确地估计价值函数
3.3.4 终止条件
环境在以下任一条件满足时终止:
超时终止与碰撞终止的区别:
- 碰撞终止:机器人倒地,给予负奖励(惩罚)
- 超时终止:正常完成回合,不给予终止惩罚
3.4 奖励工程详解
奖励设计是足式机器人 RL 训练的核心。本项目采用多目标加权奖励体系,共 17 个奖励/惩罚项。
3.4.1 速度跟踪奖励(高斯核)
线速度跟踪:
rlin=exp(−∥cxy−vxy∥2σ2)
r_{lin} = \\exp\\left(-\\frac{\\|c_{xy} – v_{xy}\\|^2}{\\sigma^2}\\right)
rlin=exp(−σ2∥cxy−vxy∥2)
角速度跟踪:
rang=exp(−(cyaw−ωz)2σ2)
r_{ang} = \\exp\\left(-\\frac{(c_{yaw} – \\omega_z)^2}{\\sigma^2}\\right)
rang=exp(−σ2(cyaw−ωz)2)
其中 σ=0.25\\sigma = 0.25σ=0.25 为高斯核宽度。误差越小,奖励越接近 1;误差越大,奖励趋近 0。
3.4.2 姿态与运动惩罚
| Z 轴线速度惩罚 | r=−vz2r = -v_z^2r=−vz2 | 防止跳跃/下蹲 |
| XY 轴角速度惩罚 | r=−(ωx2+ωy2)r = -(\\omega_x^2 + \\omega_y^2)r=−(ωx2+ωy2) | 防止翻滚 |
| 姿态倾斜惩罚 | r=−(gproj,x2+gproj,y2)r = -(g_{proj,x}^2 + g_{proj,y}^2)r=−(gproj,x2+gproj,y2) | 保持基座水平 |
| 基座高度惩罚 | r=−(z−ztarget)2r = -(z – z_{target})^2r=−(z−ztarget)2 | 维持目标行走高度 |
3.4.3 关节相关惩罚
| 力矩惩罚 | r=−∑iτi2r = -\\sum_i \\tau_i^2r=−∑iτi2 | 节能,减少电机磨损 |
| 关节速度惩罚 | r=−∑iq˙i2r = -\\sum_i \\dot{q}_i^2r=−∑iq˙i2 | 平滑运动 |
| 关节加速度惩罚 | r=−∑i(Δq˙iΔt)2r = -\\sum_i \\left(\\frac{\\Delta \\dot{q}_i}{\\Delta t}\\right)^2r=−∑i(ΔtΔq˙i)2 | 减少抖动 |
| 动作变化率惩罚 | r=−∑i(ait−ait−1)2r = -\\sum_i (a_i^{t} – a_i^{t-1})^2r=−∑i(ait−ait−1)2 | 动作连贯性 |
3.4.4 接触与步态奖励
足部腾空时间奖励:
rair=∑i∈feet(tair,i−0.5)⋅1[first_contacti]⋅1[∥cxy∥>0.1]
r_{air} = \\sum_{i \\in feet} (t_{air,i} – 0.5) \\cdot \\mathbb{1}[first\\_contact_i] \\cdot \\mathbb{1}[\\|c_{xy}\\| > 0.1]
rair=i∈feet∑(tair,i−0.5)⋅1[first_contacti]⋅1[∥cxy∥>0.1]
其中:
- tair,it_{air,i}tair,i:第 iii 只脚的腾空时间
- 仅在首次触地时计算奖励
- 零指令时不奖励(避免原地踏步获得奖励)
碰撞惩罚:
rcollision=−∑j∈penalized1[∥fcontact,j∥>0.1]
r_{collision} = -\\sum_{j \\in penalized} \\mathbb{1}[\\|f_{contact,j}\\| > 0.1]
rcollision=−j∈penalized∑1[∥fcontact,j∥>0.1]
终止惩罚:
rterm=1[reset]⋅1[¬timeout]
r_{term} = \\mathbb{1}[reset] \\cdot \\mathbb{1}[\\neg timeout]
rterm=1[reset]⋅1[¬timeout]
仅对非超时的终止(即碰撞倒地)施加惩罚。
3.4.5 软限制惩罚
为防止关节到达物理硬极限,引入软限制:
qsoft,lower=m−0.5⋅r⋅α
q_{soft,lower} = m – 0.5 \\cdot r \\cdot \\alpha
qsoft,lower=m−0.5⋅r⋅α
qsoft,upper=m+0.5⋅r⋅α
q_{soft,upper} = m + 0.5 \\cdot r \\cdot \\alpha
qsoft,upper=m+0.5⋅r⋅α
其中 mmm 为限制中点,rrr 为限制范围,α∈[0,1]\\alpha \\in [0,1]α∈[0,1] 为软限制系数(默认 0.9)。
当关节超出软限制时:
rpos_limit=−∑i[(qi−qsoft,lower)−+(qi−qsoft,upper)+]
r_{pos\\_limit} = -\\sum_i \\left[(q_i – q_{soft,lower})_- + (q_i – q_{soft,upper})_+\\right]
rpos_limit=−i∑[(qi−qsoft,lower)−+(qi−qsoft,upper)+]
其中 (x)−=min(x,0)(x)_- = \\min(x, 0)(x)−=min(x,0),(x)+=max(x,0)(x)_+ = \\max(x, 0)(x)+=max(x,0)。
3.4.6 奖励裁剪策略
若启用 only_positive_rewards:
rtotal=max(∑iwi⋅ri,0)+wterm⋅rterm
r_{total} = \\max\\left(\\sum_i w_i \\cdot r_i, 0\\right) + w_{term} \\cdot r_{term}
rtotal=max(i∑wi⋅ri,0)+wterm⋅rterm
终止奖励在正奖励裁剪后单独添加,确保终止惩罚始终生效。
3.5 域随机化(Domain Randomization)
域随机化通过在训练时随机化物理参数,使策略对参数不确定性具有鲁棒性。
3.5.1 摩擦力随机化
预生成 64 个摩擦系数桶:
μk∼U(μmin,μmax),k=1,…,64
\\mu_k \\sim U(\\mu_{min}, \\mu_{max}), \\quad k = 1, …, 64
μk∼U(μmin,μmax),k=1,…,64
每个环境随机分配一个桶:
μenv=μbucket[randint(0,63)]
\\mu_{env} = \\mu_{bucket[randint(0, 63)]}
μenv=μbucket[randint(0,63)]
3.5.2 基座质量随机化
mbase=mbase,original+Δm,Δm∼U(−1,3) kg
m_{base} = m_{base,original} + \\Delta m, \\quad \\Delta m \\sim U(-1, 3)\\,\\text{kg}
mbase=mbase,original+Δm,Δm∼U(−1,3)kg
3.5.3 随机推力扰动
按固定间隔(默认 15 秒)为所有环境设置随机的基座水平速度:
vpush∼U(−vmax,vmax),vmax=1.0 m/s
v_{push} \\sim U(-v_{max}, v_{max}), \\quad v_{max} = 1.0\\,\\text{m/s}
vpush∼U(−vmax,vmax),vmax=1.0m/s
3.6 步态相位控制(G1/H1/H1_2)
双足机器人需要显式的步态相位控制以实现交替行走。
3.6.1 相位计算
设步态周期 T=0.8 sT = 0.8\\,\\text{s}T=0.8s,则当前相位:
ϕ=(t⋅Δt)mod TT∈[0,1)
\\phi = \\frac{(t \\cdot \\Delta t) \\mod T}{T} \\in [0, 1)
ϕ=T(t⋅Δt)modT∈[0,1)
左右脚相位偏移 0.5(半周期,交替步态):
ϕleft=ϕ
\\phi_{left} = \\phi
ϕleft=ϕ
ϕright=(ϕ+0.5)mod 1
\\phi_{right} = (\\phi + 0.5) \\mod 1
ϕright=(ϕ+0.5)mod1
3.6.2 相位编码
将相位以 sin/cos 编码注入观测:
ophase=[sin(2πϕ),cos(2πϕ)]
o_{phase} = [\\sin(2\\pi\\phi), \\cos(2\\pi\\phi)]
ophase=[sin(2πϕ),cos(2πϕ)]
这种连续编码比原始相位值更利于神经网络学习周期性模式。
3.6.3 接触一致性奖励
检查每只脚的接触状态是否与步态相位一致:
is_stance=ϕleg<0.55
is\\_stance = \\phi_{leg} < 0.55
is_stance=ϕleg<0.55
contact=fz,foot>1 N
contact = f_{z,foot} > 1\\,\\text{N}
contact=fz,foot>1N
consistent=¬(contact⊕is_stance)
consistent = \\neg(contact \\oplus is\\_stance)
consistent=¬(contact⊕is_stance)
其中 ⊕\\oplus⊕ 为异或(XOR),¬\\neg¬ 为取反。stance 阶段应接触,swing 阶段应离地。
四、PPO 算法解析
本项目使用 rsl_rl 库实现的 PPO 算法进行策略训练。
4.1 PPO 核心思想
PPO 通过**裁剪替代目标(Clipped Surrogate Objective)**限制策略更新幅度,避免传统策略梯度方法中步长过大导致策略崩溃的问题。
4.2 关键公式
4.2.1 策略目标
LCLIP(θ)=E^t[min(rt(θ)A^t,clip(rt(θ),1−ϵ,1+ϵ)A^t)]
L^{CLIP}(\\theta) = \\hat{\\mathbb{E}}_t \\left[ \\min\\left( r_t(\\theta) \\hat{A}_t, \\text{clip}(r_t(\\theta), 1-\\epsilon, 1+\\epsilon) \\hat{A}_t \\right) \\right]
LCLIP(θ)=E^t[min(rt(θ)A^t,clip(rt(θ),1−ϵ,1+ϵ)A^t)]
其中:
- rt(θ)=πθ(at∣st)πθold(at∣st)r_t(\\theta) = \\frac{\\pi_\\theta(a_t|s_t)}{\\pi_{\\theta_{old}}(a_t|s_t)}rt(θ)=πθold(at∣st)πθ(at∣st):概率比
- A^t\\hat{A}_tA^t:广义优势估计(GAE)
- ϵ=0.2\\epsilon = 0.2ϵ=0.2:裁剪参数
4.2.2 广义优势估计(GAE)
A^tGAE(γ,λ)=∑l=0∞(γλ)lδt+lV
\\hat{A}_t^{GAE(\\gamma,\\lambda)} = \\sum_{l=0}^{\\infty} (\\gamma\\lambda)^l \\delta_{t+l}^V
A^tGAE(γ,λ)=l=0∑∞(γλ)lδt+lV
其中:
δtV=rt+γV(st+1)−V(st)
\\delta_t^V = r_t + \\gamma V(s_{t+1}) – V(s_t)
δtV=rt+γV(st+1)−V(st)
- γ=0.99\\gamma = 0.99γ=0.99:折扣因子
- λ=0.95\\lambda = 0.95λ=0.95:GAE 参数
4.2.3 价值函数损失
LVF(θ)=E^t[(Vθ(st)−Vttarget)2]
L^{VF}(\\theta) = \\hat{\\mathbb{E}}_t \\left[ (V_\\theta(s_t) – V_t^{target})^2 \\right]
LVF(θ)=E^t[(Vθ(st)−Vttarget)2]
若启用裁剪价值损失:
LVF,clip(θ)=E^t[max((Vθ−Vtarget)2,(Vθold+clip(Vθ−Vθold,−ϵ,ϵ)−Vtarget)2)]
L^{VF,clip}(\\theta) = \\hat{\\mathbb{E}}_t \\left[ \\max\\left( (V_\\theta – V^{target})^2, (V_{\\theta_{old}} + \\text{clip}(V_\\theta – V_{\\theta_{old}}, -\\epsilon, \\epsilon) – V^{target})^2 \\right) \\right]
LVF,clip(θ)=E^t[max((Vθ−Vtarget)2,(Vθold+clip(Vθ−Vθold,−ϵ,ϵ)−Vtarget)2)]
4.2.4 总损失
LTOTAL(θ)=−LCLIP(θ)+c1LVF(θ)−c2⋅Entropy(πθ)
L^{TOTAL}(\\theta) = -L^{CLIP}(\\theta) + c_1 L^{VF}(\\theta) – c_2 \\cdot \\text{Entropy}(\\pi_\\theta)
LTOTAL(θ)=−LCLIP(θ)+c1LVF(θ)−c2⋅Entropy(πθ)
其中:
- c1=1.0c_1 = 1.0c1=1.0:价值损失系数
- c2=0.01c_2 = 0.01c2=0.01:熵正则化系数
4.2.5 自适应学习率
若学习率调度为 adaptive:
- 当 KL 散度 <0.5×target_KL< 0.5 \\times target\\_KL<0.5×target_KL 时:学习率 ×1.5\\times 1.5×1.5
- 当 KL 散度 >1.5×target_KL> 1.5 \\times target\\_KL>1.5×target_KL 时:学习率 ×0.5\\times 0.5×0.5
- 目标 KL 散度:0.010.010.01
4.3 网络架构
4.3.1 标准 Actor-Critic(GO2)
Actor:
Input: obs (48-dim)
→ Linear(48, 512) + ELU
→ Linear(512, 256) + ELU
→ Linear(256, 128) + ELU
→ Linear(128, 12) # 输出动作均值
→ 可学习噪声标准差
Critic:
Input: privileged_obs (48-dim)
→ Linear(48, 512) + ELU
→ Linear(512, 256) + ELU
→ Linear(256, 128) + ELU
→ Linear(128, 1) # 输出状态价值
4.3.2 LSTM Actor-Critic(G1/H1/H1_2)
Actor:
Input: obs (47-dim or 41-dim)
→ Linear(input_dim, 32) + ELU
→ LSTM(32, 64, num_layers=1)
→ Linear(64, num_actions)
Critic:
Input: privileged_obs (50-dim or 44-dim)
→ Linear(input_dim, 32) + ELU
→ LSTM(32, 64, num_layers=1)
→ Linear(64, 1)
LSTM 隐状态在每个回合开始时重置为零。
五、关键文件技术说明
5.1 配置体系(legged_robot_config.py)
配置采用分层嵌套结构,12 个子配置类覆盖全部参数:
| env | 环境维度 | num_envs=4096, num_observations=48 |
| terrain | 地形参数 | mesh_type='plane', 摩擦系数 |
| commands | 速度指令 | 采样范围 [-1, 1] m/s, 重采样间隔 10s |
| init_state | 初始状态 | 基座高度, 默认关节角 |
| control | 控制器 | control_type='P', PD 增益, decimation=4 |
| asset | 机器人资产 | URDF 路径, 碰撞体关键词 |
| domain_rand | 域随机化 | 摩擦力/质量/推力范围 |
| rewards | 奖励权重 | 17 项奖励的权重值 |
| normalization | 归一化 | 观测缩放系数, 裁剪范围 |
| noise | 噪声 | 各维度噪声缩放, 噪声等级 |
| viewer | 查看器 | 相机位置 [10, 0, 6] |
| sim | 仿真参数 | dt=0.005s, PhysX 求解器参数 |
5.2 任务注册表(task_registry.py)
采用注册表模式解耦任务定义与使用:
# 注册(envs/__init__.py)
task_registry.register("go2", LeggedRobot, GO2RoughCfg(), GO2RoughCfgPPO())
# 使用(scripts/train.py)
env, env_cfg = task_registry.make_env(name=args.task, args=args)
runner, train_cfg = task_registry.make_alg_runner(env=env, name=args.task, args=args)
5.3 程序化地形(terrain.py)
地形生成支持三种模式:
7 种地形类型及难度参数化:
| 平滑斜坡 | slope = difficulty × 0.4 | 50% 概率反向 |
| 粗糙斜坡 | slope + 随机高度 ±0.05m | 叠加随机地形 |
| 上/下楼梯 | step_height = 0.05 + 0.18 × difficulty | 正负号区分上下 |
| 离散障碍 | height = 0.05 + 0.2 × difficulty | 20 个随机矩形 |
| 踏步石 | size = 1.5 × (1.05 – difficulty) | 难度越高石块越小 |
| 缝隙 | gap_size = 1.0 × difficulty | 中央缝隙 |
| 坑洞 | depth = 1.0 × difficulty | 中央坑洞 |
六、技术架构设计
6.1 类继承关系
BaseConfig
└── LeggedRobotCfg
├── GO2RoughCfg
├── H1RoughCfg
├── H1_2RoughCfg
└── G1RoughCfg
BaseConfig
└── LeggedRobotCfgPPO
├── GO2RoughCfgPPO
├── H1RoughCfgPPO
├── H1_2RoughCfgPPO
└── G1RoughCfgPPO
BaseTask
└── LeggedRobot
├── (GO2 直接使用 LeggedRobot)
├── H1Robot
├── H1_2Robot
└── G1Robot
6.2 训练数据流
┌─────────────┐ actions ┌──────────────┐ torques ┌──────────────┐
│ PPO 算法 │ ──────────────→ │ LeggedRobot │ ──────────────→ │ Isaac Gym │
│ (rsl_rl) │ │ _compute_ │ │ 物理仿真 │
│ │ ←────────────── │ torques() │ ←────────────── │ │
│ │ obs, rew, done │ step() │ 状态更新 │ │
└─────────────┘ └──────────────┘ └──────────────┘
↑ ↑
│ │
策略网络更新 奖励计算/终止检测/域随机化
6.3 控制频率链
仿真频率: 1/dt (默认 200Hz, H1_2 为 400Hz)
│
│ ÷ decimation (默认 4, H1_2 为 8)
↓
策略频率: 50Hz (所有机器人统一)
6.4 观测空间对比
| GO2 | 48 | None | 12 | MLP |
| G1 | 47 | 50 | 12 | LSTM |
| H1 | 41 | 44 | 10 | LSTM |
| H1_2 | 47 | 50 | 12 | LSTM |
七、使用方法及示例代码
7.1 环境依赖
# 基础依赖
pip install torch>=1.10 numpy scipy
# Isaac Gym (需从 NVIDIA 官网下载)
# https://developer.nvidia.com/isaac-gym
# rsl_rl (强化学习算法库)
pip install git+https://github.com/leggedrobotics/rsl_rl.git
7.2 训练
# 训练 GO2 四足机器人
python legged_gym/scripts/train.py –task go2
# 训练 G1 双足机器人
python legged_gym/scripts/train.py –task g1
# 无头模式训练(服务器环境)
python legged_gym/scripts/train.py –task h1 –headless
# 指定随机种子
python legged_gym/scripts/train.py –task go2 –seed 42
# 指定最大迭代次数
python legged_gym/scripts/train.py –task g1 –max_iterations 5000
7.3 推理与测试
# 测试 GO2
python legged_gym/scripts/play.py –task go2
# 指定运行名称加载模型
python legged_gym/scripts/play.py –task h1_2 –load_run <run_name>
# 指定检查点编号
python legged_gym/scripts/play.py –task g1 –load_run <run_name> –checkpoint 1000
7.4 自定义机器人配置示例
from legged_gym.envs.base.legged_robot_config import LeggedRobotCfg
class MyRobotCfg(LeggedRobotCfg):
class env(LeggedRobotCfg.env):
num_envs = 2048
num_observations = 48
num_actions = 12
class control(LeggedRobotCfg.control):
control_type = 'P'
stiffness = {'joint': 50.}
damping = {'joint': 2.}
action_scale = 0.25
decimation = 4
class asset(LeggedRobotCfg.asset):
file = '{LEGGED_GYM_ROOT_DIR}/resources/robots/my_robot/robot.urdf'
name = "my_robot"
foot_name = "foot"
class rewards(LeggedRobotCfg.rewards):
class scales(LeggedRobotCfg.rewards.scales):
tracking_lin_vel = 2.0 # 增大线速度跟踪权重
torques = –0.0005 # 调整力矩惩罚
7.5 自定义奖励函数示例
from legged_gym.envs.base.legged_robot import LeggedRobot
class MyRobot(LeggedRobot):
def _reward_custom(self):
"""自定义奖励:惩罚基座前后晃动"""
return torch.abs(self.base_ang_vel[:, 1]) # pitch 角速度绝对值
然后在配置中添加权重:
class MyRobotCfg(LeggedRobotCfg):
class rewards(LeggedRobotCfg.rewards):
class scales(LeggedRobotCfg.rewards.scales):
custom = –0.1
八、数学工具详解
8.1 四元数到欧拉角(ZYX 内旋)
给定四元数 q=[qx,qy,qz,qw]q = [q_x, q_y, q_z, q_w]q=[qx,qy,qz,qw]:
Roll(X 轴旋转):
ϕ=arctan2(2(qwqx+qyqz),qw2−qx2−qy2+qz2)
\\phi = \\arctan2(2(q_w q_x + q_y q_z), q_w^2 – q_x^2 – q_y^2 + q_z^2)
ϕ=arctan2(2(qwqx+qyqz),qw2−qx2−qy2+qz2)
Pitch(Y 轴旋转):
θ=arcsin(clip(2(qwqy−qzqx),−1,1))
\\theta = \\arcsin(\\text{clip}(2(q_w q_y – q_z q_x), -1, 1))
θ=arcsin(clip(2(qwqy−qzqx),−1,1))
Yaw(Z 轴旋转):
ψ=arctan2(2(qwqz+qxqy),qw2+qx2−qy2−qz2)
\\psi = \\arctan2(2(q_w q_z + q_x q_y), q_w^2 + q_x^2 – q_y^2 – q_z^2)
ψ=arctan2(2(qwqz+qxqy),qw2+qx2−qy2−qz2)
8.2 仅偏航旋转的四元数变换
将四元数的 roll 和 pitch 分量置零,只保留 yaw:
qyaw=normalize([0,0,qz,qw])
q_{yaw} = \\text{normalize}([0, 0, q_z, q_w])
qyaw=normalize([0,0,qz,qw])
然后对向量施加旋转:
v′=qyaw⊗v⊗qyaw∗
v' = q_{yaw} \\otimes v \\otimes q_{yaw}^*
v′=qyaw⊗v⊗qyaw∗
8.3 角度归一化到 [−π,π][-\\pi, \\pi][−π,π]
θnorm=((θmod 2π)+2π)mod 2π
\\theta_{norm} = ((\\theta \\mod 2\\pi) + 2\\pi) \\mod 2\\pi
θnorm=((θmod2π)+2π)mod2π
θnorm={θnorm−2πif θnorm>πθnormotherwise
\\theta_{norm} = \\begin{cases} \\theta_{norm} – 2\\pi & \\text{if } \\theta_{norm} > \\pi \\\\ \\theta_{norm} & \\text{otherwise} \\end{cases}
θnorm={θnorm−2πθnormif θnorm>πotherwise
8.4 平方根分布随机数
生成概率密度向中间集中的随机数:
r=sign(u)⋅∣u∣,u∼U(−1,1)
r = \\text{sign}(u) \\cdot \\sqrt{|u|}, \\quad u \\sim U(-1, 1)
r=sign(u)⋅∣u∣,u∼U(−1,1)
rnorm=r+12∈[0,1]
r_{norm} = \\frac{r + 1}{2} \\in [0, 1]
rnorm=2r+1∈[0,1]
x=(upper−lower)⋅rnorm+lower
x = (upper – lower) \\cdot r_{norm} + lower
x=(upper−lower)⋅rnorm+lower
相比均匀分布,减少了极端值的出现概率。
九、各文件功能详解与注释意义
9.1 包初始化模块
__init__.py(legged_gym/)
| 功能 | 定义项目根目录 LEGGED_GYM_ROOT_DIR 和环境目录 LEGGED_GYM_ENVS_DIR 的全局路径常量 |
| 注释意义 | 阐明路径常量的用途,便于理解其他模块中资源文件的引用方式 |
envs/__init__.py
| 功能 | 导入所有机器人配置和环境类,将它们注册到全局任务注册表 task_registry |
| 注册任务 | go2→LeggedRobot, h1→H1Robot, h1_2→H1_2Robot, g1→G1Robot |
| 注释意义 | 说明注册表模式的设计意图——解耦任务定义与使用,统一创建接口 |
9.2 基类模块(envs/base/)
base_config.py
| 核心类 | BaseConfig |
| 功能 | 配置系统基类,实现递归实例化机制——将嵌套的类定义自动转换为实例对象 |
| 设计理念 | 声明式配置:子类只需声明 class env: num_envs=4096,无需手动写 __init__ |
| 注释意义 | 解释递归实例化的规则和注意事项,帮助理解整个配置体系的工作原理 |
关键方法:
- init_member_classes(obj): 遍历对象属性,将类类型属性替换为实例,递归处理嵌套
base_task.py
| 核心类 | BaseTask |
| 功能 | 强化学习任务基类,封装 Isaac Gym 仿真的核心生命周期 |
| 核心职责 | 仿真环境创建、GPU/CPU 张量缓冲区分配、渲染管理、键盘事件处理 |
| 注释意义 | 阐明数据流(策略→动作→仿真→观测/奖励)和子类必须实现的接口 |
关键方法:
- __init__(): 解析设备、分配缓冲区、创建仿真、注册键盘事件
- step(): 抽象方法,子类实现一步仿真
- reset(): 重置所有环境并返回初始观测
- render(): 渲染当前帧,处理 ESC/V 键盘事件
legged_robot.py
| 核心类 | LeggedRobot(继承 BaseTask) |
| 功能 | 足式机器人强化学习的核心实现,包含完整的仿真-训练循环 |
| 代码量 | 项目最大文件(~600行),涵盖控制、奖励、观测、域随机化等全部逻辑 |
| 注释意义 | 逐函数解释物理含义和设计决策,帮助理解奖励工程和域随机化的实现细节 |
核心功能模块:
| 仿真步进 | step() | 动作裁剪→PD控制→物理仿真→后处理 |
| 后处理 | post_physics_step() | 状态更新→终止检测→奖励计算→域随机化→观测构建 |
| PD控制 | _compute_torques() | 支持P(位置)/V(速度)/T(力矩)三种控制模式 |
| 观测构建 | compute_observations() | 拼接速度+重力+指令+关节+动作,可选噪声注入 |
| 终止检测 | check_termination() | 碰撞/倾覆/超时三种终止条件 |
| 域随机化 | _process_rigid_shape_props() | 摩擦力随机化(64桶预生成) |
| _process_rigid_body_props() | 基座质量随机化 | |
| _push_robots() | 随机推力扰动 | |
| 奖励函数 | 17个 _reward_* 方法 | 速度跟踪/姿态保持/力矩惩罚/碰撞惩罚等 |
奖励函数体系:
| tracking_lin_vel | 奖励 | 高斯核线速度跟踪 |
| tracking_ang_vel | 奖励 | 高斯核角速度跟踪 |
| lin_vel_z | 惩罚 | Z轴线速度(防止跳跃) |
| ang_vel_xy | 惩罚 | XY轴角速度(防止翻滚) |
| orientation | 惩罚 | 基座倾斜(重力投影偏差) |
| base_height | 惩罚 | 高度偏离目标值 |
| torques | 惩罚 | 关节力矩(节能) |
| dof_vel | 惩罚 | 关节速度(平滑运动) |
| dof_acc | 惩罚 | 关节加速度(减少抖动) |
| action_rate | 惩罚 | 动作变化率(连贯性) |
| collision | 惩罚 | 非足部碰撞 |
| termination | 惩罚 | 非超时终止(倒地) |
| dof_pos_limits | 惩罚 | 关节位置超限 |
| feet_air_time | 奖励 | 足部腾空时间(步态周期) |
| stumble | 惩罚 | 绊倒检测 |
| stand_still | 惩罚 | 无指令时关节偏移 |
| feet_contact_forces | 惩罚 | 高接触力(柔和着地) |
legged_robot_config.py
| 核心类 | LeggedRobotCfg + LeggedRobotCfgPPO |
| 功能 | 定义完整的默认配置体系,所有机器人配置的父类 |
| 配置层次 | 12个子配置类,覆盖仿真/控制/奖励/域随机化等全部参数 |
| 注释意义 | 为每个参数标注物理单位、取值范围和设计意图,便于调参和理解 |
配置子类一览:
| env | 环境维度 | num_envs=4096, num_actions=12 |
| terrain | 地形参数 | mesh_type, 摩擦系数, 高度场分辨率 |
| commands | 速度指令 | 采样范围, 重采样间隔, 课程学习 |
| init_state | 初始状态 | 基座位置/姿态, 默认关节角 |
| control | 控制器 | control_type=‘P’, PD增益, decimation |
| asset | 机器人资产 | URDF路径, 碰撞配置, 物理属性 |
| domain_rand | 域随机化 | 摩擦力/质量/推力随机范围 |
| rewards | 奖励权重 | 各奖励项权重, 软限制系数 |
| normalization | 归一化 | 观测缩放系数, 裁剪范围 |
| noise | 噪声 | 各维度噪声缩放, 噪声等级 |
| viewer | 查看器 | 相机位置和注视点 |
| sim | 仿真参数 | dt=0.005s, PhysX求解器参数 |
9.3 机器人特定模块
g1/g1_config.py + g1/g1_env.py
| 机器人 | G1 双足人形机器人,12自由度(含脚踝roll) |
| 配置特点 | LSTM策略、步态相位奖励、强姿态/高度惩罚 |
| 环境特点 | 步态相位注入观测(sin/cos编码)、足部状态追踪、5个额外奖励函数 |
| 观测空间 | 47维 = 角速度(3) + 重力(3) + 指令(3) + 关节(12×3) + 相位(2) |
| 特权观测 | 50维 = 线速度(3) + Actor观测(47) |
| 注释意义 | 解释步态相位计算逻辑和足部追踪机制,阐明G1特有的奖励函数设计 |
G1 特有奖励函数:
| _reward_contact | 步态接触一致性(XOR检测) |
| _reward_feet_swing_height | 摆腿高度(目标0.08m) |
| _reward_alive | 存活奖励(鼓励不倒) |
| _reward_contact_no_vel | 接触时速度惩罚(减少滑步) |
| _reward_hip_pos | 髋关节位置惩罚(索引[1,2,7,8]) |
go2/go2_config.py
| 机器人 | GO2 四足机器人,12自由度 |
| 特殊之处 | 无自定义环境类,直接使用通用 LeggedRobot |
| 配置特点 | 标准ActorCritic策略(非LSTM)、站立姿态初始关节角 |
| 注释意义 | 说明为何四足机器人无需自定义环境——默认实现已足够 |
h1/h1_config.py + h1/h1_env.py
| 机器人 | H1 双足人形机器人,10自由度(单自由度踝关节) |
| 与G1区别 | 无脚踝roll、含手臂关节(4个)、更高PD刚度 |
| 观测空间 | 41维 = 角速度(3) + 重力(3) + 指令(3) + 关节(10×3) + 相位(2) |
| 注释意义 | 对比H1与G1的关节索引差异,解释髋关节惩罚索引[0,1,5,6]的来源 |
h1_2/h1_2_config.py + h1_2/h1_2_env.py
| 机器人 | H1_2 双足人形机器人,12自由度(双自由度踝关节) |
| 与H1区别 | 增加踝roll、更高PD刚度(髋200/膝300)、更小仿真步(0.0025s)、非零armature |
| 策略频率 | 50Hz = 0.0025s × 8(decimation) |
| 注释意义 | 解释高刚度下需要更小仿真步的原因(数值稳定性),armature参数的作用 |
9.4 脚本模块(scripts/)
train.py
| 功能 | 训练入口脚本 |
| 流程 | 解析参数 → 创建环境 → 创建PPO运行器 → 训练循环 |
| 使用 | python train.py –task go2 |
| 注释意义 | 说明训练脚本的简洁设计——核心逻辑由任务注册表和运行器封装 |
play.py
| 功能 | 推理/测试入口脚本 |
| 流程 | 加载配置 → 覆盖测试参数 → 创建环境 → 加载策略 → 推理循环 → 可选JIT导出 |
| 测试参数覆盖 | 减少环境数(≤100)、关闭噪声/随机化、启用实时同步 |
| JIT导出 | 支持标准MLP和LSTM策略,导出为TorchScript用于C++部署 |
| 注释意义 | 解释测试模式与训练模式的参数差异,JIT导出的部署意义 |
9.5 工具模块(utils/)
helpers.py
| 核心函数 | 7个辅助函数 + 1个JIT导出器类 |
| 功能 | 配置转换、参数解析、模型加载、JIT导出 |
| 注释意义 | 详细说明每个函数的输入输出和设计决策 |
关键函数:
| class_to_dict() | 配置类→字典(递归,用于序列化) |
| update_class_from_dict() | 字典→配置类(递归更新) |
| set_seed() | 全局随机种子(Python/NumPy/PyTorch) |
| parse_sim_params() | 合并命令行和配置文件的仿真参数 |
| get_load_path() | 查找最新模型检查点路径 |
| get_args() | 解析命令行参数(集成Isaac Gym参数) |
| export_policy_as_jit() | 导出策略为JIT(支持MLP和LSTM) |
| PolicyExporterLSTM | LSTM策略JIT导出器(封装隐状态管理) |
isaacgym_utils.py
| 核心函数 | get_euler_xyz() |
| 功能 | 四元数→欧拉角(ZYX内旋顺序) |
| 注释意义 | 解释与Isaac Gym自带函数的区别——TorchScript兼容、GPU加速 |
logger.py
| 核心类 | Logger |
| 功能 | 训练日志记录,支持状态量和奖励统计 |
| 注释意义 | 说明按回合聚合奖励的加权平均计算方式 |
math.py
| 核心函数 | 3个数学工具函数 |
| 注释意义 | 解释每个函数的物理含义和应用场景 |
| quat_apply_yaw() | 仅偏航旋转的四元数变换 | 航向指令计算 |
| wrap_to_pi() | 角度归一化到[-π,π] | 航向误差最短路径 |
| torch_rand_sqrt_float() | 平方根分布随机数 | 减少极端值采样 |
task_registry.py
| 核心类 | TaskRegistry(全局单例 task_registry) |
| 功能 | 任务的注册、查询、环境创建、算法创建 |
| 设计模式 | 注册表模式——解耦任务定义与使用 |
| 注释意义 | 阐明统一创建接口的设计意图和完整创建流程 |
核心方法:
- register(): 注册任务(名称→环境类+配置)
- make_env(): 创建仿真环境(解析参数→设置种子→创建实例)
- make_alg_runner(): 创建PPO运行器(设置日志→创建运行器→可选恢复)
terrain.py
| 核心类 | Terrain + 2个辅助函数 |
| 功能 | 程序化地形生成,支持7种地形类型和课程学习 |
| 注释意义 | 详细说明地形类型的选择逻辑和难度参数化 |
地形类型:
| 平滑斜坡 | pyramid_sloped_terrain() | slope=difficulty×0.4 |
| 粗糙斜坡 | pyramid_sloped_terrain() + random_uniform_terrain() | slope + 随机高度±0.05m |
| 上楼梯 | pyramid_stairs_terrain() | step_height=0.05+0.18×difficulty |
| 下楼梯 | pyramid_stairs_terrain() | step_height=-(0.05+0.18×difficulty) |
| 离散障碍 | discrete_obstacles_terrain() | height=0.05+0.2×difficulty |
| 踏步石 | stepping_stones_terrain() | size=1.5×(1.05-difficulty) |
| 缝隙 | gap_terrain() | gap_size=1.0×difficulty |
| 坑洞 | pit_terrain() | depth=1.0×difficulty |
十、快速上手
训练
# 训练 GO2 四足机器人
python legged_gym/scripts/train.py –task go2
# 训练 G1 双足机器人
python legged_gym/scripts/train.py –task g1
# 无头模式训练
python legged_gym/scripts/train.py –task h1 –headless
测试
# 测试 GO2
python legged_gym/scripts/play.py –task go2
# 指定运行名称测试
python legged_gym/scripts/play.py –task h1_2 –load_run <run_name>
依赖
- Isaac Gym (Preview)
- PyTorch ≥ 1.10
- rsl_rl (强化学习算法库)
- numpy, scipy