English | 简体中文
基于 NVIDIA Isaac Lab 框架的四足机器人 Legbot 强化学习训练与 Sim2Real 部署项目
sim2sim:
qla6e-jd3tq.mov
sim2real:
forward-left-right.mov
Legbot Lab 是一个完整的四足机器人强化学习(RL)训练与部署框架,目标是从仿真中训练出高性能的运动控制策略,并将其无缝部署到真实机器人上(Sim2Real)。
核心特点:
- 完整的 Sim2Real 闭环:训练 → ONNX 导出 → C++ 控制器部署 → 实物运行
- PPO 强化学习:基于 RSL-RL 实现的 Proximal Policy Optimization 算法
- DDS 通信架构:控制器与硬件解耦,仿真与实物共用同一套 C++ 控制代码
- 丰富的域随机化:质量、惯性、摩擦、PD 增益、外力扰动等多维度随机化
- 安全保护机制:多级限幅、超限保护、安全状态机(FSM)
- 4096 并行环境:GPU 加速训练,充分利用 Isaac Sim 的并行仿真能力
本项目使用 Proximal Policy Optimization (PPO) 算法训练四足机器人的运动控制策略。PPO 由 OpenAI 于 2017 年提出,是一种基于策略梯度的 Actor-Critic 强化学习算法。
PPO 的核心思想是通过裁剪策略更新幅度来限制新旧策略之间的差异,从而稳定训练过程,同时保持较高的样本效率。
PPO 通过重要性采样比率衡量新旧策略对同一动作的概率变化:
裁剪代理目标为:
其中:
-
$r_t(\theta)$ 为重要性采样比率; -
$\hat{A}_t$ 为优势函数估计; -
$\epsilon$ 为裁剪参数,本项目设置为 0.2。
为避免与重要性采样比率
TD 残差为:
广义优势估计为:
其中:
-
$\delta_t$ 为 TD 残差; -
$\gamma$ 为折扣因子,本项目设置为 0.99; -
$\lambda$ 为 GAE 参数,本项目设置为 0.95。
价值网络使用目标价值作为监督信号:
价值函数损失为:
从最大化目标的角度,PPO 的组合目标可写为:
实际程序通常采用梯度下降,因此最小化的总损失写为:
其中,策略熵用于增加策略的探索能力。本项目的相关系数设置如下:
- 价值损失系数:c₁ = 1.0;
- 熵系数:c₂ = 0.01。
PPO 根据实际 KL 散度与目标 KL 散度之间的关系动态调整学习率。
当实际 KL 散度过大时,降低学习率:
当实际 KL 散度过小时,提高学习率:
其余情况下,保持当前学习率不变:
本项目的目标 KL 散度设置为 0.01。其中,αmin 和 αmax 分别表示学习率下限和学习率上限。
策略输出关节期望位置,并通过 PD 控制器计算关节力矩:
训练仿真中的执行器增益设置为:
- Kp = 50 N·m/rad;
- Kd = 3.0 N·m·s/rad。
实物部署的 FixStand 状态采用:
- Kp = 60 N·m/rad;
- Kd = 4.0 N·m·s/rad。
不同运行状态下的 PD 增益,应分别以训练配置文件和部署配置文件为准。
首先对高斯策略网络输出的动作进行逐元素裁剪:
随后将裁剪后的动作映射为关节期望位置:
其中,a 为 12 维高斯策略网络原始输出。环境首先将各维动作裁剪到 [-100, 100],随后乘以 0.25 rad,并叠加到默认关节位置。因此,策略输出并未通过 tanh 函数硬限制在 [-1, 1]。
默认站立姿态设置如下:
- Hip 关节:0.0 rad;
- Thigh 关节:0.9 rad;
- Calf 关节:-1.8 rad。
| 组件 | 架构 |
|---|---|
| Actor(策略网络) | MLP:[actor_obs_dim] → 512 → 256 → 128 → 12,ELU 激活 |
| Critic(价值网络) | MLP:[critic_obs_dim] → 512 → 256 → 128 → 1,ELU 激活 |
| 策略分布 | 高斯策略,初始动作噪声标准差为 1.0 |
单帧策略观测维度为 45。配置设置了 history_length=10、concatenate_terms=True 和 flatten_history_dim=True,因此 10 帧历史观测会被展平拼接,Actor 的实际输入维度为
legbot_lab/
├── README.md # 项目文档(中文)
├── README_EN.md # 项目文档(英文)
├── env_cfg.py # 主环境配置(4096并行环境PPO训练)
├── legbot_rl_lab/ # 核心 RL 训练与部署系统
│ ├── source/unitree_rl_lab/ # Python 训练源码(基于 Isaac Lab)
│ │ └── unitree_rl_lab/
│ │ ├── assets/robots/ # Legbot 机器人配置
│ │ │ ├── unitree.py # UNITREE_LEGBOT_CFG
│ │ │ └── unitree_actuators.py # 执行器 T-N 曲线模型
│ │ ├── tasks/locomotion/
│ │ │ ├── agents/ # PPO 算法配置
│ │ │ ├── mdp/ # MDP 组件(奖励/观测/域随机化/命令)
│ │ │ └── robots/legbot/ # Legbot 环境配置
│ │ └── utils/ # 工具函数(部署配置导出)
│ ├── scripts/ # 训练/推理/测试脚本
│ │ └── rsl_rl/
│ │ ├── train.py # 训练入口(headless 模式)
│ │ ├── play.py # 推理/ONNX 导出
│ │ └── cli_args.py # 命令行参数
│ ├── deploy/ # C++ 部署代码(Sim2Real)
│ │ ├── include/
│ │ │ ├── FSM/ # 有限状态机
│ │ │ │ ├── CtrlFSM.h # 1 kHz 主状态机
│ │ │ │ ├── State_FixStand.h # 站姿状态
│ │ │ │ ├── State_Passive.h # 被动阻尼状态
│ │ │ │ └── State_RLBase.h # RL 策略运行状态
│ │ │ ├── deploy_safety.h # 安全保护(力矩/温度/姿态超限)
│ │ │ ├── deploy_csv_logger.h # 50 Hz CSV 诊断日志
│ │ │ └── param.h # 命令行参数解析
│ │ └── robots/legbot/
│ │ ├── config/config.yaml # FSM 配置 & 安全参数
│ │ ├── include/Types.h # DDS 接口定义
│ │ ├── main.cpp # 控制器入口
│ │ └── src/State_RLBase.cpp # ONNX Runtime 推理
│ ├── unitree_ros/robots/legbot_description/ # URDF & MJCF 模型
│ └── logs/rsl_rl/unitree_legbot_velocity/ # 训练日志 & 模型
├── simulate/ # MuJoCo DDS 仿真器
│ ├── src/
│ │ ├── main.cc # 仿真主循环
│ │ ├── legbot_bridge.h # DDS↔MuJoCo 桥接
│ │ └── physics_joystick.h # 手柄/键盘驱动
│ └── config.yaml # 仿真配置
├── serial_dds_gateway/ # 串口⇔DDS 硬件网关
│ ├── src/legbot_rt_gait_pd.cpp # 网关主程序(500 Hz)
│ ├── include/ # IMU 帧解析/电机协议
│ └── start_gateway.sh # 一键启动脚本
├── legbot/ # NumPy 训练环境(独立导航任务)
│ ├── cfg.py # 场景配置
│ ├── legbot_section001_np.py # 训练入口
│ └── xmls/
│ ├── legbot.xml # Legbot MJCF 定义
│ ├── scene_stairs.xml # 楼梯场景
│ └── scene_world.xml # 完整赛道场景
├── terrain_tool/ # 地形生成工具
└── unitree_sdk2/ # 宇树 DDS 通信库
Legbot 机器人 URDF 模型定义文件位于 legbot_rl_lab/unitree_ros/robots/legbot_description/urdf/legbot_description.urdf。
| 部件 | 质量 (kg) | 数量 | 总质量 (kg) |
|---|---|---|---|
| 基座 (base) | 6.584 | 1 | 6.584 |
| 髋关节 (hip) | 0.080 | 4 | 0.319 |
| 大腿 (thigh) | 1.550 | 4 | 6.202 |
| 小腿 (calf) | 0.184 | 4 | 0.736 |
| 足部 (foot) | 0.040 | 4 | 0.160 |
| 总计 | ~14.0 |
| 参数 | 值 |
|---|---|
| 机器人名称 | Legbot |
| 电机型号 | RobStride RS02(12 个) |
| 自由度 | 12(四足 × 髋/大腿/小腿) |
| 基座高度(站立) | 0.28 m(质心高度 0.277 m) |
| 总质量 | ~14.0 kg |
| 基座质量 | 6.584 kg |
| 大腿长度 | 0.1985 m |
| 小腿长度 | 0.214 m |
| 足端半径 | 0.021 m |
| 关节 | 峰值力矩 | 峰值速度 | 减速比 | 备注 |
|---|---|---|---|---|
| 髋关节 (hip) | ±16 N·m | 30 rad/s | 1:1 | |
| 大腿关节 (thigh) | ±16 N·m | 30 rad/s | 1:1 | |
| 小腿关节 (calf) | ±32 N·m | 15.7 rad/s | 1:2 | 减速比 1:2,模型空间角度转换电机空间需乘 2 |
| 关节 | 下限 (rad) | 上限 (rad) |
|---|---|---|
| FR/FL Hip | -0.733 | 0.733 |
| FR/FL Thigh | -1.559 | 3.130 |
| RR/RL Hip | -0.733 | 0.733 |
| RR/RL Thigh | -0.512 | 4.177 |
| 所有 Calf | -2.639 | -0.785 |
| 参数 | 值 |
|---|---|
| 板载电脑 | Orange Pi 6 (aarch64) |
| 传感器 | 自制串口 IMU(加速度 + 陀螺仪) |
| 通信总线 | USB-CAN × 2 + USB-串口(IMU) |
| 控制频率 | 1 kHz(控制器)/ 500 Hz(网关) |
| 通信中间件 | CycloneDDS(本机 lo 回环) |
严格按照 Isaac Lab 官方指南 完成安装。
# 克隆仓库
git clone https://github.com/Robot-Nav/legbot_lab.git
cd legbot_lab
# 激活 Isaac Lab 的 conda 环境
conda activate env_isaaclab
# 安装本项目为 editable 模式
pip install -e legbot_rl_lab/source/unitree_rl_labpython legbot_rl_lab/scripts/rsl_rl/train.py \
--task Unitree-Legbot-Velocity \
--headless \
--num_envs 4096 \
--max_iterations 50000说明:PPO 配置文件中的默认最大迭代次数为 100000;上述命令通过
--max_iterations 50000将本次训练覆盖为 50000 次迭代。
命令行参数:
| 参数 | 说明 | 默认值 |
|---|---|---|
--task |
训练任务名称 | Unitree-Legbot-Velocity |
--headless |
无渲染模式(加速训练) | False |
--num_envs |
并行环境数量 | 4096 |
--max_iterations |
最大训练迭代次数 | 配置默认 100000;示例命令覆盖为 50000 |
--seed |
随机种子 | 随机 |
--resume |
从断点恢复训练 | False |
--load_run |
恢复时指定运行目录 | - |
--checkpoint |
恢复时指定模型文件名 | - |
# 自动查找最新检查点恢复
python legbot_rl_lab/scripts/rsl_rl/train.py \
--task Unitree-Legbot-Velocity \
--resume \
--headless
# 手动指定实验目录和检查点
python legbot_rl_lab/scripts/rsl_rl/train.py \
--task Unitree-Legbot-Velocity \
--resume \
--load_run 2026-06-25_17-11-16 \
--checkpoint model_108.pt \
--headlesspython legbot/legbot_section001_np.py# 运行推理并自动导出 ONNX
python legbot_rl_lab/scripts/rsl_rl/play.py --task Unitree-Legbot-Velocity推理时自动导出以下文件到 logs/rsl_rl/unitree_legbot_velocity/<run_name>/exported/:
policy.pt— TorchScript 模型(Python 推理)policy.onnx— ONNX 模型(C++ 部署)deploy.yaml— 部署配置(关节映射、缩放因子等)
┌────────────────────┐ DDS (rt/lowcmd, rt/lowstate) ┌──────────────────────┐ 串口 ┌──────────────┐
│ legbot_ctrl │◄──────────────────────────────►│ serial_dds_gateway │◄────────►│ 12电机 + IMU │
│ (RL策略控制器) │ CycloneDDS │ (DDS↔串口协议网关) │ type1-4 │ (凌足USB-CAN)│
└────────────────────┘ └──────────────────────┘ └──────────────┘
│ │
运行在香橙派上 运行在香橙派上
1 kHz 控制循环 500 Hz 协议转换
└──────────── 共享 lo 网卡 ────────────┘
核心设计思想:控制器代码在仿真和实物上完全一致,DDS 抽象层使得切换只需更换通信对端(MuJoCo 仿真器 或 serial_dds_gateway 硬件网关)。
cd serial_dds_gateway
mkdir -p build && cd build
cmake -S .. -B .
cmake --build . -j$(nproc)cd legbot_rl_lab/deploy/robots/legbot
mkdir -p build && cd build
cmake -S .. -B .
cmake --build . -j$(nproc)终端 1 — 启动网关(必须先启动):
cd serial_dds_gateway
./build/dds_to_serial_gateway \
--serial-port-a /dev/myttyCAN0 \
--serial-port-b /dev/myttyCAN1 \
--imu-port /dev/myttyIMU \
--network lo \
--tick-hz 500 \
--joint-bias-load-file config/joint_prone_bias.fatu.txt \
--send-disable-on-exit终端 2 — 启动控制器(网关就绪后):
cd legbot_rl_lab/deploy/robots/legbot
./build/legbot_ctrl --network loPassive(阻尼模式)──LT+A──► FixStand(站姿)──Start──► Velocity(RL接管)
▲ ▲ │
│ │ │
└──────── LT+B ──────────┴────────── LT+B ─────────────┘
| 观测项 | 维度 | 缩放 | 噪声 |
|---|---|---|---|
| 基座角速度(IMU 陀螺仪) | 3 | 0.25 | U(-0.2, 0.2) |
| 投影重力方向 | 3 | 1.0 | U(-0.05, 0.05) |
| 速度命令 | 3 | 1.0 | - |
| 关节位置(相对默认值) | 12 | 1.0 | U(-0.03, 0.03) |
| 关节速度 | 12 | 0.05 | U(-2.0, 2.0) |
| 上一动作 | 12 | 1.0 | - |
| 总计 | 45 |
单帧策略观测共 45 维。配置设置了 10 帧历史观测,并将历史维展平拼接,因此 Actor 的实际输入维度为 450 维。
| 参数 | 值 |
|---|---|
| 控制方式 | 关节位置控制 (PD) |
| 策略原始输出 |
|
| 环境动作裁剪 | |
| 动作缩放 | ×0.25 rad |
| 训练执行器刚度 |
50 N·m/rad |
| 训练执行器阻尼 |
3.0 N·m·s/rad |
| FixStand 部署增益 |
|
| 默认髋角 (hip) | 0.0 rad |
| 默认大腿角 (thigh) | 0.9 rad |
| 默认小腿角 (calf) | -1.8 rad |
Critic 网络可观测到策略无法获取的信息(Asymmetric Actor-Critic):
| 特权信息 | 维度 | 说明 |
|---|---|---|
| 基座线速度 | 3 | 真实速度值 |
| 关节加速度 | 12 | 加速度值 |
| 关节力矩 | 12 | 真实力矩 |
| 足部接触力 | 4 | 四足法向力 |
| 高度扫描(大范围) | 187 | 1.6×1.0m 地形高度图 |
Critic 使用当前单帧的 45 维基础观测,并额外加入 218 维特权信息,因此 Critic 总输入维度为 263 维;Critic 不使用 Actor 的 10 帧历史拼接。
| 条件 | 阈值 |
|---|---|
| 超时 | Episode 达到 25 秒 |
| 摔倒 | 基座接触力 > 1.0 N |
训练中的总奖励为以下各项的加权和。正奖励鼓励跟踪速度命令,负奖励惩罚不期望的运动模式。
| 奖励项 | 权重 | 公式 | 说明 |
|---|---|---|---|
| 线速度跟踪 | +1.0 | 线速度跟踪指数奖励 | |
| 角速度跟踪 | +0.5 | 偏航角速度跟踪指数奖励 |
| 惩罚项 | 权重 | 说明 |
|---|---|---|
| 垂直线速度 L2 | -2.0 | 防止上下晃动 |
| 水平角速度 L2 | -0.05 | 抑制 roll/pitch |
| 基座高度偏差 L2 | -1.0 ~ -10.0 | 课程学习,保持 0.28 m 目标高度 |
| 关节加速度 L2 | -1e-7 | 鼓励平滑运动 |
| 关节功率 | -2e-5 | 降低能耗 |
| 关节力矩 L2 | -1e-4 | 避免过大扭矩 |
| 动作变化率 L2 | -0.01 | 鼓励平滑控制 |
| 动作平滑性 L2 | -0.01 | 三阶导数惩罚 |
| 不期望接触 | -1.0 | 大腿/小腿不应触地(阈值 5 N) |
| 关节限位 | -2.0 | 避免超出关节范围 |
| 足部调节 | -0.05 | 足部高度和间距约束 |
| Hip 位置 L1 | -0.05 | 静止时 Hip 保持 0 附近 |
| 关节位置 L1 | -0.01 | 静止时保持默认姿态 |
| 课程项 | 起始值 | 终值 | 迭代区间 |
|---|---|---|---|
| 垂直速度惩罚权重 | -2.0 | 0.0 | 0 → 1,500 |
| 高度惩罚权重 | -1.0 | -10.0 | 0 → 5,000 |
| 地形难度 | Level 0 | Level 5 | 随进度递增 |
为了使策略从仿真迁移到真实机器人(Sim2Real),训练过程中引入大量域随机化。所有参数在每次训练启动、每次 Episode 重置或按固定间隔进行随机扰动。
模式:
startup(训练开始时随机化一次,整个训练过程保持不变)
| 参数 | 分布 | 操作 | 说明 |
|---|---|---|---|
| 基座质量 | U(-1.0, 1.0) kg | 加法 | 模拟负载变化(±1kg) |
| 非基座部件质量 | U(0.9, 1.1) | 乘法 | 连杆质量 ±10% |
| 转动惯量 | U(0.9, 1.1) | 乘法 | 所有部件 ±10% |
| 质心位置 | U(-0.05, 0.05) m | 偏移 | base 质心 ±5 cm(x/y/z) |
模式:
startup
| 参数 | 范围 | 说明 |
|---|---|---|
| 静摩擦系数 | U(0.0, 2.0) | 64 种离散摩擦组合 |
| 动摩擦系数 | U(0.0, 2.0) | 与静摩擦一致绑定 |
| 恢复系数 | U(0.0, 0.5) | 碰撞弹性 |
模式:
reset(每次 Episode 重置时随机化)
| 参数 | 分布 | 说明 |
|---|---|---|
| PD 刚度 Kp | U(0.9, 1.1) | 乘法,模拟电机差异 |
| PD 阻尼 Kd | U(0.9, 1.1) | 乘法 |
| 关节零位偏移 | U(-0.035, 0.035) rad | ±35 mrad ≈ ±2°,模拟编码器误差 |
模式:
reset
| 参数 | 分布 | 说明 |
|---|---|---|
| 初始关节位置 | U(0.5, 1.5) × 默认值 | 50%~150% 默认姿态 |
| 基座位置 | U(-0.5, 0.5) m (x/y) | 随机水平偏移 |
| 基座高度 | U(0.0, 0.2) m 偏移 | 随机初始高度 |
| 基座偏航 | U(-π, π) | 全方向随机朝向 |
| 基座线速度 | U(-0.5, 0.5) m/s | 随机初始速度 |
| 基座角速度 | U(-0.5, 0.5) rad/s | 随机初始角速度 |
模式:
interval,每 4 秒触发一次
| 参数 | 分布 | 说明 |
|---|---|---|
| 线速度扰动 (x/y) | U(-0.4, 0.4) m/s | 模拟外力推搡 |
| 角速度扰动 (roll/pitch/yaw) | U(-0.6, 0.6) rad/s | 模拟旋转扰动 |
模式:每步叠加(策略观测中)
| 观测量 | 噪声分布 | 缩放系数 |
|---|---|---|
| 基座角速度 | U(-0.2, 0.2) | 0.25 |
| 投影重力 | U(-0.05, 0.05) | 1.0 |
| 关节位置 | U(-0.03, 0.03) | 1.0 |
| 关节速度 | U(-2.0, 2.0) | 0.05 |
注:Critic 观测不叠加观测噪声(
enable_corruption=False),有助于降低价值估计中的额外随机扰动。
地形使用程序化生成器创建粗糙地形,支持课程学习。随训练进度(train_env_steps),地形难度从 Level 0(平坦)递增到 Level 5(复杂崎岖)。
| 参数 | 值 |
|---|---|
| 算法 | PPO (Proximal Policy Optimization) |
| 学习率 |
|
| 裁剪参数 |
0.2 |
| 折扣因子 |
0.99 |
| GAE 参数 |
0.95 |
| 熵系数 | 0.01 |
| 价值损失系数 | 1.0 |
| 最大梯度范数 | 1.0 |
| 每环境步数 | 24 |
| 学习轮数 | 5 |
| Mini-batch 数量 | 4 |
| 目标 KL 散度 | 0.01 |
| 最大迭代次数 | 100,000(配置默认值) |
| 参数 | 值 |
|---|---|
| 并行环境数 | 4,096 |
| 物理步长 | 0.005 s (200 Hz) |
| 控制降频系数(decimation) | 4(控制频率为 50 Hz) |
| Episode 长度 | 25 s(1,250 控制步) |
| 每次迭代样本数 | 4,096 × 24 = 98,304 |
| 总训练步数上限 | 4,096 × 24 × 100,000 ≈ 9.83 × 10⁹ |
| 参数 | 值 |
|---|---|
| 隐藏层维度 | [512, 256, 128] |
| 激活函数 | ELU |
| 单帧策略观测维度 | 45 |
| 历史长度 | 10 帧 |
| Actor 实际输入维度 | 450(45 × 10 帧,历史维展平拼接) |
| 策略输出维度 | 12(关节位置偏移) |
| Critic 观测维度 | 263 |
| 保护项 | 限值 | 说明 |
|---|---|---|
| Action Clip | ±100 | 策略原始输出裁剪 |
| 关节角度限位 | 可配置 | 硬限位保护 |
| 力矩限幅 | ±40 N·m | 力矩裁剪 |
| 角度变化限幅 | 0.05 rad/tick | 防神经网络突跳 |
| 速度变化限幅 | 1.0 rad/s/tick | 防速度突变 |
| 监测项 | 阈值 | 动作 |
|---|---|---|
| 通信超时 | - | → Passive |
| 关节速度 | 30 rad/s | → Passive |
| 反馈力矩 | 45 N·m | → Passive |
| 电机温度 | 80°C | → Passive |
| IMU Roll | ±0.5 rad (~28°) | → Passive |
| IMU Pitch | ±0.5 rad (~28°) | → Passive |
| 急停标志 | - | → Passive |
| 类别 | 技术 |
|---|---|
| 仿真平台 | NVIDIA Isaac Sim 5.0.0 / Isaac Lab 2.2.0 |
| 物理引擎 | PhysX GPU (4,096 并行) |
| RL 算法 | RSL-RL 2.3.1(PPO) |
| 深度学习 | PyTorch(CUDA 加速) |
| 模型导出 | ONNX Runtime 1.22.0 |
| 部署语言 | C++17 |
| 通信中间件 | CycloneDDS |
| 机器人模型 | URDF / MJCF |
| 配置管理 | Hydra / YAML |
| 仿真验证 | MuJoCo + DDS 桥接 |
| 板载电脑 | Orange Pi 6 (aarch64) |
| 电机 | RobStride RS02 |
本项目由 Robot-Nav 团队开发与维护。
GitHub: https://github.com/Robot-Nav/legbot_lab
MIT License
@misc{schulman2017proximal,
title={Proximal Policy Optimization Algorithms},
author={John Schulman and Filip Wolski and Prafulla Dhariwal and Alec Radford and Oleg Klimov},
year={2017},
eprint={1707.06347},
archivePrefix={arXiv},
primaryClass={cs.LG},
url={https://arxiv.org/abs/1707.06347},
}
@misc{rudin2022advanced,
title={Advanced Skills by Learning Locomotion and Local Navigation End-to-End},
author={Nikhil Rudin and David Hoeller and Marko Bjelonic and Marco Hutter},
year={2022},
eprint={2209.12827},
archivePrefix={arXiv},
primaryClass={cs.RO},
url={https://arxiv.org/abs/2209.12827},
}
感谢 unitree_rl_lab 团队提供的开源训练框架、工程实现与社区贡献。本项目在其相关工作基础上完成了 Legbot 机器人的环境适配、训练配置、仿真验证与 Sim2Real 部署开发,在此表示诚挚感谢。