洪流學堂,讓你快人幾步。你好,我是你的技術探路者鄭洪智,你可以叫我大智(VX: zhz11235)。
上次咱們一起探索了人臉追蹤,並且實現了通過點擊往臉上“添彩”的功能。
但是很多時候,咱們想識別出臉部後直接在探索SenseAR中人臉追蹤眼睛、鼻子、嘴巴掛點的位置,還送你一個開箱即用的擴展工具類哦~臉上給它添加一些裝飾物,而不需要玩家手動點擊臉上才能添加上。上一篇最後也給你了一些思路,就是根據射線檢測到的點,計算出離點擊點最近的頂點,這個頂點順序大概率是不會變的,可以作為錨定的座標點。咱們這節課一起使用這個思路來探索一下是否可行。
對SenseAR還不太熟悉的同學可以看下大智的視頻:
商湯SenseAR全功能初體驗(含填坑經驗)
視頻B站鏈接:https://www.bilibili.com/video/av89332645
最終效果
首先要給你顆定心丸,上面的思路是可行的。這次不需要手動點擊往臉上放小球了,小球可以直接出現!
開工
想要達成今天的目標,咱們需要依次解決以下幾個問題:
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{ /<face.vertices.length>/<arface>/<arsessionorigin>/<code>
minIndex=index;
min=distance;
}
}
}
}
}
我找到的幾個點索引是:
<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{ /<face.vertices.length>/<arface>/<arsessionorigin>/<renderer>/<pointindexs.length>/<arfacemanager>/<code>
minIndex=index;
min=distance;
}
}
}
}
}
}
通過執行上面的代碼試驗幾次(最好在不同的人臉上測試下),你會發現這些頂點索引是固定的,並不會變化,咱們以後就可以根據這些點的索引來獲取對應位置。
寫一個工具類
人臉對應下面枚舉的點如下圖(以下標註的位置是真人臉上的位置,注意前置相機是左右鏡像狀態):
需要注意一下這個腳本的位置:最好放到<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),你的技術探路者,下次見!
閱讀更多 洪流學堂 的文章