版权声明:Davidwang原创文章,严禁用于任何商业途径,授权后方可转载。
ARFoundation在进行人脸检测时需要借助底层Provider提供的算法及功能特性,不同的底层能提供的功能特征也不相同,如在ARCore人脸检测中,提供人脸区域的概念与功能,而在ARKit人脸检测中,则提供Blend Shape功能。
(一)人脸区域
ARCore在进行人脸检测时其实是同时运行了两个实时深度神经网络模型:一个负责检测整张图像并计算人脸位置的探测模型,另一个是借助通用3D网格模型通过回归方式检测并预测大概的面部结构。ARCore在检测的人脸时,除了提供一张468个点的人脸网络之外,其还对网格特定区域进行了标定,如下图所示,ARCore提供的人脸网格包括了顶点信息、中心点姿态信息、人脸区域信息。其中中心点位于人体头部的中央位置,中心点也是人脸网格顶点的坐标原点,即为人脸风格的局部空间原点。
ARCore目前定义了三个人脸局部区域,即上图所示三个黄色小圈区域,可以在应用开发中直接使用,并在代码中定义了一个枚举数 ARCoreFaceRegion,如下表所示:
属性 | 描述 |
---|---|
ForeheadLeft | 定位人脸模型的左前额 |
ForeheadRight | 定位人脸模型的右前额 |
NoseTip | 定位人脸模型的鼻尖 |
因为人脸特征,在有这三个局部区域定位后, 我们可以方便的将虚拟物体挂载到人脸上,ARCoreFaceRegion枚举以后应该还会继续扩充,起码会包括眼睛、耳朵、嘴部、脸颊,更方便我们精确定位检测到的人脸模型的具体位置。
下面以鼻尖区域(NoseTip)为例,我们将人脸盔甲面罩戴在人脸上。
思路:
1、制作一个盔甲面罩,将眼镜部分镂空。
2、注册AR Face Manager组件的facesChanged事件,在added、updated中将面罩挂在NoseTip位置上。
为了将面罩挂在NoseTip位置上,我们需要在人脸区域中找到NoseTip,新建一个C#脚本,命名为SingleFaceRegionManager,编写如下代码:
[RequireComponent(typeof(ARFaceManager))]
[RequireComponent(typeof(ARSessionOrigin))]
public class SingleFaceRegionManager : MonoBehaviour
{
[SerializeField]
private GameObject mRegionPrefab;
private ARFaceManager mARFaceManager;
private GameObject mInstantiatedPrefab;
private ARSessionOrigin mSessionOrigin;
private NativeArray<ARCoreFaceRegionData> mFaceRegions;
private void Awake()
{
mARFaceManager = GetComponent<ARFaceManager>();
mSessionOrigin = GetComponent<ARSessionOrigin>();
mInstantiatedPrefab = Instantiate(mRegionPrefab, mSessionOrigin.trackablesParent);
}
private void OnEnable()
{
mARFaceManager.facesChanged += OnFacesChanged;
}
void OnDisable()
{
mARFaceManager.facesChanged -= OnFacesChanged;
}
void OnFacesChanged(ARFacesChangedEventArgs eventArgs)
{
foreach (var trackedFace in eventArgs.added)
{
OnFaceChanged(trackedFace);
}
foreach (var trackedFace in eventArgs.updated)
{
OnFaceChanged(trackedFace);
}
/*
foreach (var trackedFace in eventArgs.removed)
{
OnFaceRemoved(trackedFace);
}
*/
}
private void OnFaceChanged(ARFace refFace)
{
var subsystem = (ARCoreFaceSubsystem)mARFaceManager.subsystem;
subsystem.GetRegionPoses(refFace.trackableId, Allocator.Persistent, ref mFaceRegions);
for (int i = 0; i < mFaceRegions.Length; ++i)
{
var regionType = mFaceRegions[i].region;
if (regionType == ARCoreFaceRegion.NoseTip)
{
mInstantiatedPrefab.transform.localPosition = mFaceRegions[i].pose.position;
mInstantiatedPrefab.transform.localRotation = mFaceRegions[i].pose.rotation;
}
}
}
void OnDestroy()
{
if (mFaceRegions.IsCreated)
mFaceRegions.Dispose();
}
}
在上述代码中,为了更有效的利用模型而不是在每次检测到人脸added、updated时重新实例化盔甲面罩,我们只在Awake()方法中实例化了一个Prefab,并在added、updated中实时的更新该实例化对象的姿态。将该脚本挂载在Hierarchy窗口中的AR Session Origin对象上,将盔甲面罩赋给mRegionPrefab属性,同时将AR Face Manager组件中的Face Prefab置空,编译运行,效果如下:
正如前所述,ARCore的区域只是方便让我们以更精确的方式在特定位置挂载虚拟物体。因为在检测人体头部时,都会提供头部中心点姿态信息,加之人脸结构的固定性,因此即使没有区域的概念,我们也可以通过操控模型以偏移一定的距离而将模型挂载在人脸的指定位置,如我们可以通过调整模型位置只使用AR Face Manager将眼镜模型挂载在人眼上,如下图所示。
相比于特征区域,这种通过模型偏移的方式有一个问题,因为我们是通过相对中心点的偏移将虚拟物体挂载到特定位置的,这个偏移量个体差异会很大,即挂在小孩脸上的眼镜正合适时挂在成人脸上的眼镜就会偏下(小孩眼睛离中心点距离比成人小),因此在能使用区域位置特性时尽量使用区域可以提高精度。
(二)多人脸检测
ARCore与ARKit都支持多人脸检测,在多人脸检测中,除了注册facesChanged事件,我们也可以直接在Update()方法中进行处理,代码如下:
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.ARCore;
[RequireComponent(typeof(ARFaceManager))]
[RequireComponent(typeof(ARSessionOrigin))]
public class ARCoreFaceRegionManager : MonoBehaviour
{
[SerializeField]
GameObject mRegionPrefab;
ARFaceManager mFaceManager;
ARSessionOrigin mSessionOrigin;
NativeArray<ARCoreFaceRegionData> mFaceRegions;
Dictionary<TrackableId, Dictionary<ARCoreFaceRegion, GameObject>> mInstantiatedPrefabs;
void Start()
{
mFaceManager = GetComponent<ARFaceManager>();
mSessionOrigin = GetComponent<ARSessionOrigin>();
mInstantiatedPrefabs = new Dictionary<TrackableId, Dictionary<ARCoreFaceRegion, GameObject>>();
}
void Update()
{
var subsystem = (ARCoreFaceSubsystem)mFaceManager.subsystem;
if (subsystem == null)
return;
foreach (var face in mFaceManager.trackables)
{
Dictionary<ARCoreFaceRegion, GameObject> regionGos;
if (!mInstantiatedPrefabs.TryGetValue(face.trackableId, out regionGos))
{
regionGos = new Dictionary<ARCoreFaceRegion, GameObject>();
mInstantiatedPrefabs.Add(face.trackableId, regionGos);
}
subsystem.GetRegionPoses(face.trackableId, Allocator.Persistent, ref mFaceRegions);
for (int i = 0; i < mFaceRegions.Length; ++i)
{
var regionType = mFaceRegions[i].region;
if (regionType == ARCoreFaceRegion.NoseTip)
{
GameObject go;
if (!regionGos.TryGetValue(regionType, out go))
{
go = Instantiate(mRegionPrefab, mSessionOrigin.trackablesParent);
regionGos.Add(regionType, go);
//Debug.Log("Object Count :" + mInstantiatedPrefabs.Count);
}
go.transform.localPosition = mFaceRegions[i].pose.position;
go.transform.localRotation = mFaceRegions[i].pose.rotation;
}
}
}
}
void OnDestroy()
{
if (mFaceRegions.IsCreated)
mFaceRegions.Dispose();
}
}
代码中我们也是在NoseTip位置挂载模型,在ForeheadLeft 或 ForeheadRight位置挂载虚拟物体,处理方式完全一致。上述代码中,我们在所有检测到的人脸NoseTip位置挂载相同的虚拟物体模型,如果需要挂载不同的虚拟物体,我们可以设置一个Prefabs数组用于保存所有的待实例化预制体,然后用一个随机数发生器随机实例化虚拟物体。当前,我们无法做到在特定的人脸上挂载特定的虚拟物体,因为ARCore、ARKit只提供人脸检测而不是人脸识别,如果需要此功能,还需要借助其他技术共同完成。