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),你的技术探路者,下次见!


分享到:


相關文章: