译者注:本文是Raywenderlich上《AR Face Tracking Tutorial for iOS: Getting Started》一文的翻译.
在本文中,你将学习如何通过TrueDepth相机来使用AR Face Tracking跟踪你的面部,在你的被跟踪面部覆盖上表情符号,并根据你制作的面部表情来操作表情符号。
资料下载 版本:Swift 4.2, iOS 12, Xcode 10
想象一下。 你刚刚吃过最神奇的韩国BBQ,现在是时候采取自拍来纪念这个场合了。 你拿出你的iPhone,做出最好的鬼脸,然后拍这一餐中自认为最棒的自拍照。 这张照片很好用 -- 但它缺少一些东西。 如果你可以在你的眼睛上放一个表情符号,显示你有多喜欢这顿BBQ就好了。 然而现在,没有一个应用程序可以做类似的事情。要是有一个使用AR Face Tracking技术的app就好了。
好消息! 你可以编写一个应用程序来做到这一点! 在本文中,你将学习如何:
- 通过TrueDepth相机使用AR Face Tracking跟踪你的脸部。
- 在被追踪到的的脸上放上emoji表情。
- 根据你的面部表情操纵emoji。
你准备好了吗? 然后闭上嘴,打开Xcode,准备开始吧!
准备开始
在本文中,你需要一台带有前置TrueDepth相机的iPhone。 在写本文时,只有iPhone X满足条件,但谁知道未来会有什么新型号?
你可能已经下载了本教程开始提供的材料,却发现里面没有初始项目文件.这并不是一个失误.
你将从头开始写完这个app--Emoji Bling.
启动Xcode创建一个Single View App新项目,并将其命名为Emoji Bling。
你应该做的第一件事是为默认的ViewController起个好名字。 在左侧的项目导航器中选择ViewController.swift。
在标准编辑器中显示的代码中,右键单击类的名称ViewController,然后从弹出的上下文菜单中选择Refactor ▸ Rename。
将类的名称更改为EmojiBlingViewController并按回车键或单击蓝色Rename按钮。
注意:有时重构进程会忘记重命名ViewController.swift文件。 如果发生这种情况,只需在Finder中手动执行此操作,然后再将该文件添加到项目中。
由于你已经在使用EmojiBlingViewController.swift,请继续将以下导入添加到头部:
import ARKit
毕竟,你是制作增强现实应用程序的,对吧?
接下来,在Main.storyboard中,选中Emoji Bling View Controller中的顶级View,将类更改为ARSCNView。
ARSCNView是一个使用SceneKit内容显示增强现实体验的特殊视图。 它可以显示摄像头数据流和显示SCNNode节点。
将顶级视图更改为ARSCNView后,你需要为EmojiBlingViewController类中的视图创建IBOutlet。
要执行此操作,请通过单击带有联锁环的按钮调出辅助编辑器。
这应该会自动在辅助编辑器中显示EmojiBlingViewController.swift相关的内容。 如果没有,你可以在项目导航器中按住Option键并单击它来显示。
现在,按住Control键从storyboard中的ARSCNView拖动到EmojiBlingViewController.swift中的EmojiBlingViewController类定义下方,并命名outlet sceneView。
在构建和运行之前,需要一些代码来显示摄像头数据流并开始跟踪你的面部。
在EmojiBlingViewController.swift中,将以下函数添加到EmojiBlingViewController类:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 1
let configuration = ARFaceTrackingConfiguration()
// 2
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// 1
sceneView.session.pause()
}
在视图出现之前,你:
- 创建配置以跟踪面部。
- 使用
ARSCNView的内置ARSession属性运行面部跟踪配置。
在视图消失之前,你需要确保:
- 暂停AR session。
到目前为止,这段代码存在一个很小的问题。 ARFaceTrackingConfiguration仅适用于带有前置TrueDepth摄像头的手机。 在做任何事之前,你需要确保检查这一点。
在同一个文件中,将以下内容添加到viewDidLoad()函数的末尾,该函数应该已经存在:
guard ARFaceTrackingConfiguration.isSupported else {
fatalError("Face tracking is not supported on this device")
}
有了这个,就检查并确保设备支持面部跟踪(即,有一个正面的TrueDepth摄像头),否则就停止。 这不是一个优雅的方式来处理这个,但由于这个应用程序只进行面部跟踪,其他任何东西都是毫无意义的!
在运行应用程序之前,还需要指定在Info.plist中需要使用相机的权限的原因。
在Project导航器中选择Info.plist,然后添加一个带有Privacy - Camera Usage Description键的条目。 它应该默认为String类型。值为,EmojiBling needs access to your camera in order to track your face。
终于现在是时候Build和运行。
当你这样做时,你应该看到你美丽,微笑的脸直视着你。
好了,鬼脸做够了.你还有很多工作要做!
注意:此项目的某些构建步骤可能需要很长时间。 虽然Xcode似乎已经去喝咖啡了,但它可能没有,你只需要耐心等待它。
Face Anchors and Geometries - 面部锚点和几何体
你已经看过ARFaceTrackingConfiguration,它用于配置设备以使用TrueDepth摄像头跟踪你的面部。 很酷吧!。
但是你还需要了解面部追踪的其他信息吗?
你很快就会使用的三个非常重要的类是ARFaceAnchor,ARFaceGeometry和ARSCNFaceGeometry。
ARFaceAnchor继承自ARAnchor。 如果你之前已经使用ARKit做过任何事情,那么你就会知道ARAnchors是如此强大和简单的原因。 它们是ARKit在现实世界中追踪到的位置,当你移动手机时它们不会移动。 ARFaceAnchors还包括有关面部的信息,例如拓扑关系和表情。
ARFaceGeometry正如听起来一样。 它是包含vertices和textureCoordinates的面部的3D描述。
ARSCNFaceGeometry使用来自ARFaceGeometry的数据来创建SCNGeometry,它可用于创建SceneKit节点 - 基本上就是你在屏幕上看到的内容。
好吧,够了。 是时候使用其中一些类了。 回到编码!
Adding a Mesh Mask - 添加网格蒙版
从表面上看,你似乎只打开了前置摄像头。 但是,你没看到的是你的iPhone已经在追踪你的脸了。
看到iPhone正在追踪的内容不是很好吗? 多么巧合,因为这正是你接下来要做的事情!
在EmojiBlingViewController类定义的右大括号后面添加以下代码:
// 1
extension EmojiBlingViewController: ARSCNViewDelegate {
// 2
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
// 3
guard let device = sceneView.device else {
return nil
}
// 4
let faceGeometry = ARSCNFaceGeometry(device: device)
// 5
let node = SCNNode(geometry: faceGeometry)
// 6
node.geometry?.firstMaterial?.fillMode = .lines
// 7
return node
}
}
在这段代码中你:
- 声明
EmojiBlingViewController实现了ARSCNViewDelegate协议。 - 从协议中定义
renderer(_:nodeFor:)方法。 - 确保用于渲染的Metal设备不是nil。
- 创建要由Metal设备渲染的面部几何体。
- 基于面部几何体创建SceneKit节点。
- 将节点材质的填充模式设置为线条。
- 返回节点。
注意:
ARSCNFaceGeometry仅在使用Metal渲染的SceneKit视图中可用,这就是你需要在初始化期间传入Metal设备的原因。 此外,此代码仅在你选择真实硬件时才会编译;如果你选中模拟器,它将无法编译。
在运行之前,你需要将此类设置为ARSCNView的代理。
在viewDidLoad()函数的末尾,添加:
sceneView.delegate = self
好的,到了每个人最喜欢的一步了。 Build并运行该应用程序!
Updating the Mesh Mask - 更新网格蒙版
你是否注意到网格蒙版有点......静态? 当然,当你移动头部时,它会跟踪你的面部位置并随之移动,但是当你眨眼或张开嘴时会发生什么? 没有。
多么令人失望。
幸运的是,这很容易解决。 你只需要添加另一个ARSCNViewDelegate方法!
在ARSCNViewDelegate类扩展的末尾,添加以下方法:
// 1
func renderer(
_ renderer: SCNSceneRenderer,
didUpdate node: SCNNode,
for anchor: ARAnchor) {
// 2
guard let faceAnchor = anchor as? ARFaceAnchor,
let faceGeometry = node.geometry as? ARSCNFaceGeometry else {
return
}
// 3
faceGeometry.update(from: faceAnchor.geometry)
}
在这里,你:
- 定义
renderer(_:didUpdate:for:)协议方法的didUpdate版本。 2)确保正在更新的锚点是ARFaceAnchor,并且节点的几何体是ARSCNFaceGeometry。 3)使用ARFaceAnchor的ARFaceGeometry更新ARSCNFaceGeometry。
现在,当你Build并运行时,你应该看到网格蒙版表单并更改以匹配你的面部表情。
Emoji Bling
如果你还没做完,直接去本文开头或末尾下载本教程的资料代码.
在里面,你会找到一个名为SuperUsefulCode的文件夹,里面有一些Swift文件。 将它们拖到EmojiBlingViewController.swift下面的项目中。 如果需要,请选择Copy items if needed, Create groups,并确保选中Emoji Bling为目标
StringExtension.swift包含String的扩展,可以将String转换为UIImage。
EmojiNode.swift包含一个名为EmojiNode的SCNNode子类,它可以呈现一个String。 它需要一个字符串数组,并可以根据需要循环它们。
你可以随意浏览这两个文件,但深入了解此代码的工作方式超出了本文章的范围。
是时候来增强一下你的鼻子了。没有任何错误.你立即成为了好看的人.:]
在你的EmojiBlingViewController类的顶部,定义下面的常数:
let noseOptions = ["👃", "🐽", "💧", " "]
数组末尾的空白区域使你可以选择清除鼻子。 如果你愿意,可以随意选择其他鼻子选项。
接下来,将以下辅助函数添加到你的EmojiBlingViewController类:
func updateFeatures(for node: SCNNode, using anchor: ARFaceAnchor) {
// 1
let child = node.childNode(withName: "nose", recursively: false) as? EmojiNode
// 2
let vertices = [anchor.geometry.vertices[9]]
// 3
child?.updatePosition(for: vertices)
}
在这里,你:
- 搜索
node,其名称为“nose”且类型为EmojiNode. - 从
ARFaceAnchor的ARFaceGeometry属性获取索引9处的顶点并将其放入数组中。 - 使用
EmojiNode的成员方法根据顶点更新它的位置。 此updatePosition(for :)方法接收顶点数组并将节点的位置设置为其中心。
注意:那么索引9来自哪里? 这是一个神奇的数字。
ARFaceGeometry中有1220个顶点,索引9位于鼻子上。 这目前是可行的,但你稍后将简要介绍使用这些索引常量的危险以及你可以采取的措施。
使用辅助函数来更新单个节点似乎很愚蠢,但是你将在以后加强该函数并严重依赖它。
现在你只需要在面部节点上添加一个EmojiNode。 在renderer(_:nodeFor:)方法中的return语句之前添加以下代码:
// 1
node.geometry?.firstMaterial?.transparency = 0.0
// 2
let noseNode = EmojiNode(with: noseOptions)
// 3
noseNode.name = "nose"
// 4
node.addChildNode(noseNode)
// 5
updateFeatures(for: node, using: faceAnchor)
在此代码中,你:
- 通过使网格蒙版透明来隐藏它。
- 使用你定义的鼻子选项创建一个
EmojiNode。 - 命名鼻子节点,以便稍后找到。
- 将节点添加到面节点。
- 调用你的辅助函数,重新定位面部特征。
你会注意到编译器错误,因为未定义faceAnchor。 要解决此问题,请将同一方法顶部的guard语句更改为以下内容:
guard let faceAnchor = anchor as? ARFaceAnchor,
let device = sceneView.device else {
return nil
}
在运行你的应用程序之前,你还应该做一件事。 在renderer(_:didUpdate:for:)中,在结束括号之前添加对updateFeatures(for:using:):
updateFeatures(for: node, using: faceAnchor)
这将确保当你揉捏脸或摆动鼻子时,表情符号的位置将随着你的动作而更新。
现在是时候Build和运行了!
Changing the Bling - 改变Bling
现在,新的鼻子很好但也许有时你会想要有不同的鼻子?
你将添加代码, 当你点击它们时,来循环切换不同的鼻子。
打开Main.storyboard并找到Tap Gesture Recognizer。 你可以通过打开storyboard右上角的Object Library来找到它。
将其拖动到控制器中的ARSCNView上。
在Main.storyboard仍在Standard editor中打开的情况下,在Assistant editor中打开EmojiBlingViewController.swift,就像之前一样。 现在按住control不动 - 从Tap Gesture Recognizer拖动到你的主EmojiBlingViewController类上。
释放鼠标并添加名为handleTap的Action,其类型为UITapGestureRecognizer。
注意:由于某种原因,你只能按住Ctrl键拖动到原始类定义而不能拖动到扩展名。 但是,如果需要,你可以随后将生成的存根剪切并粘贴到扩展名中。
现在,将以下代码添加到新的handleTap(_ :)方法中:
// 1
let location = sender.location(in: sceneView)
// 2
let results = sceneView.hitTest(location, options: nil)
// 3
if let result = results.first,
let node = result.node as? EmojiNode {
// 4
node.next()
}
在这里,你:
- 在
sceneView中获取点按的位置。 - 执行命中测试以获取点击位置下的节点列表。
- 获取点击位置的第一个(顶部)节点,并确保它是一个
EmojiNode。 - 调用
next()方法将EmojiNode切换到你创建它时使用的列表中的下一个选项。
现在是时候了。 Build和运行。当你点击你的表情符号鼻子时,它会改变。
More Emoji Bling - 更多Emoji Bling
随着表情符号的新发现,现在是时候添加更多的Bling了。
在Emoji Bling ViewController类的顶部,在noseOptions下面添加以下常量选项常量:
let eyeOptions = ["👁", "🌕", "🌟", "🔥", "⚽️", "🔎", " "]
let mouthOptions = ["👄", "👅", "❤️", " "]
let hatOptions = ["🎓", "🎩", "🧢", "⛑", "👒", " "]
如果你愿意,可以再次选择不同的表情符号。
在你的renderer(_:nodeFor:)方法中,在updateFeatures(for:using:)的上面调用,添加其余的子节点定义:
let leftEyeNode = EmojiNode(with: eyeOptions)
leftEyeNode.name = "leftEye"
leftEyeNode.rotation = SCNVector4(0, 1, 0, GLKMathDegreesToRadians(180.0))
node.addChildNode(leftEyeNode)
let rightEyeNode = EmojiNode(with: eyeOptions)
rightEyeNode.name = "rightEye"
node.addChildNode(rightEyeNode)
let mouthNode = EmojiNode(with: mouthOptions)
mouthNode.name = "mouth"
node.addChildNode(mouthNode)
let hatNode = EmojiNode(with: hatOptions)
hatNode.name = "hat"
node.addChildNode(hatNode)
这些面部特征节点就像你已定义的noseNode一样。 唯一略有不同的是设置leftEyeNode.rotation的行。 这导致节点围绕y轴旋转180度。 由于EmojiNodes从两侧都可见,因此这基本上镜像了左眼的表情符号。
如果你现在要运行代码,你会注意到所有新的表情符号都在你的脸部中央,并且不会随着你的脸一起旋转。 这是因为updateFeatures(for:using :)方法到目前为止只更新了鼻子。 其他一切都放在头部的原点。
你真的应该解决这个问题!
在文件的顶部,在hatOptions下面添加以下常量:
let features = ["nose", "leftEye", "rightEye", "mouth", "hat"]
let featureIndices = [[9], [1064], [42], [24, 25], [20]]
features是你为每个要素指定的节点名称数组,featureIndices是ARFaceGeometry中与这些要素对应的顶点索引(还记得那个神奇数字吗?)。
你会注意到" mouth "有两个与之相关的索引。 由于开口是网眼罩中的孔,因此定位嘴表情符号的最佳方法是上嘴唇和下嘴唇的平均位置。
注意:功能的硬编码索引是技术债务的潜在来源。 目前,
ARFaceGeometry有1220个顶点,但如果Apple决定要更高的分辨率会怎样? 突然间,这些索引可能不再符合你的预期。 一个可能的,鲁棒的解决方案是使用Apple的Vision框架来初步检测面部特征并将它们的位置映射到ARFaceGeometry上最近的顶点。 接下来,使用以下内容替换当前的updateFeatures(for:using:):
// 1
for (feature, indices) in zip(features, featureIndices) {
// 2
let child = node.childNode(withName: feature, recursively: false) as? EmojiNode
// 3
let vertices = indices.map { anchor.geometry.vertices[$0] }
// 4
child?.updatePosition(for: vertices)
}
这看起来非常相似,但有一些变化可以解决。 在此代码中,你:
- 循环遍历在类顶部定义的
features和featureIndexes。 - 按功能名称查找子节点,并确保它是
EmojiNode。 - 使用
ARFaceAnchor的ARFaceGeometry属性将索引数组映射到顶点数组。 - 使用这些顶点更新子节点的位置。 开始Build并运行你的应用程序。 我知道你想看看效果。
Blend Shape Coefficients - 混合形状系数
ARFaceAnchor不仅仅包含脸部的几何形状。 它还包含混合形状系数。 混合形状系数描述了你的面部表情的表达程度。 系数范围从0.0(无表情)到1.0(最大表情)。
例如,ARFaceAnchor.BlendShapeLocation.cheekPuff系数在你的脸颊放松时会记录0.0,而当你的脸颊像河豚一样膨胀到最大值时会记录1.0!
目前有52种混合形状系数可供选择。 在Apple’s official documentation中可以查看它们。
Control Emoji With Your Face! - 用你的脸控制表情符号!
在阅读了关于混合形状系数的上一节之后,你是否想知道是否可以使用它们来操纵脸上显示的表情符号? 答案是肯定的。 是的你可以。
Left Eye Blink - 左眼眨眼
在updateFeatures(for:using :)中,就在for循环的右大括号之前,添加以下代码:
// 1
switch feature {
// 2
case "leftEye":
// 3
let scaleX = child?.scale.x ?? 1.0
// 4
let eyeBlinkValue = anchor.blendShapes[.eyeBlinkLeft]?.floatValue ?? 0.0
// 5
child?.scale = SCNVector3(scaleX, 1.0 - eyeBlinkValue, 1.0)
// 6
default:
break
}
在这里,你:
1)在功能名称上使用switch语句。
2)为leftEye实现case。
3)保存节点的x-scale默认为1.0。
4)获取eyeBlinkLeft的混合形状系数,如果未找到则默认为0.0(未眨眼)。
5)根据混合形状系数修改节点的y-scale。
6)实现默认case以使switch语句详尽无遗。
很简单吧? Build并运行!
Right Eye Blink - 右眼眨眼
这与左眼的代码非常相似。 将以下case添加到同一个switch语句中:
case "rightEye":
let scaleX = child?.scale.x ?? 1.0
let eyeBlinkValue = anchor.blendShapes[.eyeBlinkRight]?.floatValue ?? 0.0
child?.scale = SCNVector3(scaleX, 1.0 - eyeBlinkValue, 1.0)
再次Build并运行你的应用程序,你应该能够用双眼眨眼!
Open Jaw -- 张嘴
目前,在应用程序中,如果你张开嘴,口腔表情会留在嘴唇之间,但不再覆盖嘴巴。 这有点奇怪,不是吗?
你现在要解决这个问题。 将以下case添加到同一个switch语句中:
case "mouth":
let jawOpenValue = anchor.blendShapes[.jawOpen]?.floatValue ?? 0.2
child?.scale = SCNVector3(1.0, 0.8 + jawOpenValue, 1.0)
在这里,你使用的是jawOpen混合形状,对于闭合的钳口为0.0,对于开放的钳口为1.0。 等一下......你不能让你的下巴张开但仍然闭上你的嘴巴吗? 是的。然而,另一个选项,mouthClose,似乎并不可靠。 这就是你使用.jawOpen的原因。
继续,最后一次build和运行你的应用程序。
接下来做什么?
哇,你已经完成了非常多的工作!恭喜一下!
你已经学会了在app中,如何将面部表情转换为输入控制。先把玩emoji放在一边。想象一下,一个app能把面部表情转变成捷径来生产力,将会有多棒?或者在游戏中用眨左右眼来控制角色移动,鼓起腮帮来控制角色跳跃?再也不要像野兽一样点击屏幕了!
如果你愿意,你可以在本教程的顶部或底部下载最终完成版项目。
希望你喜欢这个面部追踪的教程。也欢迎分享你的面部emoji表情截屏作品。 Want to go even deeper into ARKit? You’re in luck. There’s a book for that!™ Check out ARKit by Tutorials, brought to you by your friendly neighborhood raywenderlich.com team. 如果你想要更加深入研究ARKit,那么很幸运。这里raywenderlich.com为你准备了一本书ARKit by Tutorials