欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~

本文来自于腾讯Bugly公众号(weixinBugly), 作者:jennysluo,未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/YJ82vQYHAMmtueDgHgKcNA

iPhone X前置深度摄像头带来了Animoji和face ID,同时也将3D Face Tracking的接口开放给了开发者。有幸去Cupertino苹果总部参加了iPhone X的封闭开发,本文主要分享一下iPhone X上使用ARKit进行人脸追踪及3D建模的相关内容。

iPhone X前置深度摄像头识别人脸视频

新增接口

ARFaceTrackingConfiguration

ARFaceTrackingConfiguration利用iPhone X前置深度摄像头识别用户的人脸。由于不同的AR体验对iOS设备有不同的硬件要求,所有ARKit配置要求iOS设备至少使用A9及以上处理器,而face tracking更是仅在带有前置深度摄像头的iPhone X上才会有。因此在进行AR配置之前,首先我们需要确认用户设备是否支持我们将要创建的AR体验

ARFaceTrackingConfiguration.isSupported

对于不支持该ARKit配置的设备,提供其它的备选方案或是降级策略也是一种不错的解决方案。然而如果你的app确定ARKit是其核心功能,在info.plist里将ARKit添加到UIRequiredDeviceCapabilities里可以确保你的app只在支持ARKit的设备上可用。
当我们配置使用ARFaceTrackingConfigurationsession会自动添加ARFaceAnchor对象到其anchor list中。每一个face anchor提供了包含脸部位置,方向,拓扑结构,以及表情特征等信息。另外,当我们开启isLightEstimationEnabled设置,ARKit会将检测到的人脸作为灯光探测器以估算出的当前环境光的照射方向及亮度等信息(详见ARDirectionalLightEstimate对象),这样我们可以根据真实的环境光方向及强度去对3D模型进行照射以达到更为逼真的AR效果。

ARFrame

当我们设置为基于人脸的AR(ARFaceTrackingConfiguration),session刷新的frame里除了包含彩色摄像头采集的颜色信息以外(capturedImage),还包含了由深度摄像头采集的深度信息(capturedDepthData)。其结构和iPhone7P后置双摄采集的深度信息一样为AVDepthData。当设置其它AR模式时该属性为nil。在iPhone X上实测效果比7P后置的深度信息更为准确,已经可以很好的区分人像和背景区域。

需注意的是,深度摄像头采样频率和颜色摄像头并不一致,因此ARFrame的capturedDepthData属性也可能是nil。实测下来在帧率60的情况下,每4帧里有1帧包含深度信息。

ARFaceAnchor

前面说过,当我们配置使用ARFaceTrackingConfigurationsession会自动添加ARFaceAnchor对象到其anchor list中。每一个face anchor提供了包含脸部位置,方向,拓扑结构,以及表情特征等信息。比较遗憾的是,当前版本只支持单人脸识别,未来如果ARKit提供多人脸识别后开发者应该也能较快的进行版本升级。

  • 人脸位置和方向

    父类ARAnchor的transform属性以一个4*4矩阵描述了当前人脸在世界坐标系的位置及方向。我们可以使用该矩阵来放置虚拟3D模型以实现贴合到脸部的效果(如果使用SceneKit,会有更便捷的方式来完成虚拟模型的佩戴过程,后面会详述)。该变换矩阵创建了一个“人脸坐标系”以将其它模型放置到人脸的相对位置,其原点在人头中心(鼻子后方几厘米处),且为右手坐标系—x轴正方向为观察者的右方(也就是检测到的人脸的左方),y轴正方向延人头向上,z轴正方向从人脸向外(指向观察者)

  • 人脸拓扑结构 ARFaceGeometry

    ARFaceAnchor的geometry属性封装了人脸具体的拓扑结构信息,包括顶点坐标、纹理坐标、以及三角形索引(实测下来单个人脸包含1220个3D顶点以及2304个三角面片信息,精准度已经相当高了)。

有了这些数据,我们可以实现各种贴合人脸的3D面皮—比如虚拟妆容或者纹身等。我们也可以用其创建人脸的几何形状以完成对虚拟3D模型的遮挡。
如果我们使用SceneKit + Metal做渲染,可以十分方便的通过ARSCNFaceGeometry完成人脸建模,后面会详细说明。
  • 面部表情追踪

    blendShapes属性提供了当前人脸面部表情的一个高阶模型,表示了一系列的面部特征相对于无表情时的偏移系数。听起来也许有些抽象,具体来说,可以看到blendShapes是一个NSDictionary,其key有多种具体的面部表情参数可选,比如ARBlendShapeLocationMouthSmileLeft代表左嘴角微笑程度,而ARBlendShapeLocationMouthSmileRight表示右嘴角的微笑程度。每个key对应的value是一个取值范围为0.0 - 1.0的浮点数,0.0表示中立情况的取值(面无表情时),1.0表示最大程度(比如左嘴角微笑到最大值)。ARKit里提供了51种非常具体的面部表情形变参数,我们可以自行选择采用较多的或者只是采用某几个参数来达成我们的目标,比如,用“张嘴”、“眨左眼”、“眨右眼”来驱动一个卡通人物。

创建人脸AR体验

以上介绍了一下使用ARKit Face Tracking所需要了解的新增接口,下面来详细说明如何搭建一个app以完成人脸AR的真实体验。

创建一个ARKit应用可以选择3种渲染框架,分别是SceneKit,SpriteKit和Metal。对于做一个自拍类的app,SceneKit无疑是一种很好的选择。其接口方便易用,底层使用Metal2渲染,且提供了多种材质以及光照模型,通常情况下无需自定义shader即可完成3D贴脸以及3D挂件的渲染。首先我们需要添加一个ARSCNView,设置好scene以及delegate,在viewWillAppear里添加下面两行代码

ARFaceTrackingConfiguration *configuration = [ARFaceTrackingConfiguration new];
[self.sceneView.session runWithConfiguration:configuration];

这样就创建好了一个ARKit Face Tracking的场景,此时前置摄像头已经开启并实时检测/追踪人脸信息。当检测到人脸之后,我们可以通过delegate更新人脸anchor的函数来同步更新我们自定义的3D面皮或者3D模型。

- (void)renderer:(id <SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor;
- (void)renderer:(id <SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor;

比如我们要放置一张京剧脸谱贴合到用户脸上,我们可以生成一个脸谱的SCNNode

- (SCNNode *)textureMaskNode
{
if (!_textureMaskNode) {
_textureMaskNode = [self makeFaceGeometry:^(SCNMaterial *material) {
material.fillMode = SCNFillModeFill;
material.diffuse.contents = [UIImage imageNamed:@"maskImage.png"];
} fillMesh:NO];
_textureMaskNode.name = @"textureMask";
}
return _textureMaskNode;
} - (SCNNode*)makeFaceGeometry:(void (^)(SCNMaterial*))materialSetup fillMesh:(BOOL)fillMesh
{
#if TARGET_OS_SIMULATOR
return [SCNNode new];
#else
id<MTLDevice> device = self.sceneView.device; ARSCNFaceGeometry *geometry = [ARSCNFaceGeometry faceGeometryWithDevice:device fillMesh:fillMesh];
SCNMaterial *material = geometry.firstMaterial;
if(material && materialSetup)
materialSetup(material); return [SCNNode nodeWithGeometry:geometry];
#endif
}

注意这个fillMesh参数,如果设置为NO,生成的“蒙皮”眼睛和嘴巴区域是镂空的,反之亦然。模型建好以后,我们需要在face anchor刷新的时候同步更新3D蒙皮的几何信息使其与人脸达到贴合的状态。

- (void)renderer:(id<SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
ARFaceAnchor *faceAnchor = (ARFaceAnchor *)anchor;
if (!faceAnchor || ![faceAnchor isKindOfClass:[ARFaceAnchor class]]) {
return;
} if (_needRenderNode) {
[node addChildNode:self.textureMaskNode];
_needRenderNode = NO;
} ARSCNFaceGeometry *faceGeometry = (ARSCNFaceGeometry *)self.textureMaskNode.geometry;
if( faceGeometry && [faceGeometry isKindOfClass:[ARSCNFaceGeometry class]] ) {
[faceGeometry updateFromFaceGeometry:faceAnchor.geometry];
}
}

这里我们是直接将蒙皮node添加到face node作为其childNode,因而不需要对其位置信息做额外处理就能跟随人脸移动。如果是直接加到场景的rootNode上面,还需要同步更新其位置、方向等属性。打上方向光之后,蒙皮显得十分贴合立体。

SCNLight *directional = [SCNLight light];
directional.type = SCNLightTypeDirectional;
directional.color = [UIColor colorWithWhite: alpha:1.0];
directional.castsShadow = YES; _directionalLightNode = [SCNNode node];
_directionalLightNode.light = directional;

demo里我们做了一个戏剧变脸效果,当用户遮挡人脸后将其脸谱换掉。实现的原理是当用户人脸检测不到时记一个标志,再次检测到用户人脸时将其3D蒙皮的贴图换掉。比较坑的是,ARKit 检测不到人脸时也并未将其node移除,因此delegate也没有回调

- (void)renderer:(id <SCNSceneRenderer>)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor;

那么如何知道face tracking失败呢?可以通过每一帧刷新的时候遍历查找到ARAnchor,检测其isTrackFace状态。

- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
{
for (ARAnchor *anchor in frame.anchors) {
if ([anchor isKindOfClass:[ARFaceAnchor class]]) {
ARFaceAnchor *faceAnchor = (ARFaceAnchor *)anchor;
self.isTrackFace = faceAnchor.isTracked;
}
}
}

同样的,我们可以在人脸node上添加其他3D模型(比如3D眼镜)的node使其跟随人脸移动,可以达到非常逼真的效果,SceneKit支持多种格式的模型加载,比如obj、dae等。如果使用的是dae且不是放在bundle里面,需要提前用scntool压缩,模型加载及动画播放所遇到的坑此处不赘述。需要注意的是,当我们给用户戴上3D眼镜或帽子的时候,我们当然是希望模型的后面部分能正确的被用户的脸给挡住以免露出马脚。因此我们需要渲染一个用来遮挡的node并实时更新其几何信息,使用户在头歪向一边的时候3D眼镜的镜架能被人脸正确遮挡。

- (SCNNode *)occlusionMaskNode
{
if (!_occlusionMaskNode) {
_occlusionMaskNode = [self makeFaceGeometry:^(SCNMaterial *material) {
material.colorBufferWriteMask = SCNColorMaskNone;
material.lightingModelName = SCNLightingModelConstant;
material.writesToDepthBuffer = true;
} fillMesh:YES];
_occlusionMaskNode.renderingOrder = -;
_occlusionMaskNode.name = @"occlusionMask";
}
return _occlusionMaskNode;
}

同样的我们需要在face anchor刷新的时候通过updateFromFaceGeometry:更新其几何信息。需要注意的是,由于ARKit只对人脸区域进行建模,在3D模型设计的时候还需去掉一些不必要的部件:比如眼镜的模型就不需要添加镜脚,因为耳朵部分并没有东西可以去做遮挡。

3D模型设计的时候还需去掉一些不必要的部件效果视频

如果要做类似上面视频中的镜片反射效果,使用SceneKit也十分方便,只需要将镜片的反射贴图(SCNMaterial的reflective属性)映射到cube map即可,支持以下4种设置方案

  1. A horizontal strip image where 6 * image.height == image.width
  2. A vertical strip image where image.height == 6 * image.width
  3. A spherical projection image (latitude/longitude) where 2 * image.height == image.width
  4. A NSArray of 6 images. This array must contain images of the exact same dimensions, in the following order, in a left-handed coordinate system: +X, -X, +Y, -Y, +Z, -Z (or Right, Left, Top, Bottom, Front, Back).

除了人脸的空间位置信息和几何信息,ARKit还提供了十分精细的面部表情形变参数,用来做类似张嘴触发是完全没问题的,我们还可以用其实现一些有趣的效果。比如,根据脸部微笑的程度去替换3D蒙皮的diffuse贴图,使用户笑的时候会出现夸张的效果。

- (UIImage *)meshImageWithBlendShapes:(NSDictionary *)blendShapes
{
if (self.diffuseArray.count == )
return nil; NSUInteger _count = self.diffuseArray.count;
NSNumber *smileLeft = blendShapes[ARBlendShapeLocationMouthSmileLeft];
NSNumber *smileRight = blendShapes[ARBlendShapeLocationMouthSmileRight]; CGFloat smileBlend = (smileLeft.floatValue + smileRight.floatValue) / ;
smileBlend = smileBlend - 0.1;
if (smileBlend < 0.0) smileBlend = 0.0;
NSUInteger index = (NSUInteger)(smileBlend * _count / 0.5);
if (index > _count - ) {
index = _count - ;
} return self.diffuseArray[index];
}

将几个脸部表情系数的组合映射到一个具体的分值,可以实现face dance那样有趣的表情模仿。还可以将其映射到3D虚拟人物的形变上以实现animoji的效果,此处开发者们可自行脑洞大开:)

拍照 & 录制

可能是由于SceneKit原本是设计用来做游戏渲染的框架,只提供了一个截屏的接口snapshot,拍照尚可调用,而录制并不是特别方便。如果你计划通过SCNRenderer 的函数

+ (instancetype)rendererWithContext:(nullable EAGLContext *)context options:(nullable NSDictionary *)options;

将其放在OpenGL context里渲染,可以避开视频录制的坑,但也许会遇到更新人脸geometry等其他问题。如果采用默认的Metal方案,设置一个定时器,将snapshot获取到的UIImage转成pixel buffer再进行视频编码,很难做到每秒30帧的同步输出。如果你的app在录制的时候UI非常干净,可以采用系统录屏框架replaykit来进行屏幕录制;如果你想完全掌控每一帧的输出以方便在录制过程中加上水印,可以用SCNRenderer的render函数

- (void)renderAtTime:(CFTimeInterval)time viewport:(CGRect)viewport commandBuffer:(id <MTLCommandBuffer>)commandBuffer passDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor

将场景渲染到一个id对象中,通过纹理绑定的方式将其转换为CVPixelBufferRef以完成视频编码。某位朋友提醒,可以通过method swizzling的方式直接获取CAMetalLayer的nextDrawable,甚至可以避免上诉方案录制时产生的额外GPU开销,有兴趣的朋友可以尝试一下。

写在末尾

这次能有机会参加Apple的封闭开发且是如此有趣的模块,在没有网络的情况下摸索着做出demo,接触到了最前沿的AR相关技术,对我来说是一份非常宝贵的经历。心怀感恩,踏步前行。

demo效果视频

相关阅读

网页加速特技之 AMP

表格行与列边框样式处理的原理分析及实战应用

EB级别云存储是如何涨成的?

此文已由作者授权腾讯云技术社区发布,转载请注明原文出处

原文链接:https://cloud.tencent.com/community/article/388360?utm_source=bky

海量技术实践经验,尽在腾讯云社区! https://cloud.tencent.com/community

《 iPhone X ARKit Face Tracking 》的更多相关文章

  1. 《Qt on Android核心编程》介绍

    <Qt on Android核心编程>最终尘埃落定.付梓印刷了. 2014-11-02更新:china-pub的预售链接出来了.折扣非常低哦. 封面 看看封面的效果吧,历经几版,最终就成了 ...

  2. 腾讯WeTest发布《2017中国移动游戏质量白皮书》,专注手游品质提升

    1月8日,腾讯质量开放平台WeTest正式发布<2017中国移动游戏质量白皮书>. 刚刚过去的这一年,市场逐渐成熟,中国移动互联网由增量市场转向存量市场.中国移动游戏市场急剧变化,真正的精 ...

  3. 《Start Developing iOS Apps Today》摘抄

    原文:<Start Developing iOS Apps Today> Review the Source Code 入口函数main.m #import <UIKit/UIKit ...

  4. 【产品】张小龙《微信背后的产品观》之PPT完整文字版

    张小龙<微信背后的产品观>之PPT完整文字版 附:PPT下载地址:https://wenku.baidu.com/view/99d2910290c69ec3d5bb7573.html  微 ...

  5. 新书介绍 -- 《Redis核心原理与实践》

    大家好,今天给大家介绍一下我的新书 -- <Redis核心原理与实践>. 后端开发的同学应该对Redis都不陌生,Redis由于性能极高.功能强大,已成为业界非常流行的内存数据库. < ...

  6. 《JavaScript设计模式与开发实践》整理

    最近在研读一本书<JavaScript设计模式与开发实践>,进阶用的. 一.高阶函数 高阶函数是指至少满足下列条件之一的函数. 1. 函数可以作为参数被传递. 2. 函数可以作为返回值输出 ...

  7. 《LoadRunner12七天速成宝典》来了

    看到自己的新书又要发行了,算算从09年第一本书开始,不知不觉已经是第四本书了(帮朋友合写的书不算),每次写完之后都会说太累了,不想再写了,但是却又次次反悔,吞下食言的苦果.如果非要说第四本书的感受,那 ...

  8. 《连载 | 物联网框架ServerSuperIO教程》2.服务实例的配置参数说明

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍  <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制 一.综述 SuperIO(SIO)定位 ...

  9. 《连载 | 物联网框架ServerSuperIO教程》- 3.设备驱动介绍

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...

随机推荐

  1. DevOps之网络

    唠叨话 关于德语噢屁事的知识点,仅提供专业性的精华汇总,具体知识点细节,参考教程网址,如需帮助,请留言. <网络(Network)> 关于网络的网络架构和网络模型:知识与技能的层次(知道. ...

  2. akka源码导读

    akka的actor模型提供了强大的并发,本人就akka源码进行了详细的阅读,下面是一些体会. 1.object SystemMessageList: @tailrec private[sysmsg] ...

  3. kubeernetes节点资源限制

    实际应用中发现,部分节点性能不足,某些较大的服务如果跑在这些机器上.会很快消耗该机器的内存和cpu资源,如果用uptime看一下的就会发现负载特别高(合理的范围这个值应该等于cpu个数),高到一定值就 ...

  4. git subtree pull 错误 Working tree has modifications

    git subtree 是不错的东西,用于 git 管理子项目. 本文记录我遇到问题和翻译网上的答案. 当我开始 pull 的时候,使用下面的代码 git subtree pull --prefix= ...

  5. JUnit5 安装与使用

    虽然JUnit5 的测试版本早就出来了,但正式版直到几年9月份推出,目前最新版5.0.1.几乎所有的Java 开发人员都会使用JUnit 来做测试,但其实很多自动化测试人员也会使用Junit .目前, ...

  6. LINUX 笔记-wc命令

    命令参数: -c 统计字节数. -l 统计行数. -m 统计字符数.这个标志不能与 -c 标志一起使用. -w 统计字数.一个字被定义为由空白.跳格或换行字符分隔的字符串.

  7. 【Telerik控件学习】-建立自己的图形编辑工具(Diagram)

    Telerik提供了RadDiagram控件,用于图形元素的旋转,拖拽和缩放.更重要的是,它还拓展了许多绑定的命令(复制,剪切,粘贴,回退等等). 我们可以用来组织自己的图形编辑工具. Step1.定 ...

  8. 使用jQuery判断元素是否在可视区域

    $("#app").offset().top; offset().top表示 绝对偏移值,比如说有一个很长的页面,#app这个元素 在最底下,  $("#app" ...

  9. (转)IDEA破解 2017 IDEA license server 激活(可用)

    进入ide主页面,help-register-license server,然后输入 http://idea.iteblog.com/key.PHP(注意:php要小写)即可~

  10. 走近 Python (类比 JS)

    Python 是一门运用很广泛的语言,自动化脚本.爬虫,甚至在深度学习领域也都有 Python 的身影.作为一名前端开发者,也了解 ES6 中的很多特性借鉴自 Python (比如默认参数.解构赋值. ...