Filament实现简易Animoji

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

特征点捕捉

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

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

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

iris vs face_mesh

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效果。

作者

8MilesRD

发布于

2022-07-25

更新于

2022-09-29

许可协议

评论