转载自公众号:敢敢AUTOHUB
0. 摘要
本文深入解析"Hiking in the Wild"这一人形机器人感知跑酷框架的核心技术原理与实现细节。该框架提出了一套可扩展的端到端感知训练方案,使人形机器人能够在野外复杂地形中以高达2.5m/s的速度稳健行进,无需依赖外部状态估计系统。文章将从问题背景、核心方法、代码实现三个维度展开分析,帮助读者全面理解这一前沿技术。
Github主页:https://project-instinct.github.io/hiking-in-the-wild
1. 问题背景与研究动机
1.1 足式机器人运动控制的核心挑战
足式机器人在复杂非结构化环境中的运动控制一直是机器人学领域的核心难题。与轮式机器人不同,足式机器人能够跨越障碍物、穿越不连续地形,但这种能力的实现需要解决两个根本性问题:感知与控制的紧密耦合,以及从被动响应到主动规划的范式转变。
传统的盲走(Blind Locomotion)方法仅依赖本体感知信息,通过接触力反馈实现对草地、碎石等轻微不平整地形的适应。这类方法具有较强的鲁棒性,但其本质是被动响应式的控制策略。当机器人遭遇深沟、高台阶等显著障碍时,由于缺乏前瞻性感知,往往在发生碰撞后才能做出反应,此时已难以避免失稳甚至跌倒。
1.2 现有感知方案的局限性
将外部感知引入运动控制回路是解决上述问题的必然路径,但现有方案存在明显瓶颈:
基于LiDAR的建图方法:这类方法通过激光雷达构建高程图(2.5D Map)或体素网格,为路径规划提供地形信息。然而,LiDAR的更新频率通常较低(10-20Hz),且在高动态运动场景下容易产生运动畸变。更关键的是,这类方法严重依赖精确的状态估计,一旦定位出现漂移,机器人就可能踩空或误判地形。
基于深度图像的重建方法:部分研究尝试利用深度相机重建局部高程图,但这类方法在未见过的野外环境中泛化能力有限,且通常只能支持低速运动。此外,由于高度定制化的传感器配置,相关代码往往难以开源复现。
1.3 本研究的核心贡献
"Hiking in the Wild"框架针对上述问题提出了系统性解决方案:
1. 端到端感知控制:直接将原始深度图像和本体感知映射到关节动作,无需中间地图表示
2. 安全落足机制:通过地形边缘检测与体积点惩罚,隐式学习安全的落足位置选择
3. 抗奖励欺骗策略:基于平坦斑块采样生成可行的导航目标,消除训练中的奖励欺骗现象
4. 零样本迁移部署:通过双向深度对齐实现仿真到真实的零样本迁移
2. 系统架构概览
整个框架采用单阶段强化学习方案,其核心架构如下图所示:
该架构的核心设计理念是:在训练阶段通过丰富的地形课程和安全约束学习鲁棒的运动策略,在部署阶段通过轻量化的ONNX推理实现实时控制。
3. 核心技术详解
3.1 问题建模:部分可观测马尔可夫决策过程
感知人形运动控制问题被建模为部分可观测马尔可夫决策过程(POMDP),这是因为机器人无法直接获取环境的完整状态信息,只能通过传感器获得部分观测。系统采用近端策略优化(PPO)算法进行策略训练,PPO作为一种稳健的Actor-Critic框架,通过限制策略更新幅度来保证训练稳定性。相比于其他策略梯度方法,PPO在高维连续动作空间中表现出色,特别适合人形机器人这类具有大量自由度的复杂系统。
观测空间设计:Actor的观测包含本体感知和外部感知两部分信息,本体感知提供机器人自身状态,外部感知则提供环境地形信息:

为捕获时序依赖关系,系统采用滑动窗口机制保存历史h步的观测数据。训练采用非对称Actor-Critic架构,Critic额外获取无噪声的完整状态信息和基座线速度等特权信息。

3.2 自中心深度图合成
深度图像的高保真合成是实现零样本Sim-to-Real迁移的关键。系统采用NVIDIA Warp框架实现GPU加速的光线投射。

3.3 双向深度对齐机制
为最小化仿真与真实之间的域差距,系统定义了两条变换管线 Fsim 和 Freal,将各自域的原始深度观测映射到统一的感知空间 O 。
仿真管线 Fsim(退化理想深度以模拟物理传感器缺陷):
原始深度 → 裁剪缩放 → 距离相关高斯噪声 → 视差伪影合成 → 高斯模糊 → 归一化 → OOD扰动
各步骤的具体作用:
裁剪缩放:聚焦图像中心的关键特征区域•
距离相关噪声:模拟深度传感器随距离增加的精度衰减•
视差伪影:模拟双目匹配失败产生的无效"白区"•
高斯模糊:模拟光学运动模糊•
OOD扰动:以一定概率用随机噪声替换整帧,增强对瞬时感知失效的鲁棒性
真实管线 Freal(修复物理传感器缺陷):
原始深度 → 裁剪缩放 → 深度修复(Inpainting) → 高斯模糊
深度修复算法用于恢复因遮挡或视差阴影产生的"黑区"(零值像素)。
3.4 跨步时序采样策略
高速运动场景下,单帧深度图难以提供足够的时序上下文。系统采用跨步采样策略构建深度历史缓冲区:
$$ H_t = \left\{ I_{t-k\cdot \ell} \mid k = 0,1,\dots,m-1 \right\} $$
其中 m 为帧数,l 为时间步长。这种稀疏采样方式在不增加计算负担的前提下,有效扩展了时间感受野至 (m-1) · l 步,使策略能够捕捉地形变化趋势和机器人相对速度信息。
4. 安全落足机制
4.1 问题分析
人形机器人的脚掌面积远大于四足机器人,这使得落足位置的精确性变得尤为关键。与四足机器人的点接触不同,人形机器人的脚掌需要与地面形成稳定的面接触才能保持平衡。当脚掌中心踩在楼梯边缘时,由于支撑面积不足,极易发生打滑或翻倒。传统基于模型的规划器虽能生成精确落足点,但对地图误差极为敏感,一旦高程图存在厘米级的偏差,就可能导致规划的落足点实际上位于悬空区域。此外,传统方法通常需要预先定义离散的落足点候选集,这在连续变化的野外地形中难以适用。
4.2 地形边缘检测算法
系统通过比较相邻三角面片的二面角来识别尖锐地形边缘。以下是项目中实际的边缘检测实现:
class EdgeCylinder(VirtualObstacleBase):
"""基于二面角的边缘检测器基类"""
def __init__(self, cfg: EdgeCylinderCfg):
self.cfg: EdgeCylinderCfg = cfg
self.angle_threshold = cfg.angle_threshold
def generate(self, mesh: trimesh.Trimesh, device="cpu") -> None:
"""检测网格中的尖锐边缘并存储为虚拟障碍物"""
# 1. 获取所有相邻面片的二面角
angles = mesh.face_adjacency_angles
threshold = np.deg2rad(self.angle_threshold)
# 2. 筛选超过阈值的尖锐边缘
sharp_mask = angles > threshold
if not np.any(sharp_mask):
edge_end_points = np.empty((0, 6), dtype=np.float32)
print("[WARNING] No sharp edges detected.")
else:
# 获取尖锐边缘的顶点索引
sharp_edges = mesh.face_adjacency_edges[sharp_mask]
v = mesh.vertices
# 构建边缘端点坐标数组 (num_edges, 6): [x0,y0,z0, x1,y1,z1]
edge_coords = np.hstack([v[sharp_edges[:, 0]], v[sharp_edges[:, 1]]])
edge_end_points = self.process_edges(edge_coords)
# 3. 创建圆柱体空间网格用于穿透检测
if edge_end_points.size > 0:
self.cylinders = CylinderSpatialGrid(
cylinders=np.concatenate([
edge_end_points,
np.ones_like(edge_end_points[:, :1]) * self.cfg.cylinder_radius,
], axis=1),
num_grid_cells=self.cfg.num_grid_cells,
device=self.device,
)
项目提供了多种边缘处理策略,包括基于Plücker坐标的共线边缘合并、RANSAC线段拟合、以及贪婪拼接策略,可根据地形复杂度选择合适的算法。
4.3 体积点穿透惩罚
在机器人脚掌碰撞体内部分布一组体积点 P,利用NVIDIA Warp进行大规模并行距离查询,计算每个点相对于空间碰撞网格的穿透深度。
惩罚奖励的计算公式为:
$$ r_{\mathrm{vol}} = -\sum \left\| d_i \right\| \cdot \left( \left\| v_i \right\| + \varepsilon \right) $$
其中 di 为点 i 的穿透偏移量,vi 为该点在世界坐标系下的线速度,$ \varepsilon = 10^{-3} $ 为数值稳定常数。
这一设计的关键洞察在于:高速撞击或刮擦边缘的行为会受到更重的惩罚,从而引导策略学习选择稳定的落足位置。
4.4 代码实现:体积点传感器配置
以下是项目中体积点传感器的实际配置代码:
# 体积点传感器配置
leg_volume_points = VolumePointsCfg(
prim_path="{ENV_REGEX_NS}/Robot/.*_ankle_roll_link",
points_generator=Grid3dPointsGeneratorCfg(
x_min=-0.025, # 脚掌后端
x_max=0.12, # 脚掌前端
x_num=10, # X方向采样点数
y_min=-0.03, # 脚掌左侧
y_max=0.03, # 脚掌右侧
y_num=5, # Y方向采样点数
z_min=-0.04, # 脚底
z_max=0.0, # 脚面
z_num=2, # Z方向采样点数
),
debug_vis=False,
)
该配置在每只脚掌内生成10×5×2=100个体积点,覆盖脚掌的主要接触区域。
5. 抗奖励欺骗的速度指令生成
5.1 奖励欺骗问题
在强化学习训练中,当使用均匀采样的随机速度指令时,智能体往往会学习到"原地转圈"等投机行为来获取奖励,而非真正穿越障碍物。这种现象被称为"奖励欺骗"(Reward Hacking),是强化学习领域的经典难题。具体而言,如果速度指令是随机生成的,智能体可能发现只需要在原地小幅移动或旋转,就能在统计意义上满足速度跟踪奖励的要求,而无需冒险穿越困难地形。这种行为虽然在数学上最大化了累积奖励,但完全违背了训练的初衷——让机器人学会穿越复杂地形。更糟糕的是,这种投机策略一旦形成,很难通过简单调整奖励权重来纠正。
5.2 平坦斑块采样算法
系统在地形网格上预先识别"平坦斑块"作为可达的导航目标。该功能通过 IsaacLab 框架的 FlatPatchSamplingCfg 配置类实现,以下是项目中的实际配置代码:
from isaaclab.terrains import FlatPatchSamplingCfg, TerrainGeneratorCfg
# 在地形配置中定义平坦斑块采样
"pyramid_stairs": terrain_gen.PerlinPyramidStairsTerrainCfg(
proportion=0.15,
step_height_range=(0.05, 0.23),
step_width=0.3,
platform_width=2.5,
# ... 其他地形参数 ...
flat_patch_sampling={
"target": FlatPatchSamplingCfg(
num_patches=50, # 采样50个平坦斑块
patch_radius=[0.05, 0.10, 0.15, 0.20], # 多尺度半径检测
max_height_diff=0.05, # 最大高度差5cm
x_range=(3.7, 3.7), # X方向采样范围
y_range=(-0.0, 0.0), # Y方向采样范围
),
},
),
FlatPatchSamplingCfg 的核心参数说明:•
num_patches: 每个地形块采样的平坦斑块数量•
patch_radius: 用于检测平坦性的多尺度半径列表,系统会在不同半径下验证平坦性•
max_height_diff: 斑块内允许的最大高度差,超过此阈值则认为不平坦•
x_range/y_range: 限制采样的空间范围,用于引导目标点分布
该配置确保目标点位于稳定的平坦地面上,而非陡坡或不可达区域。
5.3 基于位置的速度指令生成
选定目标点后,系统根据目标相对位置动态生成速度指令:
# 速度指令计算
def compute_velocity_command(target_pos_base_frame, k_v, k_omega, v_max, omega_max):
"""
输入: 目标在机体坐标系下的位置(x_g, y_g)
输出: 线速度v_x, 角速度ω_z
"""
x_g, y_g = target_pos_base_frame
# 线速度:与目标距离成正比
v_x = clip(k_v * x_g, 0, v_max)
# 角速度:与目标方位角成正比
heading_error = atan2(y_g, x_g)
omega_z = clip(k_omega * heading_error, -omega_max, omega_max)
return v_x, omega_z
由于使用前向相机,系统仅关注前向运动和航向对齐,侧向速度指令 设为零。
5.4 代码实现:速度指令生成器
以下是项目中速度指令生成的核心实现:
def _update_command(self):
"""根据目标位置更新速度指令"""
# 计算目标相对位置
target_vec = self.pos_command_w - self.robot.data.root_pos_w[:, :3]
target_dist = torch.norm(target_vec[:, :2], dim=1)
# 转换到机体坐标系
self.pos_command_b[:] = quat_rotate_inverse(
yaw_quat(self.robot.data.root_quat_w), target_vec
)
# 计算线速度指令(与距离成正比)
self.vel_command_b[:, :2] = (
self.pos_command_b[:, :2] * self.cfg.velocity_control_stiffness
)
# 计算航向误差
target_direction = torch.atan2(target_vec[:, 1], target_vec[:, 0])
self.heading_command_w = wrap_to_pi(
target_direction - self.robot.data.heading_w
)
# 计算角速度指令
self.vel_command_b[:, 2] = (
self.heading_command_w * self.cfg.heading_control_stiffness
)
# 速度限幅
self.vel_command_b[:, 0] = torch.clamp(
self.vel_command_b[:, 0], min=0, max=self.max_command_b[:, 0]
)
# 距离阈值判断:目标过近时停止
self.vel_command_b[:] *= (
target_dist > self.cfg.target_dis_threshold
).unsqueeze(-1)
6. 对抗运动先验(AMP)
6.1 动机与原理
为使机器人运动更加自然流畅,系统引入对抗运动先验(Adversarial Motion Priors)框架。该框架通过训练一个判别器来区分策略生成的运动与参考运动数据,从而引导策略输出符合物理规律且像人一样的自然步态。传统的强化学习方法往往产生机械、僵硬的运动模式,虽然能够完成任务但缺乏美感和效率。AMP框架借鉴了生成对抗网络(GAN)的思想,将参考运动数据作为"真实样本",将策略生成的运动作为"生成样本",通过对抗训练使策略逐渐学会模仿参考运动的风格特征。这种方法的优势在于无需手工设计复杂的步态奖励函数,判别器会自动学习区分自然与非自然运动的特征。
6.2 参考数据集构成
参考数据集D以50Hz频率采集,来源于三个渠道:
| 数据来源 | 内容 | 时长 | 用途 |
|---|---|---|---|
| MPC合成数据 | 稳定行走模式 | - | 提供基础稳定性 |
| 人体动捕数据 | 上下台阶、攀爬平台 | 379.62s | 复杂动作参考 |
| LAFAN数据集 | 高速奔跑动作 | 1.54s | 高动态运动参考 |
为避免"模式坍塌"问题,行走和奔跑策略使用不同数据集分别训练。
6.3 判别器训练
判别器采用最小二乘损失进行训练:
$$ L_D = \mathbb{E}_M\left[\left(D(S)-1\right)^2\right] + \mathbb{E}_P\left[\left(D(S)+1\right)^2\right] $$
其中 M 为参考运动数据集,P 为当前策略生成的运动。风格奖励计算为:
$$ r_t = \max\left(0,\, 1 - 0.25\left(D(S_t)-1\right)^2\right) $$
相比二元交叉熵损失,MSE损失与二次奖励的组合提供更平滑的梯度,避免梯度消失问题。
7. 奖励函数设计
7.1 奖励函数分类
总奖励 R 由四个主要部分组成:
$$ R = r_{\mathrm{task}} + r_{\mathrm{reg}} + r_{\mathrm{safe}} + r_{\mathrm{amp}} $$
各部分的具体作用如下表所示:
| 类别 | 奖励项 | 权重 | 作用 |
|---|---|---|---|
| 任务奖励 | track_lin_vel_xy_exp | 2.0 | 线速度跟踪 |
| track_ang_vel_z_exp | 2.0 | 角速度跟踪 | |
| is_alive | 3.0 | 存活奖励 | |
| 正则化 | volume_points_penetration | -4.0 | 边缘穿透惩罚 |
| feet_air_time | 0.5 | 鼓励抬腿 | |
| feet_slide | -0.4 | 惩罚脚滑 | |
| action_rate_l2 | -0.005 | 动作平滑性 | |
| 安全奖励 | dof_pos_limits | -1.0 | 关节位置限制 |
| undesired_contacts | -1.0 | 非法接触惩罚 | |
| AMP奖励 | style_reward | - | 运动自然性 |
7.2 关键奖励函数实现
以下是项目中几个关键奖励函数的实现:
def feet_air_time(env, command_name, vel_threshold, sensor_cfg):
"""奖励双足机器人的长步态"""
contact_sensor = env.scene.sensors[sensor_cfg.name]
# 获取空中时间和接触时间
air_time = contact_sensor.data.current_air_time[:, sensor_cfg.body_ids]
contact_time = contact_sensor.data.current_contact_time[:, sensor_cfg.body_ids]
# 判断是否处于单腿支撑状态
in_contact = contact_time > 0.0
in_mode_time = torch.where(in_contact, contact_time, air_time)
single_stance = torch.sum(in_contact.int(), dim=1) == 1
# 计算奖励:仅在单腿支撑时给予奖励
reward = torch.min(
torch.where(single_stance.unsqueeze(-1), in_mode_time, 0.0), dim=1
)[0]
# 零速度指令时不给奖励
reward *= torch.norm(
env.command_manager.get_command(command_name)[:, :2], dim=1
) > vel_threshold
return reward
8. 结语
"Hiking in the Wild"框架代表了人形机器人感知运动控制领域的重要进展。通过端到端的学习范式、创新的安全机制设计以及高效的部署架构,该框架成功实现了人形机器人在野外复杂地形中的高速稳健行进。与传统的分层控制架构相比,该框架避免了感知、规划、控制各模块之间的信息损失和延迟累积,使机器人能够以更快的速度响应环境变化。这一工作不仅在学术上具有重要价值,也为人形机器人的实际应用场景(如搜救、巡检、物流配送等)奠定了技术基础。
411