创建基于人脸的AR应用

本文翻译自Creating Face-Based AR Experiences
结合iPhone X的TrueDepth摄像头,进行人脸追踪和表情移植。

概述

这个Demo 实现了基于iPhone X的TrueDepth摄像头的四种AR方式

  • 纯相机,没有任何AR内容。
  • ARKit 传来的面部网格,可以自动估计真实的定向照明环境。
  • 虚拟3D
  • 一个简单的跟随用户表情的机器人
image

在SceneKit View 中创建一个 Face Tracking Session

和其他ARKit用法一样,人脸跟踪需要配置开启一个ARSession类型的会话,并将虚拟内容和相机图像一起渲染到view中。关于session和view创建的更多信息可以参考About Augmented Reality and ARKitBuilding Your First AR Experience 两篇文章。这个例子使用SceneKit显示AR信息,但你也可以使用SpriteKit或者用Metal创建一个渲染器(参考ARSKViewDisplaying an AR Experience with Metal)。

面部跟踪和其他ARKit 中配置session的方法不同。创建一个ARFaceTrackingConfiguration对象,设置一些属性,并传给绑定view的AR session的run(_:options:)方法。具体如下所示。


guard ARFaceTrackingConfiguration.isSupported else { return }
let configuration = ARFaceTrackingConfiguration()
configuration.isLightEstimationEnabled = true
session.run(configuration, options: [.resetTracking, .removeExistingAnchors])

在请求面部跟踪 session之前,需要用ARFaceTrackingConfiguration.isSupported方法判断当前设备是否支持该特性。

跟踪表情的位置和方向

当面部跟踪激活时,ARKit自动添加ARFaceAnchor对象到AR session中,其中包含用户面部的位置和方向信息。

ARKit 只能检测一个面部信息。如果图像中出现多个面部信息,ARKit选择最大的或者最清晰可辨的面孔。

如果使用SceneKit实现的AR效果,你可以在[renderer(_:didAdd:for:)]
(https://developer.apple.com/documentation/arkit/arscnviewdelegate/2865794-renderer)方法中添加与anchor相对应的3D内容(来自ARSCNViewDelegate。ARKit 为每个anchor增加一个SceneKit节点,并在每一帧更新这个节点的位置和方向,因此你添加到该节点的任何SceneKit内容都会自动跟随用户脸部的位置和方向。


func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    // Hold onto the `faceNode` so that the session does not need to be restarted when switching masks.
    faceNode = node
    serialQueue.async {
        self.setupFaceNodeContent()
    }
}

在这个Demo中,[renderer(_:didAdd:for:)]
(https://developer.apple.com/documentation/arkit/arscnviewdelegate/2865794-renderer)方法调用setupFaceNodeContent方法向faceNode中添加SceneKit 内容。比如,如果你改变 showsCoordinateOrigin 变量值,Demo将各个点的 x/y/z 轴可视化,指示面部锚点坐标系的起点。

使用面几何来建模用户脸部

ARKit 提供一个和用户脸部 的大小,形状,拓扑和当前面部表情相匹配的粗略3D网格几何。ARKit 还提供ARSCNFaceGeometry类方便在SceneKit中创建可视化网格。

你可以使用这个网格向脸部绘制一些内容,比如绘制半透明纹理来模拟纹身或者妆效。


// This relies on the earlier check of `ARFaceTrackingConfiguration.isSupported`.
let device = sceneView.device!
let maskGeometry = ARSCNFaceGeometry(device: device)!

Demo 里的 setupFaceNodeContent 方法在场景中添加了一个包含面几何的节点。通过使该节点成为由脸部锚点提供的节点的子节点,脸部模型自动跟踪用户脸部的位置和方向。

为了能在用户眨眼,说话或者做其他面部动作时也让屏幕脸部和真实用户保持一致,你还需要在renderer(_:didUpdate:for:)回调中更新面部网格。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let faceAnchor = anchor as? ARFaceAnchor else { return }
    
    virtualFaceNode?.update(withFaceAnchor: faceAnchor)
}

然后更新ARSCNFaceGeometry 对象。

func update(withFaceAnchor anchor: ARFaceAnchor) {
    let faceGeometry = geometry as! ARSCNFaceGeometry
    faceGeometry.update(from: anchor.geometry)
}

在脸上渲染3D内容

ARKit 提供的另一种面网格的用法是在场景中创建封闭几何。一个封闭集合是一个不渲染任何可见物体的3D模型,但会阻碍相机对场景中其他虚拟内容的视图。

这种技术创造出真实面对与虚拟对象交互的错觉,即使脸部是2D摄像机图像,虚拟内容是渲染的3D对象。 例如,如果您将遮挡几何图形和虚拟眼镜放置在用户的脸部上,则脸部可能会遮挡眼镜框架。

要创建面部的遮挡几何,请先如上例所示创建一个ARSCNFaceGeometry对象。 但是,不要使用可见的外观来配置该对象的SceneKit材质,而是在渲染期间将材质设置为渲染深度而不是颜色:

geometry.firstMaterial!.colorBufferWriteMask = []
occlusionNode = SCNNode(geometry: geometry)
occlusionNode.renderingOrder = -1

因为材料呈现深度,所以SceneKit渲染的其他对象正确地出现在它的前面或后面。 但是由于材质不会呈现颜色,相机图像会出现在其位置。 Demo将此技术与位于用户眼睛前方的SceneKit对象相结合,创建一个物体被用户的鼻子遮蔽的效果。

用Blend Shapes动画化一个人物

除了上述示例中所示的面部网格之外,ARKit还提供了一个更为抽象的用户面部表情模型,其形式为blendShapes字典。 您可以使用该字典中的命名系数值来控制自己的2D或3D资产的动画参数,创建遵循用户真实面部动作和表达的角色(如头像或木偶)。

作为混合形状动画的基本演示,此示例包含使用SceneKit原始形状创建的机器人角色头部的简单模型。 (请参阅源代码中的robotHead.scn文件。)

renderer:didUpdateNode:forAnchor:回调中读取face Anchor的blendShapes字典内容以获取用户当前真实表情。

func update(withFaceAnchor faceAnchor: ARFaceAnchor) {
    blendShapes = faceAnchor.blendShapes
}

然后,检查该字典中的键值对以计算模型的动画参数。 有52个独特的ARBlendShapeLocation系数。 您的应用程序可以使用尽可能少的或很多的必要条件来创建您想要的艺术效果。 在此示例中,RobotHead类执行此计算,将ARBlendShapeLocationEyeBlinkLeftARBlendShapeLocationEyeBlinkRight参数映射到机器人眼睛的scale的一个轴以及ARBlendShapeLocationJawOpen参数,以抵消机器人下颚的位置。

var blendShapes: [ARFaceAnchor.BlendShapeLocation: Any] = [:] {
    didSet {
        guard let eyeBlinkLeft = blendShapes[.eyeBlinkLeft] as? Float,
            let eyeBlinkRight = blendShapes[.eyeBlinkRight] as? Float,
            let jawOpen = blendShapes[.jawOpen] as? Float
            else { return }
        eyeLeftNode.scale.z = 1 - eyeBlinkLeft
        eyeRightNode.scale.z = 1 - eyeBlinkRight
        jawNode.position.y = originalJawY - jawHeight * jawOpen
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容