ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ThreeDPoseTracker 0.5.1单目动捕实战:从2D关键点到BVH导出

ThreeDPoseTracker 0.5.1单目动捕实战:从2D关键点到BVH导出

简介:ThreeDPoseTracker 0.5.1 Windows 版是一套面向 VAM、MMD 与 Blender 动画创作者的动作捕捉工具,核心用法是导入视频素材,自动提取人体动作并生成可用于 MMD 或 Blender 的动画文件,配合插件还能转换为 VAM 的 timeline 动作,适合需要低成本制作角色动作、补齐动捕素材的创作者与进阶玩家。压缩包为 zip 格式,共 174 个文件,约 254.71MB。其中 142 个 dll 构成主要运行库与依赖,另有 config、ini、xml 等配置文件,以及 exe、assets、ress、map 等程序资源与场景数据,整体呈完整软件目录结构,便于直接部署和排查运行依赖。目前已有 713 人学习。借助这套工具,读者可以围绕视频转骨骼动画搭建可用的 Windows 动捕流程,产出 MMD、Blender 可继续编辑的动作数据,并为 VAM 角色表演与 timeline 编排提供素材;完整依赖与配置也方便定位缺失组件、复现运行环境。对于想从现成视频快速获得角色骨骼动画的人而言,可省去逐帧手 K 的关键帧工作量。

1. 单目摄像头做动捕:ThreeDPoseTracker 0.5.1 在 Windows 上到底能干到什么程度

ThreeDPoseTracker 0.5.1 版是跑在 Windows 上的单目动作捕捉工具:一个普通 USB 摄像头、一台没有独显的轻薄本,录一段人做动作的视频,它就能生成可直接导入三维软件的 BVH 骨骼动画。它解决的是“明天要交一段人物走路循环,但买不起光学动捕、也不愿意手动 K 帧”这类问题,目标不是影视级动捕。它适合三类人:做原型的独立开发者、练动画重定向的三维学习者、需要快速验证动作方案的从业者。它给不了手指细节和表情,也扛不住快速旋转与多人同框;但在 2 米距离、单人、慢中速动作这个区间里,出片率够用。下面按“原理—跑通—调参—避坑—补救”的顺序走一遍。

2. 从像素到骨骼旋转:2D 关键点是怎么被“提升”成 3D 姿态的

很多人第一次用 ThreeDPoseTracker,会觉得它有点奇怪:它不直接输出“关节的 XYZ 坐标”,而是输出一堆欧拉角通道。这个设计不是随手定的,它决定了整条管线怎么搭、参数为什么那么调。想把 0.5.1 用明白,得先搞清楚中间那两步:2D 关键点怎么变 3D 坐标,3D 坐标又怎么变骨骼旋转。

2.1 单目 3D 姿态估计的三条路线,以及 0.5.1 为什么走“提升 + IK”

单目做 3D 人体姿态,行业里归纳下来无非三条路,每条路的代价完全不同。

第一条是端到端坐标回归:一个网络吃进图像,直接吐 15 到 25 个关节在相机坐标系下的 (x, y, z)。好处是链路短、实现简单;坏处是绝对尺度天然不可知——同一个人离镜头 2 米和 3 米,输出的骨架会自动“缩水”,而且单帧回归抖动大,关节之间的骨长会像橡皮人一样伸缩。

第二条是 2D 检测加 3D 提升(lifting):先用一个轻量的 2D 关键点检测器拿到像素坐标和每个点的置信度,再用一个时序网络把一段窗口内的 2D 序列映射成 3D 坐标。2D 检测这一层现在已经很成熟,1080p 下 30fps 实时跑在 CPU 上都吃得消;提升网络参数量小,主要靠时序上下文去猜深度。代价是深度方向存在固有歧义——正面面对镜头时,左手在前还是右手在前,光看单帧根本分不出来,只能靠前后帧的运动趋势去推。

第三条是纯 IK 或数值优化:直接把骨骼旋转当成待优化变量,用 2D 关键点作为重投影约束去解。它天然输出旋转、骨长刚性、不会出橡皮人,但对初值极其敏感,容易掉进局部最优,而且慢。

ThreeDPoseTracker 0.5.1 走的是混合路线:2D 检测 → 时序 3D 提升 → 用一套刚性骨架做 IK 拟合 → 输出 BVH。这个组合的逻辑很实在:最终产物是旋转通道,路线一给的坐标还得再解算一次;路线三单独用精度不够稳;让提升网络负责给深度初值,IK 只在局部做微调,两边都扬长避短。

落到参数上,这条路线有两个隐含前提你得知道。一是“视频模式”和“单帧模式”差别很大——视频模式会用一个时间窗做时序平滑,轨迹明显更稳,但首尾几帧因为窗口填不满会偏;单帧模式响应快,抖动也大,只适合做快速预览。二是输入分辨率不是越高越好,抬到 1080p 以上对深度估计的收益很小,反而拖帧率,720p 到 900p 通常是甜点区。

2.2 关键点变骨骼旋转:rest pose、骨段向量与父子链

拿到 3D 关节坐标之后,真正的活儿才开始。BVH 要的是每个关节相对父关节的旋转,而提升网络给的是世界坐标,中间差一次“对齐 + 转局部”。

工具内部有一套固定的标准骨架,每个关节在 rest 状态(也就是 T-Pose)下有一份局部偏移,这份偏移最后会原样写进 BVH 的 OFFSET 字段。求解时对每根骨段做一件事:取当前帧的父关节指向子关节的向量 v_world,再取 rest 状态下同一骨段的向量 v_rest,求一个最小旋转 R,使得 R·v_rest ≈ v_world。这一步用四元数做两点对齐最省事,避免欧拉角在中途产生万向锁。

import numpy as np def quat_mul(q1, q2): x1, y1, z1, w1 = q1 x2, y2, z2, w2 = q2 return np.array([ w1*x2 + x1*w2 + y1*z2 - z1*y2, w1*y2 - x1*z2 + y1*w2 + z1*x2, w1*z2 + x1*y2 - y1*x2 + z1*w2, w1*w2 - x1*x2 - y1*y2 - z1*z2, ]) def rot_from_vec_pair(v_from, v_to): """求把 v_from 旋到 v_to 的最小旋转四元数,用于骨段对齐""" a = v_from / np.linalg.norm(v_from) b = v_to / np.linalg.norm(v_to) cross = np.cross(a, b) dot = float(np.clip(np.dot(a, b), -1.0, 1.0)) s = np.linalg.norm(cross) if s < 1e-8: # 两向量平行或反向,走退化分支 if dot > 0: return np.array([0.0, 0.0, 0.0, 1.0]) axis = np.array([1.0, 0.0, 0.0]) if abs(a[0]) < 0.9 else np.array([0.0, 1.0, 0.0]) axis = np.cross(a, axis) axis /= np.linalg.norm(axis) return np.array([axis[0], axis[1], axis[2], 0.0]) # 180 度 axis = cross / s half = np.arctan2(s, dot) * 0.5 return np.array([axis[0]*np.sin(half), axis[1]*np.sin(half), axis[2]*np.sin(half), np.cos(half)]) def to_local(q_parent, q_world): """世界旋转转父关节局部系:R_local = R_parent^-1 * R_world""" x, y, z, w = q_parent q_inv = np.array([-x, -y, -z, w]) # 单位四元数共轭即逆 return quat_mul(q_inv, q_world)

这段代码对应 IK 解算里最核心的一步。rot_from_vec_pair用的是叉积构造旋转轴、点积求夹角的标准做法,s < 1e-8那个分支必须写,因为骨段在伸直或完全反向时叉积会退化成零向量,不处理就会出 NaN,NaN 一路传下去整段动画直接废掉。to_local是把世界旋转搬回父关节坐标系,q_inv用共轭代替求逆是因为单位四元数满足这个性质,别在外层再归一化一次,会把数值误差放大。

有个细节容易被忽略:向量对齐只约束了骨段的朝向,绕骨段自身轴向的旋转(twist)是自由量,必须靠额外规则补——常见做法是拿髋部朝向或者上一帧的 twist 做连续性约束。这也是为什么肩膀在快速绕环时容易“拧麻花”,后面调参章节还会提到。

根节点的处理是另一套逻辑。骨盆位置需要从相机坐标系搬到世界系,并且用身高标定换算成真实单位,这一步做不好,导出后就会看到人物在原点附近疯狂平移,或者干脆原地踏步。

2.3 BVH 的通道语义:导出前必须看懂的 20 行文本

BVH 是纯文本格式,分两大段。HIERARCHY 段描述骨架树,ROOT 下逐层 JOINT / End Site,每个节点带 OFFSET(相对父节点的偏移,本质上就是骨长)和 CHANNELS(根节点 6 个通道:三个平移加三个旋转;其余关节 3 个旋转通道)。MOTION 段先是 Frames 和 Frame Time 两个头,然后每行一帧,数值按声明顺序平铺。

导入端翻车,八成栽在这几个约定上。欧拉角顺序不一致会导致手臂在某个角度后突然翻转 180 度,BVH 里旋转通道的顺序是写死在 CHANNELS 里的,比如Zrotation Xrotation Yrotation,改顺序等于改语义,导出端和导入端必须对得上。单位不统一会让模型整体放大或缩小一百倍,因为 OFFSET 的单位完全靠约定,厘米和米的差别就是一场灾难。帧率对不上会让动作变慢或变快,录 30fps 导入到 60fps 工程里就是二倍慢放。根位移如果没烘焙,动作再准也只是一段原地动画。

验证方法最简单粗暴:拿文本编辑器打开导出的 BVH,看前二十行的 OFFSET 是不是你预期的人体尺寸(身高 170cm 的话,髋到膝应该接近 40 到 45 个单位),看 CHANNELS 的旋转顺序,看 Frame Time 是不是 0.033333。这三处对上了,后面才有谈精度的必要。

3. 把 0.5.1 在 Windows 上跑通:硬件清单、输入源与一次完整录制

装完就能用是 ThreeDPoseTracker 少见的优点,它的 Windows 包是解压即跑的形态,不用装运行时、不用配环境变量。但“能跑”和“能出片”之间隔着一段距离,硬件、光照、输入源、录制顺序,每一环都能把成片质量拉下来一大截。

3.1 跑 0.5.1 之前的硬件与场景清单

先看一张表,照着对一遍再动手,能省掉后面一大半的返工。

项目最低可用舒适区说明
CPU4 核 8 线程8 核 16 线程提升网络与 IK 都吃单核,主频比核数重要
内存8 GB16 GB长视频导入后会整段驻留内存
显卡集成显卡6 GB 显存独显只影响推理速度,不影响精度
摄像头720p / 30fps1080p / 60fps60fps 对快速动作的提升非常明显
照明单侧柔光双侧均匀,300 lux 以上逆光和频闪是隐形杀手
背景纯色墙面无杂物、无镜面画面里第二个人会直接毁掉结果
空间2 m × 2 m2.5 m × 3 m保证全身入镜且手脚不越界

这张表里最容易被低估的是照明和背景。单目动捕的深度全靠纹理和阴影线索去猜,顶光会在眼窝和腋下打出死黑区域,关键点直接掉到阈值以下;背景里的镜子、玻璃门、显示器反光会造出“第二个人”,检测器分不清哪个是真的,输出的骨架会在两个人之间来回跳。有条件的,拉一块哑光纯色布当背景,比换摄像头收益大得多。

3.2 三种输入源怎么接,各自的坑在哪

0.5.1 支持三类输入:实时摄像头、本地视频文件、图片序列。三者不是简单换个来源,预处理要求完全不同。

实时摄像头最省事,也最考验现场。接上之后第一件事是关掉摄像头的自动曝光和自动白平衡,这两个功能在工作时会不停改变画面增益,关键点的像素坐标会跟着整体漂移,表现出来就是“人站着不动,骨架在微微抽搐”。摄像头参数一般在系统自带的相机设置里调,把曝光锁死、白平衡设成固定色温,画面会暗一点但稳定得多。帧率优先选 30fps,除非你确定要录跑跳类动作。

本地视频文件适合反复调试同一段素材。要求有三条:固定帧率、无明显运动模糊、无转场剪辑。手机拍的视频经常是可变帧率,丢进工具里时间轴会错位,得先转成恒定帧率再导入。快门速度低于 1/60 秒时,快速挥手会糊成一片,关键点检测会直接失败,这种素材再怎么调参也救不回来。

图片序列用得少,主要是在做逐帧对比实验时有用。要注意命名必须连续编号,中间缺一张就会整段错位。

3.3 一次完整录制的标准操作顺序

顺序错了,前面全白干。我一般按这个流程走:

第一步,清空场地。把椅子、水杯、线材移出取景框,地面标一个站位十字。

第二步,定机位。摄像头高度放在腰部到胸口之间,镜头水平,不要俯拍。俯拍会让深度估计把人的腿长压短,站着看起来像蹲着。距离控制在 2 到 2.5 米,保证双手平举时手腕不超出画面。

第三步,开灯、锁曝光、试录 10 秒。回放看骨架是否贴合,特别看脚踝和手腕这两个末端。

第四步,录一段 T-Pose 或 A-Pose 作为标定帧。这一帧决定了骨长比例,后面所有动作都基于它,所以姿势要标准:手臂平举、掌心向下、双腿与肩同宽、脚尖朝前。

第五步,正式录制。每个动作单独录一条,动作之间留 2 秒静止,方便后期裁切。不要一条视频里塞五个动作,任何一处出错都得整段重来。

第六步,检查回放。重点看三个地方:手有没有穿模到身体里、脚有没有离地漂浮、转身时骨架有没有整体翻转。

第七步,导出。导出前确认帧率与录制一致,根位移烘焙打开。

第八步,归档。按“日期_动作名_编号”命名,BVH 和源视频放同一个文件夹。

3.4 导出设置与目录约定

导出环节能调的其实不多,但每一项都影响下游。帧率跟录制保持一致,别想着导出高帧率后期再抽帧,插值出来的中间帧会让关节出现不自然的抖动。根位移一定要烘焙,否则导入三维软件后人物就在原地踏步,得手动补一条位移曲线,非常麻烦。

目录我习惯这么分:capture/放原始视频,bvh_raw/放未清理的导出文件,bvh_clean/放平滑后的成品,ref/放参考视频或动作分解图。这套结构在动作数量上到五十条之后会救命,因为文件名再规范,你也记不住哪条是“第三次重录的走路”。

4. 决定成品能不能用的 6 组参数:平滑、置信度、骨长标定与地面

同一段素材,参数调得好和调得差,成品质量能差出一个量级。0.5.1 的参数面板不算复杂,但每一项背后的取舍都很具体,搞清楚“调这个会牺牲什么”,比记住默认值有用得多。

4.1 平滑窗口与滤波强度:抖动和延迟只能二选一

平滑是单目动捕里最玄学的一环。原始输出的抖动来源有三层:2D 检测的像素级噪声、3D 提升的深度跳变、IK 解算的局部最优抖动。平滑窗口就是在时间轴上做加权平均,窗口越长越稳,但动作的起停会变“肉”。

我一般这么定:慢速动作(走路、太极拳、挥手)用 7 到 9 帧窗口;中速(跑步、跳跃)用 5 帧;快速(出拳、挥拍)用 3 帧甚至关闭。判断标准很简单——把平滑后的曲线和原始曲线叠在一起看,如果峰值被削掉了超过 15%,说明窗口太大,动作的力量感全没了。

滤波强度是另一层,通常是对每个关节的旋转做低通。强度过高的典型症状是“果冻感”:动作整体对,但四肢像泡在水里,末端总是慢半拍。这时候降强度,改用加长平滑窗口来换稳定性,效果会好很多。

还有一个常见误用:在预览界面看到很顺就以为成了,导出时忘了把平滑“烘焙”进数据。预览的平滑是显示层的,导出的是原始通道。养成导出前重新播放一遍原始数据的习惯,能省掉一次返工。

4.2 置信度阈值与关键点补帧策略

每个 2D 关键点都带一个 0 到 1 的置信度。阈值设高了,遮挡时点会被判为无效,骨架缺一块;设低了,错误检测会被当成真值,产生瞬移。

常用区间是 0.3 到 0.5。室内正常光照下 0.35 是个稳妥的起点;如果画面偏暗或者人物穿深色衣服,降到 0.25 并接受更多噪声,因为丢点的代价比噪声大得多——丢一个手腕点,整条手臂的旋转都会歪。

关键点失效之后怎么补,有两条策略。一是保持上一帧,适合短暂遮挡(手从身体前面划过),缺点是遮挡时间长了会“粘住”,动作结束还在原位。二是线性插值,适合有明确前后帧的短缺口,超过 10 帧的缺口插值出来的结果是假的,不如直接断开。我的经验是:缺口 5 帧以内用插值,5 到 15 帧用保持加手动修,超过 15 帧重录,别浪费时间。

4.3 身高标定:同一个人两次录制比例不一样是怎么回事

单目估计出来的 3D 坐标是相机坐标系下的相对量,没有绝对尺度。0.5.1 靠标定帧来换算:你在 T-Pose 时给出身高或者某个已知长度,工具按比例缩放整套骨架。

问题在于,标定帧本身是有误差的。如果 T-Pose 时手臂没完全平举、或者人站得离镜头比正式录制时近了半米,标出来的比例就会偏。表现出来就是“同一个人的两条走路动画,一条腿长一条腿短”。

我的做法是固定流程:标定帧和正式录制之间不动机位、不移动站位标记,标定帧录三条取中间那条,每条都检查一下手腕到手腕的距离是否合理。如果工具支持手输身高,优先手输,别依赖自动估计——自动估计在弯腰、屈膝时会给出离谱的结果。

另外注意,标定只影响整体缩放,不影响骨段比例。如果发现大腿明显比小腿短,那不是标定的问题,是关键点拓扑或者 IK 求解出了问题,调标定参数没用。

4.4 相机高度、俯仰角与地面平面

机位这件事,前面提过一次,这里展开说,因为它是所有参数里唯一“录完就改不了”的。

相机高度建议放在腰部(大约身高的 55% 到 60%)。太低会仰拍,腿部被拉长、上半身被压缩,深度估计会误判前后关系;太高变俯拍,腿被压短,而且头顶会遮挡躯干关键点。镜头必须水平,很多笔记本内置摄像头默认有 5 到 10 度的俯角,直接用会一直有系统性偏差。

地面平面决定脚部接触。0.5.1 通常在标定阶段会记录一个地面高度,之后所有根节点位移都相对这个平面。如果标定时脚没踩实、或者地面标记物有厚度,导出的动画里脚就会一直陷进地面或者浮在空中。检查方法很简单:让被录的人原地踏步十次,看每次落脚时脚踝的高度是否一致,不一致就重标。

5. 避坑与排查:单目动捕翻车的 5 个典型现场

参数调完还是不对劲的时候,问题往往不在参数上。下面这五条是我踩过次数最多的,按现象、原因、解决拆开写。

第一条:导入三维软件后模型变成面条,或者整体放大了 100 倍。

现象是骨架结构还在,但骨段被拉得极长,人物像融化的塑料;或者模型小得只剩一个点。原因几乎都是单位约定不一致——BVH 的 OFFSET 数值没有单位,导出端按厘米写,导入端按米读,就差一百倍;面条状则是 OFFSET 里的骨长比例本身就错了,通常是标定帧姿势不标准导致的。解决办法:先用文本编辑器打开 BVH 看 ROOT 下第一个 OFFSET 的数值量级,髋关节高度在 90 到 100 之间就是厘米制;导入时手动指定缩放,别用自动检测。如果确认是骨长比例问题,重新录标定帧,别在导入端硬掰。

第二条:手臂自然下垂贴近身体时,忽然整条手臂翻转 180 度。

现象是动作前半段正常,手臂一贴近躯干就“啪”地翻到手背朝前,然后又翻回来。原因是深度歧义加上向量对齐的自由度——手臂贴着身体时,2D 上的投影几乎重合,提升网络分不清手在前还是在后,同时骨段向量的 twist 没有约束。解决办法有三步:把置信度阈值降到 0.3 让更多点参与;检查旋转通道顺序有没有和导入端对上,顺序错了会在特定角度触发万向锁,表现和这个一模一样;实在不行就在动作设计上避开“手臂完全贴身”的姿势,让手臂离躯干保留 10 度夹角,代价很小,收益很大。

第三条:动作本身很准,但人物一直在原地滑步。

现象是腿部动作完全正确,可整个角色像站在跑步机上。原因是根节点位移没有烘焙,或者地面平面标定错了。先查导出选项里根位移有没有勾上,再查标定时的地面高度。如果两者都没问题,那大概率是录制时人走动了但机位没动——单目估计的根位移是相对相机的,人往镜头方向走两米,输出的位移量是对的,但如果标定时的参考距离设错了,整体位移就会按比例缩水。解决办法是重标一次,把站位标记和相机距离写进记录里。

第四条:导出的动画比录的时候慢了一半。

现象是动作形态完全对,就是节奏不对。原因是帧率不匹配:录制 30fps,导出时选了 60fps,工具按帧号而非秒数写 MOTION 数据,于是每帧被拉长成两帧的时长。解决办法是检查 BVH 的 Frame Time 字段,0.033333 对应 30fps,0.016667 对应 60fps,和源视频对齐。导入三维软件时工程帧率也要一起改,三处必须一致。

第五条:预览很顺,导出后抖动明显。

现象是软件里播放丝滑,导出的 BVH 一导入就开始高频抖。原因是预览层做了实时平滑,导出写的是原始通道,或者平滑参数只作用在显示上没烘焙。解决办法是导出前把平滑强度调高一级再导一次做对比,如果抖动消失,说明之前的平滑没生效;如果还抖,那就是 2D 检测层的噪声,得从光源和快门速度上解决,靠后期平滑只能压不能除。

6. 导出之后的十分钟:BVH 清理脚本与重定向验证

拿到 BVH 别急着往工程里拖,花十分钟做两件事:清一遍数据,再做一次重定向验证。

清理主要是平滑根位移和统一帧率。根位移的高频抖动在三维软件里会被放大成整体“呼吸式”晃动,用滑动平均压一下就很明显。下面这段脚本读 BVH、只处理根节点的三个平移通道,其它通道原样写回:

import numpy as np def smooth_root(path_in, path_out, win=5): """只平滑根节点平移通道,win 为奇数窗口帧数""" lines = open(path_in, encoding="utf-8").read().splitlines() motion_idx = next(i for i, l in enumerate(lines) if l.strip().upper() == "MOTION") frames_idx = motion_idx + 3 # 跳过 Frames / Frame Time / 空行 head, body = lines[:frames_idx], lines[frames_idx:] data = np.array([[float(v) for v in l.split()] for l in body if l.strip()]) pad = win // 2 kernel = np.ones(win) / win for ch in range(3): # 前三个通道是根平移 col = np.pad(data[:, ch], pad, mode="edge") data[:, ch] = np.convolve(col, kernel, mode="valid") with open(path_out, "w", encoding="utf-8") as f: f.write("\n".join(head) + "\n") for row in data: f.write(" ".join(f"{v:.6f}" for v in row) + "\n") smooth_root("bvh_raw/walk_01.bvh", "bvh_clean/walk_01.bvh", win=5)

win是关键参数:3 帧只压掉最尖的毛刺,5 帧适合走路跑步,9 帧以上会让起步和停步变得拖沓。mode="edge"是必须的,不加的话首尾帧会被零填充拉向原点,动画开头会出现一次“瞬移”。这个脚本只动平移通道,旋转通道原样保留,因为旋转平滑一旦做过头,动作的力量感就没了,那个得在三维软件里用曲线编辑器手动修。

然后是重定向验证。把清理后的文件导入三维软件,绑到一个和你身高接近的人形骨架上,重点看三个地方:脚掌在支撑相是否贴地不打滑、手肘和膝盖有没有反向弯折、脊柱在转身时有没有被扭断。这三处过了,剩下的就是艺术调整。如果脚打滑,回去查地面平面;如果关节反折,回去查关键点拓扑有没有和骨架对错;如果脊柱扭断,通常是根节点朝向的解算出了问题,只能在三维软件里补一条修正曲线。

我最早的几条动画就是跳过验证直接进工程的,结果在引擎里跑起来才发现脚一直在滑,回头改了三遍。后来定了个规矩:任何 BVH 不经过这十分钟,不进项目目录。这个习惯比任何参数都值钱。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进