Files
opendance-web/tasks/006-keypoint-preprocess.md
zijunchiang 6913e181b5 docs: 其余易混淆字符改 ASCII(× − ≥ ≤ ≈ ✓ 等),消除 Gitea 告警
实测确认 Gitea 的「模棱两可 Unicode」检测覆盖:— – … § → × − ≥ ≤ ≈ ✓。
三库 .md/.txt 全部替换为 x - >= <= ~ OK;contracts 三库同源保持一致。
重新打包 v0.2.0。
2026-09-15 21:35:10 +08:00

5.0 KiB
Raw Permalink Blame History

T-WEB-006 BVH/VMD/FBX -> 关键点序列预处理

  • 状态:done
  • 依赖:T-WEB-004

目标

把上传的 3D 动作数据(BVH / VMD / FBX / glTF / keypoints-json)转换为与 Android 端输出同构的 MediaPipe 33 点关键点序列,使 Windows 端 DTW 引擎只有一条输入通路。

load

DSH.md
contracts/model-formats.md       # 锚点:2 动作格式、2.1 预处理产物、2.2 关节名映射、2.3 VMD
contracts/skeleton-topology.md   # 锚点:1 33 点、3 父节点表、5 关节角
web-server/src/preprocess/*

端点

方法 路径 说明
POST /api/motions/:id/preprocess 执行预处理,返回更新后的 Motion
GET /api/motions/:id/keypoints 下载 NDJSON 关键点序列

实现分层

文件 职责
bvh-parser.ts 纯 TS BVH 词法/语法解析 + 逐帧前向运动学(FK)
vmd-parser.ts VMD 二进制解析:30 字节头 + Shift-JIS 模型名 + 111 字节/条骨骼关键帧 + morph 段;MMD 贝塞尔插值,固定 30fps
mmd-skeleton.ts 标准 MMD 骨架(含全角/半角 IK/IK 别名归一);静止比例从参考 PMX 实测
mmd-pose.ts MMD FK + 两骨骼解析腿部 IK + 左手系->右手系 Z 镜像,输出逐帧世界坐标
math3d.ts 向量/矩阵工具、旋转合成、关节角余弦
joint-map.ts 关节名 -> MediaPipe 点索引的默认映射表
retarget.ts 导出的 worldFramesToKeypointFrames():任意骨架世界坐标 -> 骨盆归零 -> 自适应缩放 -> 投影 -> 9 个关节角;BVH 与 VMD 共用
keypoint-codec.ts 239 字节二进制帧编解码、NDJSON 序列化/解析、关键点 JSON 校验
assimp.ts 可选外部 Assimp 调用(FBX/glTF -> BVH)
preprocess.service.ts 编排、落盘、状态与错误记录

拓扑与缩放(契约冻结)

  • 33 点顺序与父节点表来自 contracts/skeleton-topology.md,虚拟关节 neck=33、pelvis=34 只参与关节角计算,不进入输出序列。
  • 投影:x_img = 0.5 + x s,y_img = 0.5 - y s,z_img = -z s, s = 0.35 / max(|x|,|y|,|z|)(按整段动作自适应,保证不出画)。
  • 置信度分层:直接映射 1.0、按层级推算 0.5、无法推算 0.0。

格式支持

格式 处理
.bvh 内置解析器,无外部依赖
.vmd 内置解析器,无外部依赖;固定 30fps,见「VMD 通路」
.json 直通校验与规范化(须符合 mediapipe33@0.1.0)
.fbx .gltf .glb 调用可选 Assimp 转 BVH;未安装 Assimp 时写入可读错误并置 preprocessState=failed

VMD 通路

.vmd ─▶ vmd-parser.ts ─▶ mmd-skeleton.ts ─▶ mmd-pose.ts ─▶ retarget.ts
        二进制 + Shift-JIS   标准 MMD 骨架     FK + 腿部 IK    worldFramesToKeypointFrames()
        + 贝塞尔插值                           + Z 镜像         (与 BVH 共用)
  • VMD 不含骨骼长度、只有旋转(少数骨骼带位移),必须靠 mmd-skeleton.ts 的 标准骨架做 FK;骨骼名按别名表归一(全角 IK / 半角 IK),未纳入标准骨架的 骨骼(裙摆 / 物理 / 表情等)被忽略但计数。
  • 腿部驱动自动判定:足IK 轨道存在非零位移 -> 两骨骼解析 IK(膝盖朝角色 正面弯,目标不可达时收敛为伸直,不产生 NaN);否则走 FK 旋转(部分动作数据把 腿烘焙成 FK)。
  • 手性:MMD 为左手系,统一做 Z 镜像(旋转 (x,y,z,w) -> (-x,-y,z,w)、位移 z 取反)后进入右手系通路。由真实数据对照实验确定:腿部烘焙为 FK 的样本中, zMirrored 下「膝盖朝前」占 94.1%,不转换仅 5.9%(见 test/mmd-vmd.spec.ts)。
  • 重定向完全复用 BVH 通路:worldFramesToKeypointFrames() 负责骨盆归零 / 自适应缩放 / 图像空间投影 / 9 个关节角,因此两种来源的产物结构一致。
  • 固定 30 fps,上传时填写的 fps 被忽略。

验收

npm run test          # 85 例(含 31 例 VMD):解析 / FK / 重定向 / 编解码 / 拓扑 / 契约版本
npm run test:smoke    # 冒烟 [3] 段落

覆盖:60 帧样例经预处理后 60 帧 x 33 点 x 9 关节角、坐标落在 MediaPipe 归一化范围、置信度分层、时间戳按帧率推进、序列随时间变化、 NDJSON 往返、解码器拒绝错误长度与 magic。

VMD 侧另测:二进制解析与截断报错、贝塞尔/四元数插值、标准骨架比例与镜像、 FK 与腿部 IK(可达/不可达/退化)、手性对照、合成 VMD 端到端 0 NaN / 0 越界。

两个真实样本实测(../示例文件/):【动作】霜雪千年.vmd 2901 帧(腿部 IK) 约 0.4s、恋爱循环.vmd 4604 帧(烘焙 FK)约 2.5s,均 0 NaN / 0 越界。

样例数据由 test/fixtures/generate-sample-bvh.mjs 生成(60 帧挥手 + 深蹲, 21 关节 / 63 通道),可复现;VMD 诊断可用 scripts/inspect-vmd.mjs。