版權聲明: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只提供人臉檢測而不是人臉識別,如果需要此功能,還需要藉助其他技術共同完成。