- Published on
BVH 格式:动作捕捉数据的标准格式
文章
引言
在动作捕捉、3D 动画制作和机器人运动重定向等领域,数据格式的标准化是确保不同系统和软件之间能够无缝协作的关键。从动作捕捉设备采集的原始数据,到最终应用于虚拟角色或机器人的动作指令,需要一个统一、高效、易于解析的数据格式作为桥梁。BVH(BioVision Hierarchy)格式正是这样一个标准,它已经成为动作捕捉和动画制作领域最广泛使用的数据格式之一。
BVH 格式的诞生可以追溯到 20 世纪 90 年代,当时 BioVision 公司开发了这一格式用于存储和交换动作捕捉数据。经过多年的发展和广泛应用,BVH 格式已经成为动作捕捉行业的实际标准,被众多软件和系统支持,包括 3ds Max、Maya、Blender、MotionBuilder 等主流 3D 动画软件,以及各种动作捕捉系统和机器人控制框架。
BVH 格式的核心价值在于其简洁而强大的设计。它采用文本格式存储数据,使得文件易于阅读、编辑和调试。同时,BVH 格式通过层次化的骨骼结构定义和逐帧的运动数据记录,能够精确描述复杂的角色动画。这种设计不仅保证了数据的完整性,还实现了高效的存储和传输,使得大规模动作数据集的创建和共享成为可能。
在机器人学领域,BVH 格式同样发挥着重要作用。随着人形机器人和动作重定向技术的发展,将人类动作数据转换为机器人控制指令的需求日益增长。BVH 格式作为标准的人类动作数据格式,被广泛应用于机器人运动重定向系统,如 GMR(General Motion Retargeting)框架,实现了从动作捕捉数据到机器人动作的无缝转换。
本文将全面深入地探索 BVH 格式的方方面面,从基础概念到文件结构,从技术细节到实际应用,从解析方法到格式转换。无论您是刚开始接触动作捕捉的新手,还是希望深入了解 BVH 格式技术细节的开发者,或是需要在机器人系统中集成 BVH 数据的研究人员,都能从本文中获得有价值的知识和实践指导。
第一部分:BVH 格式基础
什么是 BVH 格式
BVH(BioVision Hierarchy)是一种用于存储和描述 3D 角色骨骼动画数据的文件格式。该格式最初由 BioVision 公司开发,主要用于动作捕捉数据的存储和交换。BVH 格式采用文本格式,以层次化的方式描述角色的骨骼结构,并记录每一帧中各关节的旋转和位移数据。
BVH 格式的核心特点
BVH 格式具有以下核心特点:
文本格式:采用纯文本格式存储,易于阅读、编辑和调试,不依赖特定的二进制解析器。
层次化结构:通过父子关系定义骨骼的层次结构,能够准确描述复杂的角色骨架。
标准化:作为动作捕捉领域的实际标准,被众多软件和系统广泛支持。
高效存储:通过相对旋转而非绝对位置存储数据,实现高效的数据压缩。
易于解析:文件结构清晰,便于编写解析器和转换工具。
跨平台兼容:文本格式确保了跨平台兼容性,可以在任何支持文本处理的系统上使用。
BVH 格式的历史
BVH 格式最初由 BioVision 公司开发,该公司是一家专门从事动作捕捉技术的公司。在 20 世纪 90 年代,动作捕捉技术开始广泛应用于电影、游戏和动画制作,但不同系统之间的数据交换存在困难。BVH 格式的推出解决了这一问题,提供了一个统一的数据交换标准。
随着动作捕捉技术的普及和 3D 动画软件的发展,BVH 格式逐渐被广泛采用。虽然 BioVision 公司后来被其他公司收购,但 BVH 格式作为开放标准继续发展,并被各种软件和工具支持。如今,BVH 格式已经成为动作捕捉和动画制作领域的标准格式之一。
BVH 格式的定位
在动作捕捉和动画数据格式的生态系统中,BVH 格式占据了一个独特的位置:
与 FBX 的关系:FBX 是 Autodesk 开发的更复杂的 3D 模型和动画格式,支持更多功能,但 BVH 格式更轻量级,专注于动作数据。
与 SMPLX 的关系:SMPLX 是参数化的人体模型格式,包含形状和姿态参数,而 BVH 格式专注于骨骼动画数据。
与 Collada 的关系:Collada 是更通用的 3D 内容交换格式,而 BVH 格式专门针对动作捕捉数据进行了优化。
BVH 格式的优势在于其简洁性和专注性,它专门为动作捕捉数据设计,不包含不必要的复杂性,使得数据文件更小、解析更快、使用更简单。
BVH 格式的应用领域
BVH 格式在多个领域都有广泛应用,主要包括:
动作捕捉
动作捕捉是 BVH 格式最主要的应用领域。动作捕捉系统通过传感器或摄像头记录演员或物体的运动,然后将数据保存为 BVH 格式。这些数据可以用于:
电影制作:在电影中创建逼真的角色动画,特别是在需要大量动作场景的电影中。
游戏开发:为游戏角色提供丰富的动作库,增强游戏的真实感和互动性。
虚拟现实:在 VR 应用中驱动虚拟角色的动作,实现用户与虚拟环境的实时交互。
动画制作
在 3D 动画制作流程中,BVH 格式作为动作数据的中间格式,连接动作捕捉和最终动画:
角色动画:将动作捕捉数据应用于 3D 角色模型,创建自然的动画效果。
动作编辑:在动画软件中导入 BVH 文件后,动画师可以对动作进行进一步编辑和优化。
动作库管理:作为标准格式,便于动作库的创建、管理和共享。
机器人学
在机器人学领域,BVH 格式用于将人类动作数据转换为机器人控制指令:
动作重定向:通过 GMR 等框架,将 BVH 格式的人类动作数据重定向到人形机器人。
遥操作:在机器人遥操作系统中,使用 BVH 格式传输动作数据。
动作学习:作为机器人学习人类动作的数据格式,用于模仿学习和强化学习。
研究和教育
BVH 格式也被广泛用于研究和教育:
动作分析:研究人体运动模式,分析运动学特征。
数据集创建:创建大规模的动作数据集,如 LAFAN1 数据集。
算法开发:作为动作处理算法的输入和输出格式。
第二部分:BVH 文件结构详解
BVH 文件整体结构
BVH 文件由两个主要部分组成:HIERARCHY(层次结构)和 MOTION(运动数据)。这种两段式结构使得文件既包含了骨骼的静态定义,也包含了动态的运动信息。
HIERARCHY
[骨骼层次结构定义]
MOTION
[运动数据]
HIERARCHY 部分
HIERARCHY 部分定义了角色的骨骼结构,包括:
- 根关节的定义和初始位置
- 每个关节的名称、类型和层级关系
- 每个关节相对于父关节的偏移量(Offset)
- 每个关节的旋转通道(Channels)定义
MOTION 部分
MOTION 部分包含了实际的动画数据:
- 帧率(Frames)定义
- 总帧数(Frame Time)定义
- 每一帧中所有关节的旋转和位移数据
HIERARCHY 部分详解
根关节(ROOT)
每个 BVH 文件都以根关节开始,根关节定义了整个骨骼系统的根节点:
HIERARCHY
ROOT Hips
{
OFFSET 0.00 0.00 0.00
CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
JOINT LeftHip
{
...
}
JOINT RightHip
{
...
}
}
- ROOT 关键字:标识根关节
- 关节名称:根关节的名称,通常是 "Hips" 或 "Root"
- OFFSET:根关节的初始位置(相对于世界坐标系)
- CHANNELS:根关节的数据通道,通常包括 3 个位置通道和 3 个旋转通道
关节(JOINT)
除了根关节外,其他关节使用 JOINT 关键字定义:
JOINT LeftKnee
{
OFFSET 0.00 0.00 0.00
CHANNELS 3 Zrotation Xrotation Yrotation
JOINT LeftAnkle
{
...
}
End Site
{
OFFSET 0.00 0.00 0.00
}
}
- JOINT 关键字:标识普通关节
- OFFSET:关节相对于父关节的偏移量(局部坐标系)
- CHANNELS:关节的数据通道,通常只包含旋转通道
- 嵌套结构:子关节定义在父关节的大括号内
末端位置(End Site)
End Site 定义了骨骼链的末端位置,通常用于标记没有子关节的关节:
End Site
{
OFFSET 0.00 0.00 0.00
}
- End Site 关键字:标识骨骼链的末端
- OFFSET:末端相对于父关节的偏移量
OFFSET 参数
OFFSET 定义了关节相对于父关节的位置偏移:
- 格式:
OFFSET x y z - 单位:通常以厘米为单位
- 坐标系:使用右手坐标系,Y 轴通常向上
- 根关节:根关节的 OFFSET 是相对于世界坐标系的原点
- 子关节:子关节的 OFFSET 是相对于父关节的局部坐标系
CHANNELS 定义
CHANNELS 定义了关节的数据通道,指定了在 MOTION 部分中该关节的数据顺序:
- 位置通道:Xposition、Yposition、Zposition(通常只有根关节有)
- 旋转通道:Xrotation、Yrotation、Zrotation
通道的顺序很重要,它决定了 MOTION 部分中数据的读取顺序。常见的旋转顺序包括:
- ZXY(Z 轴、X 轴、Y 轴)
- XYZ(X 轴、Y 轴、Z 轴)
- ZYX(Z 轴、Y 轴、X 轴)
MOTION 部分详解
帧率和帧数定义
MOTION 部分以帧率和总帧数开始:
MOTION
Frames: 120
Frame Time: 0.033333
- Frames:动画的总帧数
- Frame Time:每帧的时间间隔(秒),帧率 = 1 / Frame Time
运动数据格式
每一行的运动数据包含了该帧中所有关节的数据,按照 HIERARCHY 部分定义的顺序排列:
0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ...
- 数据顺序:按照 HIERARCHY 中定义的 CHANNELS 顺序
- 根关节数据:通常包括 6 个值(3 个位置 + 3 个旋转)
- 普通关节数据:通常包括 3 个值(3 个旋转)
- 数据单位:位置通常以厘米为单位,旋转以度为单位
BVH 文件示例
以下是一个简化的 BVH 文件示例:
HIERARCHY
ROOT Hips
{
OFFSET 0.00 0.00 0.00
CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
JOINT LeftHip
{
OFFSET 3.00 0.00 0.00
CHANNELS 3 Zrotation Xrotation Yrotation
JOINT LeftKnee
{
OFFSET 0.00 -40.00 0.00
CHANNELS 3 Zrotation Xrotation Yrotation
End Site
{
OFFSET 0.00 -40.00 0.00
}
}
}
JOINT RightHip
{
OFFSET -3.00 0.00 0.00
CHANNELS 3 Zrotation Xrotation Yrotation
JOINT RightKnee
{
OFFSET 0.00 -40.00 0.00
CHANNELS 3 Zrotation Xrotation Yrotation
End Site
{
OFFSET 0.00 -40.00 0.00
}
}
}
}
MOTION
Frames: 1
Frame Time: 0.033333
0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
这个示例定义了一个简单的两腿骨骼结构,包含根关节(Hips)和左右腿的各两个关节(Hip 和 Knee)。
第三部分:BVH 格式技术细节
关节类型和约束
关节类型
BVH 格式主要支持旋转关节,通过 Euler 角(欧拉角)表示旋转:
- 旋转关节:大多数关节都是旋转关节,通过三个旋转角度定义
- 自由关节:根关节通常是自由关节,可以同时进行平移和旋转
- 固定关节:某些关节可能没有旋转通道,保持固定
旋转表示
BVH 格式使用 Euler 角表示旋转,而不是四元数或旋转矩阵:
- 优点:数据量小,易于理解和编辑
- 缺点:存在万向锁问题,某些旋转可能无法准确表示
- 单位:通常以度为单位,某些实现可能使用弧度
旋转顺序
旋转顺序(Euler 角顺序)对最终姿态有重要影响。常见的旋转顺序包括:
- ZXY:先绕 Z 轴旋转,再绕 X 轴旋转,最后绕 Y 轴旋转
- XYZ:先绕 X 轴旋转,再绕 Y 轴旋转,最后绕 Z 轴旋转
- ZYX:先绕 Z 轴旋转,再绕 Y 轴旋转,最后绕 X 轴旋转
旋转顺序由 CHANNELS 定义中的顺序决定,例如 CHANNELS 3 Zrotation Xrotation Yrotation 表示 ZXY 顺序。
坐标系定义
坐标系约定
BVH 格式通常使用右手坐标系:
- X 轴:通常指向右侧
- Y 轴:通常指向上方
- Z 轴:根据右手定则确定(通常指向观察者或前方)
坐标转换
不同软件可能使用不同的坐标系约定,因此在导入 BVH 文件时可能需要进行坐标转换:
- 轴向转换:可能需要交换或反转某些轴
- 单位转换:某些系统使用米而非厘米
- 旋转方向:某些系统使用左手坐标系
数据精度和单位
位置精度
- 单位:通常以厘米为单位
- 精度:浮点数精度,通常保留两位小数
- 范围:根据实际应用场景确定
旋转精度
- 单位:通常以度为单位(0-360 度或 -180 到 180 度)
- 精度:浮点数精度,通常保留两位小数
- 范围:通常在 -180 到 180 度之间
时间精度
- 帧率:通常为 30 FPS 或 60 FPS
- 时间单位:秒
- 精度:Frame Time 通常保留 6 位小数
常见变体和扩展
标准 BVH
标准 BVH 格式遵循上述规范,但不同实现可能有细微差异:
- 关键字大小写:某些实现可能对关键字大小写敏感
- 空格处理:某些实现可能对空格和制表符的处理不同
- 注释支持:标准 BVH 不支持注释,但某些实现可能支持
BVH 扩展
某些软件和工具对 BVH 格式进行了扩展:
- 额外通道:某些实现可能支持额外的数据通道
- 元数据:某些实现可能在文件开头添加元数据
- 压缩格式:某些实现可能使用压缩的 BVH 格式
第四部分:BVH 格式的应用
动作捕捉数据存储
动作捕捉流程
在动作捕捉流程中,BVH 格式作为标准的数据存储格式:
- 数据采集:动作捕捉系统(如光学捕捉、惯性捕捉)采集演员的动作数据
- 数据导出:将采集的数据导出为 BVH 格式
- 数据编辑:在动画软件中导入 BVH 文件,进行编辑和优化
- 数据应用:将编辑后的动作数据应用于 3D 角色模型
常见动作捕捉系统
支持 BVH 格式的动作捕捉系统包括:
- 光学捕捉系统:如 OptiTrack、Vicon、Motion Analysis
- 惯性捕捉系统:如 Xsens、Perception Neuron
- 基于摄像头的系统:如 Microsoft Kinect、Intel RealSense
动画制作流程中的应用
3D 动画软件支持
主流 3D 动画软件都支持 BVH 格式:
3ds Max
- 通过 Motion Capture 功能导入 BVH 文件
- 可以将 BVH 数据应用于 Biped 骨骼系统
- 支持 BVH 到 BIP 格式的转换
Maya
- 通过 File > Import 导入 BVH 文件
- 自动创建骨骼层次结构
- 支持动作数据的编辑和优化
Blender
- 通过内置的 BVH 导入器导入文件
- 支持动作数据的可视化编辑
- 可以导出为其他格式
MotionBuilder
- 专为动作捕捉数据设计
- 提供强大的 BVH 编辑功能
- 支持实时预览和编辑
游戏开发中的应用
游戏动作库
在游戏开发中,BVH 格式用于创建游戏角色的动作库:
- 动作采集:通过动作捕捉采集各种游戏动作
- 动作处理:在动画软件中编辑和优化动作
- 动作导入:将 BVH 文件导入游戏引擎
- 动作应用:将动作应用于游戏角色模型
游戏引擎支持
主流游戏引擎都支持 BVH 格式或可以通过插件导入:
- Unity:通过 Asset Store 中的插件支持 BVH 导入
- Unreal Engine:通过插件或中间格式转换支持
- Godot:通过插件支持 BVH 导入
机器人运动重定向
GMR 框架中的应用
在 GMR(General Motion Retargeting)框架中,BVH 格式作为标准的人类动作数据格式:
- 数据输入:GMR 支持从 BVH 文件读取人类动作数据
- 动作重定向:将 BVH 格式的人类动作重定向到机器人
- 数据转换:提供 BVH 到机器人动作的转换工具
LAFAN1 数据集
LAFAN1 是 Ubisoft 发布的大规模动作数据集,使用 BVH 格式:
- 数据规模:包含大量高质量的动作捕捉数据
- 数据格式:标准 BVH 格式,便于使用和转换
- 应用场景:广泛用于动作重定向和动作生成研究
Nokov 动作捕捉系统
Nokov(度量科技)的动作捕捉系统支持 BVH 格式导出:
- 实时捕捉:支持实时动作捕捉和 BVH 导出
- 高精度数据:提供高精度的动作数据
- 机器人应用:广泛应用于机器人遥操作和动作重定向
虚拟现实和增强现实应用
VR 角色动画
在虚拟现实应用中,BVH 格式用于驱动虚拟角色的动作:
- 用户动作捕捉:通过 VR 设备捕捉用户动作
- 动作数据传输:将动作数据转换为 BVH 格式
- 虚拟角色驱动:使用 BVH 数据驱动虚拟角色
AR 应用
在增强现实应用中,BVH 格式用于:
- 虚拟角色叠加:在真实场景中叠加虚拟角色动画
- 动作同步:实现虚拟角色与真实环境的动作同步
- 交互体验:提供自然的交互体验
第五部分:BVH 文件的处理与转换
BVH 文件解析方法
手动解析
BVH 文件是文本格式,可以手动解析:
- 读取文件:使用文本读取功能读取 BVH 文件
- 解析 HIERARCHY:逐行解析骨骼层次结构
- 解析 MOTION:解析运动数据部分
- 数据存储:将解析的数据存储到数据结构中
解析库和工具
有多种编程语言提供了 BVH 解析库:
Python
- bvh:Python 的 BVH 解析库
- pybvh:另一个 Python BVH 库
- 自定义解析器:可以根据需要编写自定义解析器
C++
- BVH Parser:C++ 的 BVH 解析库
- 自定义实现:可以根据文件格式规范实现解析器
JavaScript
- bvh-parser:JavaScript 的 BVH 解析库
- Three.js 插件:Three.js 的 BVH 加载器
Rust
- bvh_anim_parser:Rust 的 BVH 解析库,支持全局关节位置计算和可视化
格式转换
BVH 到 FBX
FBX 是 Autodesk 开发的 3D 模型和动画格式,功能更强大:
- 转换工具:使用 Autodesk MotionBuilder 或 FBX SDK
- 转换步骤:导入 BVH 文件,然后导出为 FBX 格式
- 数据保留:转换过程中保留动作数据
BVH 到 SMPLX
SMPLX 是参数化的人体模型格式:
- 转换方法:使用专门的转换工具,如 SMPL2BVH 的反向转换
- 数据映射:将 BVH 的关节数据映射到 SMPLX 的参数
- 应用场景:用于需要参数化人体模型的应用
BVH 到其他格式
BVH 可以转换为多种其他格式:
- Collada:通用的 3D 内容交换格式
- glTF:现代的 3D 场景格式
- JSON:自定义的 JSON 格式,便于 Web 应用使用
数据预处理和优化
数据清理
在使用 BVH 数据之前,可能需要进行数据清理:
- 去除噪声:使用滤波算法去除动作数据中的噪声
- 平滑处理:对动作数据进行平滑处理,减少抖动
- 异常值处理:检测和处理异常的数据值
数据优化
优化 BVH 数据以提高性能:
- 关键帧提取:提取关键帧,减少数据量
- 数据压缩:使用压缩算法减少文件大小
- 精度调整:根据应用需求调整数据精度
坐标系转换
在不同系统之间转换 BVH 数据时,可能需要进行坐标系转换:
- 轴向转换:转换坐标系轴向
- 单位转换:转换单位(厘米到米等)
- 旋转顺序转换:转换 Euler 角顺序
第六部分:BVH 格式的局限性与最佳实践
BVH 格式的局限性
技术局限性
BVH 格式存在一些技术局限性:
- Euler 角问题:使用 Euler 角表示旋转,存在万向锁问题
- 无缩放信息:不支持骨骼缩放信息
- 无变形信息:不支持顶点变形或形状关键帧
- 有限的数据类型:只支持位置和旋转,不支持其他数据类型
应用局限性
在某些应用场景中,BVH 格式可能不够灵活:
- 复杂动画:对于需要复杂变形和效果的动画,BVH 格式可能不够
- 实时编辑:BVH 格式不适合实时编辑和预览
- 元数据支持:标准 BVH 格式不支持元数据和注释
常见问题和解决方案
坐标系不匹配
问题:不同软件使用不同的坐标系约定,导致导入 BVH 文件时出现轴向错误。
解决方案:
- 在导入时进行坐标系转换
- 使用专门的转换工具
- 在导出 BVH 时统一坐标系约定
旋转顺序问题
问题:不同软件使用不同的 Euler 角顺序,导致旋转错误。
解决方案:
- 明确记录旋转顺序
- 在转换时进行旋转顺序转换
- 使用标准化的旋转顺序(如 ZXY)
单位不一致
问题:不同系统使用不同的单位(厘米 vs 米),导致缩放问题。
解决方案:
- 统一使用标准单位(通常为厘米)
- 在导入时进行单位转换
- 在文档中明确标注单位
数据精度问题
问题:浮点数精度可能导致累积误差。
解决方案:
- 使用足够的数据精度
- 定期进行数据验证和校正
- 使用双精度浮点数
最佳实践建议
文件组织
- 命名规范:使用清晰的命名规范,包含动作类型和版本信息
- 目录结构:组织好 BVH 文件的目录结构,便于管理
- 版本控制:对 BVH 文件进行版本控制,记录修改历史
数据质量
- 数据验证:在使用前验证 BVH 数据的完整性和正确性
- 数据清理:清理数据中的噪声和异常值
- 数据文档:为 BVH 文件添加文档,说明数据来源和用途
性能优化
- 数据压缩:对于大型数据集,考虑使用压缩格式
- 关键帧提取:提取关键帧以减少数据量
- 批量处理:使用批量处理工具提高处理效率
兼容性
- 标准格式:尽量使用标准 BVH 格式,避免使用扩展功能
- 测试验证:在不同软件中测试 BVH 文件的兼容性
- 格式转换:准备格式转换工具,以便在不同格式之间转换
与其他格式的对比
BVH vs FBX
| 特性 | BVH | FBX |
|---|---|---|
| 格式类型 | 文本格式 | 二进制/文本格式 |
| 复杂度 | 简单 | 复杂 |
| 功能范围 | 动作数据 | 完整 3D 场景 |
| 文件大小 | 较小 | 较大 |
| 解析难度 | 容易 | 困难 |
| 应用场景 | 动作捕捉 | 完整动画制作 |
BVH vs SMPLX
| 特性 | BVH | SMPLX |
|---|---|---|
| 数据表示 | 关节角度 | 参数化模型 |
| 数据量 | 较大 | 较小 |
| 适用模型 | 通用骨骼 | 人体模型 |
| 形状支持 | 不支持 | 支持 |
| 应用场景 | 通用动画 | 人体动作 |
BVH vs Collada
| 特性 | BVH | Collada |
|---|---|---|
| 格式类型 | 专用格式 | 通用格式 |
| 复杂度 | 简单 | 复杂 |
| 功能范围 | 动作数据 | 完整 3D 内容 |
| 标准化 | 事实标准 | 正式标准 |
| 应用场景 | 动作捕捉 | 3D 内容交换 |
总结
BVH(BioVision Hierarchy)格式作为动作捕捉和动画制作领域的标准格式,在多个领域都发挥着重要作用。其简洁而强大的设计,使得它成为存储和交换动作数据的理想选择。从动作捕捉到动画制作,从游戏开发到机器人学,BVH 格式都提供了可靠的数据基础。
理解 BVH 格式的结构和技术细节,对于有效使用和处理动作数据至关重要。无论是解析 BVH 文件、进行格式转换,还是在不同系统中应用 BVH 数据,都需要对格式有深入的理解。同时,了解 BVH 格式的局限性和最佳实践,可以帮助我们更好地利用这一格式,避免常见问题,提高工作效率。
随着动作捕捉技术和机器人学的发展,BVH 格式将继续发挥重要作用。虽然新的格式和技术不断涌现,但 BVH 格式作为成熟、稳定、广泛支持的标准格式,仍将在未来很长一段时间内保持其重要地位。掌握 BVH 格式,不仅有助于当前的工作和研究,也为未来的技术发展奠定了坚实的基础。
发表评论
请登录后发表评论