用Mediapipe Python做单目视觉实时动作捕捉

动作捕捉 2023-04-04 12725

最近打工的项目中,我负责了实时动捕的模块。项目要求实时交互,必须纯视觉,不能用穿戴设备。一开始做的是基于多目视觉关键点的动捕,效果虽好,但模型太慢,头疼了很久。最后干脆用Google Mediapipe的单视角关键点检测,发现无论是速度还是质量都超出了预期。让虚拟老婆动起来很有趣,做这个模块的过程也学习到很多,所以来简单分享一下。

这个模块达到了:

  1. 实时:不需要gpu,在笔记本上帧率15-30fps,延迟<50ms。能应用于套皮直播、视频电话。
  2. 廉价:仅依赖于单目RGB视频输入。笔记本摄像头就可以。
  3. 兼容:适应不同模型的骨骼结构;适应不同模型一定程度上身材比例差异。
  4. 手指捕捉(尽管精度不高,但是个意思,高糊视频很难追踪)

单目RGB摄像头是最廉价的采集设备,但单目视觉的关键点精度质量毕竟有限。整个算法是分成检测3D关键点追踪和定制IK Solver两个部分的。只要把关键点输入替换成基于RGBD输入的关键点,或者多视角关键点,显然可以得到更高质量的动捕。

尽管目前还远远达不到专业动捕的质量,但廉价便捷,也许可以拿来给各路创作者整活。来看看效果吧。

鸡你太美
堡垒之夜橙色正义

我从b站下载了一些视频,然后下载了一些开放的模型。把提取出的动作动画导入到Blender里渲染(动捕很快,但Blender渲染很慢)。

原神模型链接:

《原神》迫近的客星视频征集计划 (biligame.com)

《原神》「明霄升海平」1.3版本视频征集计划 (biligame.com)

堡垒之夜舞蹈视频片段

https://www.bilibili.com/video/BV18V411e7G9

Mediapipe

近年实时关键点检测技术越来越好,Mediapipe的这套方案是20年出的,性能、效果都优化的很好,而且接口也非常简单易用。Mediapipe是Google开发的基于深度学习的实时多媒体套件,汇集了人体、手部、人脸关键点追踪、物体检测、物体识别等多个流行的视觉任务的成果。整套 Mediapipe正如其名字,专门为便携设备上的多媒体应用设计,模型非常小,在笔记本手机上也能实时跑,跨平台、跨语言。

用mediapipe那实时性没问题了。然而性能和廉价牺牲了质量,这种关键点检测一般是用来做动作识别,类似于运动app跳绳计数、判定瑜伽动作标不标准这种。要拿来驱动三维模型还差得远。

用Mediapipe Python做单目视觉实时动作捕捉  第1张
用Mediapipe Python做单目视觉实时动作捕捉  第2张

首先第一个问题,单目关键点是二维的,而动捕是三维,从二维关键点如何得到三维关键点是一个非适定的问题。如果是深度相机例如Kinect,那很好办,有深度输入的话要预测关键点的深度不是难事。或者从多个视角,也能重建出三维的关键点。然而对于单目图像的关键点,各个关键点到相机深度。然而Mediapipe有这样一个方案

  1. Mediapipe Pose 能够预测一个相对深度,也就是各个关键点离相机的大致远近关系;
  2. 人体各个部位的尺寸及比例是有先验的

因此mediapipe能估计一个大致的三维的人体姿态,它可以输出pose_world_landmark。不过这个world_landmark和图像的关键点并不一致,而且是以屁股为原点的,没有绝对位置。怎么处理我们后面再说。

第二个问题是稳定性,这样得到三维关键点会有非常严重的抖动,包括遮挡或者是比较难的姿势导致关键点追踪不准引起的xy方向的抖动,或者是深度不准确引起的z方向的抖动。所以我们要对关键点加很强的平滑,同时还要尽量不损失动作幅度。

第三个问题,有了关键点才刚刚开始,最终要驱动模型骨骼运动,需要的是从关键点三维坐标计算出每个骨骼的位置和旋转。这是Inverse Kinetic问题,需要一个IK求解器

算法流程

用Mediapipe Python做单目视觉实时动作捕捉  第3张

处理Mediapipe的关键点

首先第一点是怎么得到相机坐标系的3D关键点。Mediapipe在输出图像2D关键点(pose_landmarks)的同时,也会输出3D维关键点(pose_world_landmarks),但mediapipe这个所谓的“世界坐标系”其实是以left_hip和right_hip为原点的坐标系,也就是人始终在原点附近,是没有全局的平移和旋转的。这个“世界坐标系”的初衷是为了方便识别3维的姿态,但要用作动捕的话,人就只能定在原地。

然而我们同时有2D图像关键点和3D关键点,容易想到可以调用Perspective-n-Points算法去求解出3D关键点相对于相机的变换,只需要额外提供一个相机内参即可,相机内参不用精确,差不多就行。然而不幸的是,这个world_landmark和2D关键点并不一致,solvePnP的结果并不能完美投影,这会导致结果和输入的人体动作对不齐,而且还会抖的很厉害。所以我们又进一步solvePnP的结果:保持相机空间坐标的深度z不变,直接调整x,y使得投影结果和2D关键点一致。尽管有点暴力,但效果确实好许多。

然后是对抖动的平滑。我们要对关键点加很强的平滑,同时还要尽量不损失动作幅度。我使用的是移动最小二乘(MLS)。MLS既是插值方法,也是平滑方法,用这个方法比滑动平均好,它能保证在匀速运动情况下没有延迟,甚至还平滑地插值来提高渲染帧率,分离输入与输出的采样点,一举多得。

即便这样,人体还是会有整体晃动,特别是有大幅动作时前后晃动,看起来很不自然。这是难免的,因为纯单目RGB输入的图像,人所在的绝对深度本来就很难确定。但是,基于物理事实,人的质心的运动轨迹远比各个关键点的轨迹平滑,所以我们可以对各个关键点加权,估计出一个近似的人体质心,对质心轨迹做一个很强的平滑。这个针对全身动捕的稳定性很有用。不过当然,平滑质心的前提是相机静止,否则在相机坐标系下平滑质心就没有意义了。

IK Solver

与常见的IK目标不同,要鲁棒地驱动一个模型的话有几个问题:

  1. 关键点的人体尺度和要驱动的模型的人体尺度可能不一致,身体各部位比例也不一致。

2. 目标关键点之间相互耦合,人体的骨骼也有耦合,不能分若干部位独立求解,要整体优化。

3. 人体的关节并不都能万向活动,不同关节有不同的约束,而且有的是软性的。

所以我用pytorch (cpu)自己写了一个IK Solver,优化参数是一些骨骼的欧拉旋转角,优化目标是一些给定的关键点对的方向与对应的骨骼关节相对方向的MSE+关节旋转约束+一个比较弱的l2正则化。调的是pytorch的LBFGS优化器。

有同学可能震惊这么偷懒又暴力,用pytorch得多慢。确实用pytorch就是要承受巨大的overhead,特别是计算骨骼,大量骨骼的级联关系会形成一个巨大的计算图。所以为了性能,我用C++实现骨骼计算及其梯度的计算,把这个overhead最重的部分优化了,基本上没啥overhead了,在连续帧的情况下收敛速度可以接受。而且IK用不到的bone不会参与骨骼计算,也会快一点。

此外这个IK求解器能兼容一定程度的模型骨骼结构的变化,以及rest pose的一些变化(最好是T pose),只要关键的骨骼按照特定的命名,这里我采用的按类似SMPLX的命名。

用Mediapipe Python做单目视觉实时动作捕捉  第4张

头部的姿态是由两耳的相对位置确定的,但绝大多数模型建模的时候不会加耳朵的armature,所以为了能捕捉头部姿态,要手动加上left_ear, right_ear两个armature。

性能

在我的笔记本上,Mediapipe可以跑到20ms~30ms处理一帧640x480图片(不会跑满CPU,占用率不高)。遗憾的是mediapipe不支持Windows GPU,如果是Mac Linux能用GPU的话可能更快。

IK Solver不追踪手指的话一般涉及到30个左右的骨骼,这时可以跑到5-10ms一帧(纯单线程);如果追踪手指的话会涉及到60到70个左右的骨骼,收敛也会慢一些,在有上一帧初始化的情况下大概要15ms到20ms一帧。

不过上面说的性能其实弹性很大。一方面mediapipe提供了几档模型复杂度可选,你可以根据需求选择更复杂更慢,或者更轻量更快但效果略差的模型。具体可以参考meidapipe文档,换模型复杂度只要改一个参数就行。另一方面IK Solver也可以根据你对精度的需求设定迭代终止,如果要完全实时例如用于直播,可以eps设大一点,平滑设高一点;如果要做离线视频的动捕,例如舞蹈等这种非常复杂的动作,就把eps调小和iterations调大来获得更好的效果。

(前面的视频因为舞蹈动作很大,都是选择最大模型、IK求解更精确,两个部分大概30 + 30 = 60 ms per frame)

但其实还是要提一点,假如我直接串行跑每一帧总共30ms,那么帧率≈30fps。但实际上你可以并发的跑多个线程,轮流处理视频流中的帧,假如你有足够多的cpu核心,可以无限地提高帧率。但延迟是固定的,从采集图像到输出结果中间延迟30ms是避免不了的。

还可以做什么

现在已经做了手指动捕。我是直接将手指关键点和人体关键点对齐了之后一并丢进IK Solver。评价是这个meidiapipe手指动捕质量还是不错的,但是很多视频严重高糊,大部分帧都追踪不到手,所以也就聊胜于无。不过直播中在镜头前比一些手势的话还是挺有用的。下面是一些手势比较清晰的帧,还是有那味的。

用Mediapipe Python做单目视觉实时动作捕捉  第5张
用Mediapipe Python做单目视觉实时动作捕捉  第6张

手指这里还可以有些优化空间,给手指的IK目标加大权重啥的,可能收敛会更快。这个例子不是很好,之后再考虑做一些比较显著的demo。

另外一个是面捕。其实在项目中,基于SOTA face landmark detection模型,我已经写了一个面部表情的捕捉、头部姿态估计,它会输出表情的blend shape系数。(为什么不直接卷积回归出表情系数呢?因为项目还有其他需求,必须拟合人脸参数化模型)而且面捕的头部姿态比前面人体关键点估计出的头部姿态的准很多,用这个头的姿态会非常准,非常适合对话交互。但这部分代码还没整理,而且我觉得还有很大优化空间。等之后有空我会把面捕也优化整理出来。

最后是眼球追踪,其实从face landmark提供的瞳孔关键点可以估计出眼球的大致姿态。这部分我还没做,但是非常感兴趣。因为人的感官对于眼神是非常敏感的,如果眼睛不能动,虚拟人物就是呆滞的。如果有面捕+眼球追踪,可谓点睛,虚拟人物立刻就有了灵魂!

代码

稍微整理了一下代码,感兴趣的同学可以运行一下(需要有Blender)

图形学漫漫长路,我还只能算一个初学者。欢迎指教讨论!

The End