Filament实现简易Animoji

1,202 阅读3分钟

Animoji是首先由苹果提出的增强现实表情包,利用摄像头捕捉到的面部特征点,及麦克风记录的声音,最终生成的3D动画表情。我们尝试在Android上实现类似的效果,于是有了这篇文章记录技术重点。

特征点捕捉

模型选用Face mesh(如果对眼球转动有捕捉要求可以使用Iris),谷歌给出了其捕捉的468个特征点坐标图,我们在找到需要的特征点后,通过减法得到表情值大小,从而计算出加权顶点坐标位置对应的模型效果。

在神经网络推理框架的选择上,ncnn相对于MediaPipe有更高的性能优势,但是其camera相关功能使用camera2NDK,部分机型底层并不支持以至于出现黑屏现象,所以考虑到demo的实现效率,我们选用mediapipe,若想把此功能运用在商业产品上,可以考虑使用ncnn并替换到camera部分实现。

iris在face mesh基础上增加了Iris地标模型,如下图3d表情眼珠的区别:

google_animoji.gif

3D引擎渲染工具

我们选择Filament作为3D模型渲染库,一是其用于Android设备上的Sceneform库中实现ARCore,二是其优秀的3D渲染效果及低耗能。当然我们因为不可抗拒因素无法直接使用ARCore,对Filament的学习势在必行。

Filament不仅有自己的模型格式,且支持glb/gltf,并且提供api操作节点,在初步了解光照、天空盒等概念后,开始编写demo。

从摄像头获取面部模型

Mediapipe已经把camera数据转换成了textureFrame,进而触发filament渲染模型。filament demo提供的Choreographer触发方式在此是行不通的,垂直同步时间戳已经是最小刷新间隔,同时使用会出现严重的卡顿问题。

从特征点到3D模型

那么如何把模型捕捉到的特征点转化为3D表情呢?这里以张嘴为例,设计给出的GLB文件已经定义好嘴部动作的权重,如"weights"

    {
      "extras": {
        "targetNames": [
          "xx"
        ]
      },
      "name": "mouth",
      "primitives": [
        {
          "attributes": {
            "POSITION": 0,
            "NORMAL": 1
          },
          "indices": 2,
          "material": 0,
          "targets": [
            {
              "POSITION": 3,
              "NORMAL": 4
            }
          ]
        }
      ],
      "weights": [
        1
      ]
    },

可以看到"weights"权重默认为1,我们通过获取特征点坐标17、0处y轴坐标值,转换ndc坐标后,做减法得出嘴巴张开度,从何获取其权重

 val landmarkList = faceMeshResult.multiFaceLandmarks()[0]
 (landmarkList.getLandmark(17).y - 1) * PROJECTION_SCALE - (landmarkList.getLandmark(0).y - 1) * PROJECTION_SCALE

Filament调用setMorphWeights设置mouth节点权重,从而实现嘴部的开闭。

 modelViewer.engine.renderableManager.setMorphWeights(
 	modelViewer.engine.renderableManager.getInstance(modelViewer.asset!!.getFirstEntityByName("mouth")), 
 	floatArrayOf(1 - 5f * mouthHeight, 0f, 0f, 0f), 
 	0
)

在镜头前需要移动头部模型,那么如何实现模型的移动呢,我们决定选用鼻子顶部坐标来映射模型移动,即94处坐标。

 Matrix.setIdentityM(transformMatrix, 0)
 Matrix.rotateM(transformMatrix, 0, 180f, 0f, 4f, 0f)
 Matrix.translateM(transformMatrix, 0, 0f, 0f, 4f)
 Matrix.translateM(
	transformMatrix, 0, -(noseCoord.x - ratio) * PROJECTION_SCALE,
	-(noseCoord.y - ratio) * PROJECTION_SCALE, 0f
)

Filament使用setTransform即可实现模型移动,如上可实现简易的animoji效果。