本文档面向已具备 ROS 2 参数系统基础认知的开发者,系统拆解 config/ 目录下多组推理配置 YAML 的结构语义、校验规则与运行时映射关系。掌握这些配置项的精确含义后,你将能够安全地新增自定义策略、调整观测布局、或为不同 ONNX 模型匹配专用的帧堆叠与归一化参数,而无需改动 C++ 源码。
Sources: inference.yaml, ros_interface.cpp
YAML 配置在启动链路中的位置
推理节点并非直接读取磁盘上的 YAML 文件,而是通过 ROS 2 Parameter 机制接收配置。启动链路如下:Launch 文件将指定的 YAML 路径注入 Node::parameters,节点构造时调用 load_config(),将 ROS 参数转换为内部的 PolicyRuntime 向量。每个 PolicyRuntime 包含独立的模型上下文(ModelContext)、观测布局(obs_layout)、帧堆叠状态与可选的 MotionLoader。
flowchart LR
A[inference.launch.py] -->|parameters=configs| B[ROS 2 Parameter Server]
B --> C[InferenceNode 构造函数]
C --> D[load_config]
D --> E[PolicyRuntime 向量]
E --> F[ONNX Session 初始化]
E --> G[MotionLoader 初始化]
Sources: inference.launch.py, inference_node.hpp
核心参数字段分类详解
配置文件中所有参数均声明在 inference_node/ros__parameters 命名空间下。按照功能可划分为六大类。
策略列表参数
策略列表参数是多策略系统的核心,决定了节点启动时加载多少个 ONNX 模型以及是否绑定动作参考文件。
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
model_names |
string[] |
是 | 每个元素对应 models/ 目录下的一个 ONNX 文件名,数组长度即策略总数 |
motion_names |
string[] |
否 | 每个元素对应 motions/ 目录下的 .npz 文件;若为空数组,则所有策略均不加载动作参考 |
关键约束:motion_names 要么为空,要么长度必须与 model_names 严格一致。运行时通过手柄 LB 键在基础策略与 motion 策略之间切换,通过 RB 键在多个 motion 策略之间轮询。若某策略绑定了 motion 文件,其观测布局中通常需要包含 motion_pos 与 motion_vel 源,否则模型输入将缺少动作先验。
Sources: ros_interface.cpp, ros_interface.cpp, ros_interface.cpp
观测布局参数
观测布局参数定义了每个策略的 ONNX 输入向量由哪些语义字段拼接而成。
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
obs_layouts |
string[] |
是 | 主观测布局,每个策略一个字符串,采用 name:size, name:size 格式 |
extra_obs_layouts |
string[] |
否 | 额外观测布局,不参与帧堆叠,直接拼接在主观测之后 |
parse_obs_layout() 在启动时对每个布局字符串执行严格解析:字段名必须存在于预定义的 obs_source_definitions() 注册表中,尺寸必须是正整数,格式必须为 name:size。任何拼写错误或尺寸不匹配都会在构造阶段抛出异常并终止节点。
Sources: obs_manager.cpp, ros_interface.cpp
帧堆叠参数
帧堆叠参数控制时序观测如何组装成模型输入。对于需要历史帧信息的策略(如行走),通常设置较大的 frame_stack;对于基于参考动作的策略(如舞蹈、起身),通常设置为 1。
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
frame_stacks |
int[] |
是 | 每个策略的堆叠帧数,必须 > 0 |
obs_stack_orders |
string[] |
是 | 堆叠顺序,支持 frame_major 或 obs_major |
frame_major 将连续帧按时间顺序紧密排列:[frame0_obs0, frame0_obs1, ..., frameN_obs0, frameN_obs1];obs_major 则按观测字段分组排列:[frame0_obs0, frame1_obs0, ..., frame0_obs1, frame1_obs1, ...]。选择哪种顺序必须与训练时使用的 PyTorch 张量布局保持一致,否则模型输入将被错误解释。
Sources: inference_node.cpp, inference_node.hpp
运行时控制参数
| 字段 | 类型 | 典型值 | 说明 |
|---|---|---|---|
decimation |
int |
5 |
控制线程与推理线程的频率比。控制线程周期为 dt,推理线程周期为 dt * decimation |
dt |
float |
0.004 |
控制周期(秒),对应 250 Hz |
act_alpha |
float |
1.0 |
动作平滑系数,last_act = alpha * act + (1-alpha) * last_act。1.0 表示无平滑 |
intra_threads |
int |
1 |
ONNX Runtime 全局线程池内的算子内并行线程数,-1 表示不设置 |
decimation 与 dt 共同决定了推理频率。例如 dt=0.004、decimation=5 时,控制频率为 250 Hz,推理频率为 50 Hz。这一参数必须与训练时的策略控制频率严格对齐。
Sources: ros_interface.cpp, inference_node.hpp
归一化与限幅参数
| 字段 | 类型 | 说明 |
|---|---|---|
obs_scales_lin_vel |
float |
线速度指令缩放系数 |
obs_scales_ang_vel |
float |
角速度(IMU 与指令)缩放系数 |
obs_scales_dof_pos |
float |
关节位置偏差缩放系数 |
obs_scales_dof_vel |
float |
关节速度缩放系数 |
obs_scales_gravity_b |
float |
机体坐标系重力向量缩放系数 |
clip_observations |
float |
观测值硬限幅绝对值上限 |
action_scale |
float |
网络输出动作缩放系数 |
clip_actions |
float |
动作输出硬限幅绝对值上限 |
这些系数必须与训练阶段的 env_cfg 保持一致。clip_observations 在推理线程的每次循环中通过 std::clamp 执行,防止异常传感器读数导致模型发散;action_scale 与 joint_default_angle 共同将网络输出映射为实际关节目标位置:act = output * action_scale + default_angle。
Sources: ros_interface.cpp, inference_node.cpp
运动学与安全参数
| 字段 | 类型 | 说明 |
|---|---|---|
joint_num |
int |
机器人主动关节总数,当前为 23 |
usd2urdf |
int[] |
ONNX 输出关节索引到 URDF/电机关节索引的映射表 |
joint_default_angle |
double[] |
各关节默认站立姿态(弧度),长度须为 joint_num |
joint_limits |
double[] |
各关节限位,每两个元素构成 [min, max],共 joint_num 组 |
clip_cmd |
double[] |
手柄/指令速度限幅,每两个元素构成 [min, max],顺序为 vx, vy, yaw |
gravity_z_upper |
float |
机体坐标系重力向量 Z 分量的安全上限。当 gravity_b.z > gravity_z_upper 时判定机器人摔倒,触发紧急停机 |
usd2urdf 是解决训练环境(USD/Isaac Gym)与真实机器人关节顺序不一致的关键映射。joint_limits 在 get_dof_pos_obs() 中实时检查,一旦越界立即调用 rclcpp::shutdown() 停止进程,属于最后一道硬件安全防线。gravity_z_upper 的数值需根据实际机器人结构调试:行走模式通常设为 -0.5 左右,而起身模式需要允许更大的姿态变化,故设为 1.0。
Sources: obs_manager.cpp, ros_interface.cpp
外部感知参数
| 字段 | 类型 | 说明 |
|---|---|---|
perception_obs_topic |
string |
外部感知观测的 ROS 话题名,默认 elevation_data |
extra_obs_layouts 中的 perception:size |
— | 声明感知观测的维度,如 187 |
当策略需要地形高程图、深度特征等外部感知输入时,通过 perception_obs_topic 订阅 Float32MultiArray 消息。感知观测被归类为 extra_obs_layouts,不参与帧堆叠,仅在每次推理时取最新一帧。
Sources: ros_interface.cpp, inference_attn_enc.yaml
可用观测源注册表
obs_source_definitions() 以静态向量形式注册了所有合法的观测源名称。任何出现在 obs_layouts 或 extra_obs_layouts 中的字段名必须匹配下表。
| 观测源名称 | 尺寸 | 语义说明 | 依赖条件 |
|---|---|---|---|
ang_vel |
3 |
IMU 角速度(机体坐标系),经 obs_scales_ang_vel 缩放 |
IMU 已初始化 |
gravity_b |
3 |
世界坐标系重力向量旋转到机体坐标系,经 obs_scales_gravity_b 缩放 |
IMU 已初始化 |
cmd_vel |
3 |
遥控器/指令的 [vx, vy, yaw] 速度指令,经 obs_scales_lin_vel/obs_scales_ang_vel 缩放 |
— |
dof_pos |
joint_num |
各关节相对默认位置的角度偏差,经 obs_scales_dof_pos 缩放 |
电机已初始化 |
dof_vel |
joint_num |
各关节角速度,经 obs_scales_dof_vel 缩放 |
电机已初始化 |
last_action |
joint_num |
上一时刻策略网络原始输出(未经 action_scale 缩放) |
— |
interrupt |
1 |
中断标志位,0.0 或 1.0,用于支持外部关节覆盖 |
配置含 interrupt 源 |
perception |
任意正整数 | 外部感知特征向量 | perception_obs_topic 有数据发布 |
motion_pos |
joint_num |
当前帧动作参考的关节目标位置 | 策略绑定了 .npz 动作文件 |
motion_vel |
joint_num |
当前帧动作参考的关节目标速度 | 策略绑定了 .npz 动作文件 |
Sources: obs_manager.cpp, obs_manager.cpp
现成配置文件全景对比
config/ 目录下已提供六组经过验证的配置,分别对应不同的运行场景。理解它们之间的差异是编写自定义配置的最佳起点。
| 配置文件 | 策略数 | 模型文件 | Motion 文件 | 帧堆叠 | 特殊观测源 | 典型用途 |
|---|---|---|---|---|---|---|
inference.yaml |
1 | policy.onnx |
— | [10] |
标准六元组 | 基础行走 |
inference_amp.yaml |
1 | policy_amp.onnx |
— | [1] |
标准六元组 | AMP 风格行走,关节限幅更宽松 |
inference_attn_enc.yaml |
1 | policy_attn_enc.onnx |
— | [5] |
标准六元组 + perception:187 |
注意力编码器地形感知行走 |
inference_getup.yaml |
2 | policy.onnx + policy_getup.onnx |
"" + getup.npz |
[10, 1] |
策略1标准;策略2含 motion_pos/motion_vel |
摔倒后自动起身 |
inference_beyondmimic.yaml |
4 | policy.onnx + policy_wave.onnx + policy_dance.onnx + policy_punch.onnx |
"" + wave.npz + dance.npz + punch.npz |
[10, 1, 1, 1] |
策略1标准;其余含 motion_pos/motion_vel |
交互式动作库(挥手/舞蹈/冲拳) |
inference_interrupt.yaml |
1 | policy_interrupt.onnx |
— | [10] |
标准六元组 + interrupt:1 |
支持外部关节中断覆盖 |
值得注意的规律是:凡是涉及动作参考(Motion)的策略,帧堆叠均为 1,因为动作序列本身已经提供了时序先验;基础行走策略的帧堆叠通常为 5 或 10,以补偿单帧观测缺少的历史动态信息。
Sources: config/
配置加载与一致性校验
load_config() 在节点构造阶段执行,其校验逻辑遵循"列表长度对齐"原则。核心校验规则可概括为:
flowchart TD
A[读取 model_names] --> B{长度 N > 0?}
B -->|否| C[抛出异常: 至少需要一个策略]
B -->|是| D[读取 obs_layouts / frame_stacks / obs_stack_orders]
D --> E{长度均等于 N?}
E -->|否| F[抛出异常: 列表长度不一致]
E -->|是| G[读取 motion_names / extra_obs_layouts]
G --> H{为空或长度等于 N?}
H -->|否| I[抛出异常: 可选列表长度不合法]
H -->|是| J[遍历构建 PolicyRuntime]
J --> K[校验 ONNX 输入尺寸]
K --> L{尺寸匹配?}
L -->|否| M[抛出异常: 模型输入维度不匹配]
L -->|是| N[启动完成]
具体来说,obs_layouts、frame_stacks、obs_stack_orders 被强制要求与 model_names 同长度;motion_names 与 extra_obs_layouts 允许为空,但若非空则也必须对齐。此外,setup_model() 会将解析后的观测维度 obs_num * frame_stack + extra_obs_num 与 ONNX 模型的实际输入尺寸进行比对,任何不匹配都会立即报错,避免在运行时出现难以调试的张量形状错误。
Sources: ros_interface.cpp, inference_node.cpp
运行时热切换的内部行为
当节点支持多策略(motion 策略或 interrupt 策略)时,手柄 LB 键触发策略切换。切换并非瞬时完成,而是遵循暂停-切换-重置-恢复的范式,以保证不同策略的观测缓冲区不会相互污染:
is_running原子交换为false,推理线程进入休眠;- 切换
active_policy_idx_或is_interrupt_标志; - 调用
reset_policy_runtime()清空新策略的观测段、输入缓冲区、motion_frame计数器,并将is_first_frame置为true,使下一帧推理时update_stacked_obs()以相同观测填满整个历史窗口; - 恢复
is_running为true。
这意味着切换瞬间会有约 dt * decimation 的停顿,随后新策略以"满窗口重复首帧"的方式平滑启动。
Sources: ros_interface.cpp, inference_node.cpp
自定义策略配置指南
若需为新的 ONNX 模型新增一套 YAML 配置,建议按以下流程操作:
flowchart LR
A[确认模型输入尺寸] --> B[编写观测布局字符串]
B --> C[确定 frame_stack 与 stack_order]
C --> D[复制并修改 YAML]
D --> E[修改 launch 文件指向新配置]
E --> F[启动验证 ONNX 尺寸匹配]
F --> G[运行时测试策略切换]
步骤详解:
- 确认模型输入尺寸:查看 ONNX 模型的输入张量形状,计算
obs_num * frame_stack + extra_obs_num应等于该尺寸。 - 编写观测布局:根据训练时的
obs_buf拼接顺序,选择对应的观测源并计算尺寸。注意dof_pos、dof_vel、last_action、motion_pos、motion_vel的尺寸均为joint_num(当前为 23)。 - 确定帧堆叠:若模型训练时使用了历史帧堆叠,则
frame_stack应等于训练时的num_hist;obs_stack_orders必须与训练时的张量展开顺序一致(通常是frame_major)。 - 复制模板:以
inference.yaml或最接近的场景配置为模板,修改model_names、obs_layouts、frame_stacks等字段。若涉及 motion,将.npz文件放入motions/目录并在motion_names中注册。 - 修改 Launch 文件:在
inference.launch.py中替换configs列表中的 YAML 路径,或追加新路径实现参数覆盖。 - 启动验证:首次启动时观察日志中的
policy N: ...与ONNX input size mismatch信息。若校验通过,节点将正常进入等待状态。
Sources: launch/inference.launch.py, inference_node.hpp
常见配置错误排查
| 错误日志 | 根因 | 修复方法 |
|---|---|---|
model_names must contain at least one policy |
model_names 数组为空 |
检查 YAML 缩进,确保参数位于 ros__parameters 下 |
obs_layouts must have the same size as model_names |
策略数与各 per-policy 列表长度不一致 | 核对 obs_layouts、frame_stacks、obs_stack_orders 的元素个数 |
Unsupported obs source: xxx |
观测字段名拼写错误或使用了未注册的源 | 对照"可用观测源注册表"检查字段名,确保使用 name:size 格式 |
ONNX input size mismatch ... |
配置计算的总输入维度与 ONNX 模型期望不符 | 重新核算 sum(obs_layout.size) * frame_stack + sum(extra_obs_layout.size) |
Motion file has no frames |
.npz 文件路径错误或文件损坏 |
确认 motion_names 中的文件名存在于 motions/ 目录,且包含有效的 pos/vel 数组 |
Robot fell down! Shutting down... |
机器人姿态超出 gravity_z_upper 阈值 |
若确实处于起身或大幅度动作场景,应将该策略的 gravity_z_upper 放宽至 1.0 左右 |
Sources: ros_interface.cpp, obs_manager.cpp, obs_manager.cpp
下一步阅读指引
如果你希望深入理解观测值在代码层面的组装、归一化与帧堆叠实现细节,建议继续阅读 观测值组装、归一化与帧堆叠。若需了解如何在运行时通过手柄或 ROS 服务触发策略切换,请参阅 多策略运行时与热切换机制。对于新增自定义观测源(如激光雷达或视觉特征)并注册到 YAML 布局中的完整流程,则可参考 新增观测源与模型适配指南。