版权声明:Davidwang原创文章,严禁用于任何商业途径,授权后方可转载。
前节所述使用“标准模型”匹配人脸以检测人脸姿态是众多人脸姿态检测方法中的一种,实际上,人脸姿态估计还有很多,如柔性模型法、非线性回归法、嵌入法等等 。在计算机视觉中,头部姿势估计是指推断头部朝向,结合AR位置信息构建人脸矩阵参数的能力,有利因素是人体头部运动范围是有限的,可以借此消除一些误差。
(一)人脸姿态
人脸姿态估计主要是获得脸部朝向的角度信息,为快速的检测到人脸姿态,通常预先定义一个6关键点的3D脸部模型(左眼角,右眼角,鼻尖,左嘴角,右嘴角,下颌),如下图所示,然后使用计算机图像算法检测出视频帧中人脸对应的6个脸部关键点,并将这6个关键点与标准模型中对应的关键点进行拟合,解算出旋转向量,最后将旋转向量转换为欧拉角。理论上流程如此,但在实际中,可能并不能同时检测到人脸的6个关键点(由于遮挡、视觉方向、光照等原因),因此人脸姿态估计通常还会进行信息推测来提高鲁棒性。
庆幸的是,在ARFoundation中,我们完全可以不用去了解这些底层细节,AR Face Manager已处理好了这一切,AR Face Manager组件如下图所示。
AR Face Manager负责检测人脸,但其使用底层Provider提供的算法进行检测,不同的底层对人脸检测的要求也不一样,一般都是使用前置摄像头,ARCore在使用前置摄像头时会禁用后置摄像头,因此所有平面检测、运行跟踪、环境理解、图像跟踪等等都会失效,为提高性能,最好禁用这些功能。而新的ARKit则可以同时使用前置与后置摄像头。
与其他 Trackable Manager一样,AR Face Manager在检测到人脸时会使用人脸的姿态(位置与方向)实例化一个预制体,即上图中的Face Prefab属性,这个属性可以不设置,但如果设置后,在实例化预制体时,AR Face Manager会负责为该预制体挂载一个ARFace组件,ARFace组件包含了检测到的人脸的相关信息。Maximum Face Count属性用于设置在同一场景中检测的人脸数量,ARCore与ARKit都是支持多人脸检测,设置较小的值有助于性能优化。
AR Face Manager提供了一个supported属性用于检测用户设备是否支持人脸检测,当前并不是所有的设备都支持人脸检测功能。AR Face Manager还提供了facesChanged事件,我们可以通过注册事件来获取到新检测、更新、移除的人脸信息,进行个性化的处理。AR Face Manager对每一个检测到的人脸生成一个唯一的TrackableId,利用TrackableId可以跟踪每一张独立的人脸。
另外,在人脸检测和后续过程中,ARFoundation会自动进行环境光检测与估计,不需要开发人员手动编写相关代码。
在ARFoundation中,AR Face Manager完成了人脸姿态检测(使用底层Provider算法),我们可以通过以下方法验证其人脸姿态检测效果。首先制作一个交点在原点的三轴正交坐标系Prefab,如下图所示。
新建一个场景,选中Hierarchy窗口的AR Session Origin对象,为其挂载AR Face Manager组件,其Maximum Face Count设置为1,并将刚才制作的三轴坐标系Prefab赋给Face Prefab属性。编译运行,效果如下图所示。
从上图可以看出,ARFoundation检测出来的人脸姿态坐标原点位于人脸鼻尖位置,Y轴向上,Z轴向里,X轴向左。对比上面两张图可以看到,X轴朝向是反的,这是因为我们使用的是前置摄像头,这种反转X轴朝向的处理在后续人脸上挂载虚拟物体时可以保持虚拟物体正确的三轴朝向。
(二)人脸网格
除了人脸姿态,ARFoundation还提供了每一个检测到的人脸网格,该网格可以覆盖检测到的人脸形状,这张网格也是由底层SDK提供,包括顶点、索引、法向、纹理坐标等相关信息,如下图所示。ARCore 人脸网格包含468个顶点,ARKit人脸网格则包含1220个顶点,对普通消费级应用来讲已经足够了。利用这些网格,开发者只需要操控网格上的坐标和特定区域的锚点就可以轻松的在人脸上附加一些特效,如面具、 眼镜、虚拟帽子,或者对人脸进行扭曲等等。
在ARFoundation中,人脸网格信息由ARFace组件提供,而人脸网格的可视化则由ARFaceMeshVisualizer组件实现,该组件会根据ARFace提供的网格数据更新显示网格信息。显示人脸网格与显示模型非常类似,需要先制作一个预制件Prefab,区别是这个预制件中没有模型,因为网格信息与网格可视化都由专门的组件负责实现。
在Hierarchy窗口中新建一个空对象,命名为FaceMesh,并在其上挂载ARFace与ARFaceMeshVisualizer组件,同时挂载渲染组件Mesh Renderer和Mesh Filter,如下图所示。
我们的目标是使用京剧脸谱渲染网格,为了将眼睛与嘴露出来,我们需要提前将眼睛与嘴的部分镂空,并将图像保存为png格式,在渲染时需要将镂空部分剔除掉,因此,编写一个Shader如下。
Shader "DavidWang/FaceShader" {
Properties{
_MainTex("Texture", 2D) = "white" {}
}
SubShader{
Tags{ "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" }
Cull Off
CGPROGRAM
#pragma surface surf Standard vertex:vert fullforwardshadows
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
float y : TEXCOORD0;
};
void vert(inout appdata_full v,out Input o) {
o.uv_MainTex = v.texcoord.xy;
o.y = v.vertex.y;
}
void surf(Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Alpha = c.w;
clip(o.Alpha - 0.1);
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
在 Shader 代码中,剔除透明区域使用的是 clip(x)函数,该函数功能是丢弃 x 小于 0 的像素。然后我们新建一个使用该Shader的材质,命名为face,并赋上相应纹理。
将FaceMesh制作成Prefab,并删除Hierarchy窗口中的FaceMesh对象,最后一步将FaceMesh预制体赋给Hierarchy窗口中AR Session Origin对象下AR Face Manager组件的Face Prefab属性,编译运行,效果如下图所示。
进一步思考,AR Face Manager组件的只有一个Face Prefab属性,只能实例化一类人脸网格,如果要实现川剧中的变脸效果该如何处理?
前文提到,AR Face Manager有一个facesChanged 事件,因此我们可以注册这个事件来实现我们需要的功能。思路如下:现实变脸就是要更换掉渲染的纹理,因此我们只需要在AR Face Manager检测到人脸的added事件中,在其实例化人脸网格之前替换掉相应的纹理即可。为此,我们需要制作几张类似的纹理,并放置在Resources文件夹中以便运行时加载。新建一个C#脚本,命名为ChangeFace,编写如下代码。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
[RequireComponent(typeof(ARFaceManager))]
public class ChangeFace : MonoBehaviour
{
private GameObject mFacePrefab;
ARFaceManager mARFaceManager;
private Material _material;
private int TextureIndex = 0;
private Texture2D[] mTextures = new Texture2D[3];
private void Awake()
{
mARFaceManager = GetComponent<ARFaceManager>();
}
void Start()
{
mFacePrefab = mARFaceManager.facePrefab;
_material = mFacePrefab.transform.GetComponent<MeshRenderer>().material;
mTextures[0] = (Texture2D)Resources.Load("face0");
mTextures[1] = (Texture2D)Resources.Load("face1");
mTextures[2] = (Texture2D)Resources.Load("face2");
}
private void OnEnable()
{
mARFaceManager.facesChanged += OnFacesChanged;
}
void OnDisable()
{
mARFaceManager.facesChanged -= OnFacesChanged;
}
void OnFacesChanged(ARFacesChangedEventArgs eventArgs)
{
foreach (var trackedFace in eventArgs.added)
{
OnFaceAdded(trackedFace);
}
/*
foreach (var trackedFace in eventArgs.updated)
{
OnFaceUpdated(trackedFace);
}
foreach (var trackedFace in eventArgs.removed)
{
OnFaceRemoved(trackedFace);
}
*/
}
private void OnFaceAdded(ARFace refFace)
{
TextureIndex = (++TextureIndex) % 3;
_material.mainTexture = mTextures[TextureIndex];
// Debug.Log("TextureIndex:" + TextureIndex);
}
}
上述代码的主要逻辑就是在AR Face Manager实例化人脸网格之前替换纹理,将该脚本也挂载在Hierarchy窗口中的AR Session Origin对象上。编译运行,当手在脸前拂过时,虚拟人脸网格纹理就会随之替换,效果如图所示。