Unity SenseAR教程:人臉追蹤掛點位置「含源碼」

洪流學堂,讓你快人幾步。你好,我是你的技術探路者鄭洪智,你可以叫我大智(VX: zhz11235)。

上次咱們一起探索了人臉追蹤,並且實現了通過點擊往臉上“添彩”的功能。

但是很多時候,咱們想識別出臉部後直接在探索SenseAR中人臉追蹤眼睛、鼻子、嘴巴掛點的位置,還送你一個開箱即用的擴展工具類哦~臉上給它添加一些裝飾物,而不需要玩家手動點擊臉上才能添加上。

上一篇最後也給你了一些思路,就是根據射線檢測到的點,計算出離點擊點最近的頂點,這個頂點順序大概率是不會變的,可以作為錨定的座標點。咱們這節課一起使用這個思路來探索一下是否可行。

對SenseAR還不太熟悉的同學可以看下大智的視頻:

  • 商湯SenseAR全功能初體驗(含填坑經驗)

  • 視頻B站鏈接:https://www.bilibili.com/video/av89332645

最終效果

首先要給你顆定心丸,上面的思路是可行的。這次不需要手動點擊往臉上放小球了,小球可以直接出現!

Unity SenseAR教程:人臉追蹤掛點位置「含源碼」

開工

想要達成今天的目標,咱們需要依次解決以下幾個問題:
1、首先確認臉部Mesh的頂點數是固定的(否則頂點索引可能會變化很大)
2、使用上節的射線檢測到的點,計算臉部Mesh上離這個點最近的點的索引
3、記錄下幾個點的索引位置,在對應位置生成小球,驗證下是否每次都是固定點
4、編寫一個ARFace擴展類,可以直接獲取對應位置的點

1、確認臉部Mesh的定點數

首先確認臉部Mesh的頂點數是固定的,否則頂點索引可能會變化很大
這個數字可以在<code>ARFace.vertices.Length/<code>獲取到

<code>voidUpdate()
{
if(m_FaceManager.subsystem!=null&&faceInfoText!=null)
{
faceInfoText.text=$"Supportednumberoftrackedfaces:{m_FaceManager.supportedFaceCount}\\n"+
$"Maxnumberoffacestotrack:{m_FaceManager.maximumFaceCount}\\n"+
$"Numberoftrackedfaces:{m_FaceManager.trackables.count}";

//這樣可以在UI上看到頂點的數量
faceInfoText.text+="\\n當前臉部Mesh的頂點數為:"+_verticeCount;
}


if(Input.GetMouseButtonUp(0))
{
varcamera=GetComponent<arsessionorigin>().camera;

varray=camera.ScreenPointToRay(Input.mousePosition);

if(Physics.Raycast(ray,outvarhit,1000))
{
vargo=GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.transform.localScale=Vector3.one*0.01f;
//設置父物體為人臉,這樣物體會跟隨人臉移動
go.transform.SetParent(hit.transform);
go.transform.position=hit.point;

//!!!下面是添加的代碼
varface=hit.transform.GetComponent<arface>();
//創建一個int類型的私有成員
_verticeCount=face.vertices.Length;
}
}
}
/<arface>/<arsessionorigin>/<code>

通過這一步,咱們就能確認臉部的網格固定是11510個頂點了,可以放心進入第二步了。

2、計算臉部Mesh上離射線檢測點最近的頂點的索引

這一步咱們需要找到幾個特殊點的索引,我準備找到的點是鼻尖、4個眼角、2個嘴角。

代碼如下:

<code>voidUpdate()
{
if(m_FaceManager.subsystem!=null&&faceInfoText!=null)
{
faceInfoText.text=$"Supportednumberoftrackedfaces:{m_FaceManager.supportedFaceCount}\\n"+
$"Maxnumberoffacestotrack:{m_FaceManager.maximumFaceCount}\\n"+
$"Numberoftrackedfaces:{m_FaceManager.trackables.count}";

//這樣可以在UI上看到頂點的數量

faceInfoText.text+="\\n當前臉部Mesh的頂點數為:"+_verticeCount;

//這樣可以在UI上看到頂點的索引
faceInfoText.text+="\\n離點擊位置最近的頂點索引是:"+_verticeCount;
}

//!!!下面是添加的代碼
if(Input.GetMouseButtonUp(0))
{
varcamera=GetComponent<arsessionorigin>().camera;
varray=camera.ScreenPointToRay(Input.mousePosition);

if(Physics.Raycast(ray,outvarhit,1000))
{
vargo=GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.transform.localScale=Vector3.one*0.01f;
//設置父物體為人臉,這樣物體會跟隨人臉移動
go.transform.SetParent(hit.transform);
go.transform.position=hit.point;

varface=hit.transform.GetComponent<arface>();
//需要在類中創建一個int類型的私有成員
_verticeCount=face.vertices.Length;


varmin=float.MaxValue;
//需要在類中創建一個int類型的私有成員
minIndex=-1;
for(varindex=0;index<face.vertices.length>{
varv=face.vertices[index];
varlocal=hit.transform.InverseTransformPoint(hit.point);

//使用sqrMagnitude可以減少一次開方計算,結果一樣,性能更好
vardistance=(v-local).sqrMagnitude;
if(distance{
minIndex=index;
min=distance;
}

}

}
}
}
/<face.vertices.length>/<arface>/<arsessionorigin>/<code>

我找到的幾個點索引是:

<code>privateint[]PointIndexs={10655,9265,9218,10796,8940,10609,9103};
/<code>

3、反向驗證第2步得到的索引

記錄下幾個點的索引位置,在對應位置生成小球,驗證下是否每次都是固定點

<code>usingSystem.Collections.Generic;
usingUnityEngine;
usingUnityEngine.UI;
usingUnityEngine.XR.ARFoundation;

[RequireComponent(typeof(ARFaceManager))]
publicclassDisplayFaceInfo:MonoBehaviour
{
[SerializeField]Textm_FaceInfoText;

publicTextfaceInfoText
{
get{returnm_FaceInfoText;}
set{m_FaceInfoText=value;}
}

ARFaceManagerm_FaceManager;
privateintminIndex;

privateint[]PointIndexs={10655,9265,9218,10796,8940,10609,9103};
//下面的顏色是調試用的,因為大智忘了上面那些數字對應是那些位置了/(ㄒoㄒ)/~~
privateColor[]Colors={Color.black,Color.white,Color.blue,Color.gray,Color.green,Color.red,Color.yellow};
privateDictionary<int,GameObject>BallMap=newDictionary<int,GameObject>();
privateint_verticeCount;


voidAwake()
{
m_FaceManager=GetComponent<arfacemanager>();


m_FaceManager.facesChanged+=delegate(ARFacesChangedEventArgsargs)
{
if(args.added.Count>0)
{
varface=args.added[0];

for(vari=0;i<pointindexs.length>{
varindex=PointIndexs[i];
vargo=GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.transform.localScale=Vector3.one*0.01f;

varpos=face.vertices[index];
//設置父物體為人臉,這樣物體會跟隨人臉移動
go.transform.SetParent(face.transform);
go.transform.localPosition=pos;
go.GetComponent<renderer>().material.color=Colors[];

BallMap[index]=go;
}
}

//更新點的位置,added的時候可能mesh還不準確,頂點位置有可能更新
if(args.updated.Count>0)
{
varface=args.updated[0];

foreach(varindexinPointIndexs)
{
varpos=face.vertices[index];
vargo=BallMap[index];
go.transform.localPosition=pos;
}
}
};
}

voidUpdate()
{
if(m_FaceManager.subsystem!=null&&faceInfoText!=null)
{

faceInfoText.text=$"Supportednumberoftrackedfaces:{m_FaceManager.supportedFaceCount}\\n"+
$"Maxnumberoffacestotrack:{m_FaceManager.maximumFaceCount}\\n"+
$"Numberoftrackedfaces:{m_FaceManager.trackables.count}";

//這樣可以在UI上看到頂點的數量
faceInfoText.text+="\\n當前臉部Mesh的頂點數為:"+_verticeCount;

//這樣可以在UI上看到頂點的索引
faceInfoText.text+="\\n離點擊位置最近的頂點索引是:"+_verticeCount;
}

//!!!下面是添加的代碼
if(Input.GetMouseButtonUp(0))
{
varcamera=GetComponent<arsessionorigin>().camera;
varray=camera.ScreenPointToRay(Input.mousePosition);

if(Physics.Raycast(ray,outvarhit,1000))
{
vargo=GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.transform.localScale=Vector3.one*0.01f;
//設置父物體為人臉,這樣物體會跟隨人臉移動
go.transform.SetParent(hit.transform);
go.transform.position=hit.point;

varface=hit.transform.GetComponent<arface>();
//需要在類中創建一個int類型的私有成員
_verticeCount=face.vertices.Length;


varmin=float.MaxValue;
//需要在類中創建一個int類型的私有成員
minIndex=-1;
for(varindex=0;index<face.vertices.length>{
varv=face.vertices[index];
varlocal=hit.transform.InverseTransformPoint(hit.point);

//使用sqrMagnitude可以減少一次開方計算,結果一樣,性能更好

vardistance=(v-local).sqrMagnitude;
if(distance{
minIndex=index;
min=distance;
}
}

}
}
}
}
/<face.vertices.length>/<arface>/<arsessionorigin>/<renderer>/<pointindexs.length>/<arfacemanager>/<code>

通過執行上面的代碼試驗幾次(最好在不同的人臉上測試下),你會發現這些頂點索引是固定的,並不會變化,咱們以後就可以根據這些點的索引來獲取對應位置。

寫一個工具類

人臉對應下面枚舉的點如下圖(以下標註的位置是真人臉上的位置,注意前置相機是左右鏡像狀態):

Unity SenseAR教程:人臉追蹤掛點位置「含源碼」

需要注意一下這個腳本的位置:最好放到<code>Example/Scripts/<code>下面。
不放在這個目錄,<code>Example/Scripts/<code>目錄下的腳本中會找不到這個API。為什麼呢?因為Example/Scripts中有一個ADF文件,相當於把這個目錄的腳本單獨設置成為了一個工程。
更多相關內容請閱讀:

程序集定義(Assembly Definition File)功能詳解

<code>//首發公眾號:洪流學堂
//作者:大智(微信:zhz11235)

usingUnityEngine;
usingUnityEngine.XR.ARFoundation;

//以下位置是真人臉上的位置,注意前置相機是左右鏡像狀態
//參考圖:https://upload-images.jianshu.io/upload_images/78733-0653b6136bd7cd40.png
publicenumFaceAnchor
{
LeftEyeL=10796,
LeftEyeR=10655,
RightEyeL=9218,
RightEyeR=9265,
Nose=8940,
MouthL=10609,
MouthR=9103,
}

publicstaticclassARFaceExtensions
{
///<summary>
///根據錨點獲基於臉部的局部座標
///
///<paramname>
///<paramname>
///<returns>face的局部座標

publicstaticVector3GetAnchor(thisARFaceface,FaceAnchoranchor)
{
intindex=(int)anchor;
if(face.vertices.Length>index)
{
returnface.vertices[index];
}
returnVector3.zero;
}
}
/<code>

上面的點不一定是最準確的點,你可以根據這個思路來進行修改。還可以添加更多錨點的位置,比如額頭、耳朵等。

擴展閱讀

  • 人臉追蹤:射線檢測添加裝飾物

  • SenseAR的手勢識別發射愛心

  • SenseAR的手勢識別2:計算手勢方向

  • 商湯SenseAR全功能初體驗(含填坑經驗)

  • 視頻B站鏈接:https://www.bilibili.com/video/av89332645

  • SenseAR常見問題總結

本教程源碼及後續更新

由於源碼後續可能會更新,就不直接打包傳在這裡了。
本工程的持續更新源碼可以在洪流學堂公眾號回覆<code>face/<code>獲取。


好了,今天就絮絮叨叨到這裡了。
沒講清楚的地方歡迎評論,也可以加我微信討論。

我是大智(VX: zhz11235),你的技術探路者,下次見!


分享到:


相關文章: