在开始这篇 ARKit 教学之前,让我快速说明一下相机的不同部分。原深感测镜头和大部分的 iPhone/ iPad 的前置相机一样,有内嵌麦克风、具七百万像素的相机、环境光源侦测器 (ambient light sensor)、距离感应器 (proximity sensor)、与扬声器。而原深感测镜头最独特的地方,就是独有的测绘点投射器 (dot projector)、泛光照射器 (flood illuminator)、与红外线镜头 (infrared camera)。
测绘点投射器会投射超过 3 万个隐形测绘点到使用者的脸上,以建构成一个面部测绘图(文章后部会提到);而红外线相机就会读取测绘点,撷取成一张红外线影像,并传送资料到 Apple A12 Bionic 处理器作比对;最后,泛光照射器就会利用红外线,方便在黑暗环境辨识容貌。
这些不同的感测元件整合在一起,就可以创造如 Animojis 和 Memojis 这种神奇体验。有了原深感测镜头,我们就可以利用使用者脸部和头部的 3D 模型,为 App 建立更多特殊效果。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:1012951431,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
教学范例专案
我相信对于开发者而言,最重要的就是学会使用原深感测镜头,应用脸部追踪功能,为使用者建构令人惊艳的脸部辨识体验。在本篇教学中,我将会说明如何利用 ARKit 框架中的 ARFaceTrackingConfiguration
,去透过 3 万个测绘点辨识不同的脸部动作。
最终成果会是这样的:
让我们开始吧!
你需要在 iPhone X、XS、XR、或 iPad Pro(第三代)上执行这个专案,因为只有这些机型才支援原深感测镜头。文章中,我们会使用 Swift 5 和 Xcode 10.2。
编注:如果你对 ARKit 还不熟悉,可以参考我们另一篇 ARKit 教学。
建立 ARKit 范例来追踪脸部动作
首先,打开 Xcode 建立一个新 Xcode 专案。在 Templates 下,请确认选择的是 iOS 的 Augmented Reality App。
接著,请为专案命名。我把专案命名为 True Depth。请确认你设定了 Language 为 Swift,Content Technology 为 SceneKit。
前往 Main.storyboard
, 这裡应该有一个 Single View,而当中的 ARSCNView
应该已经连接到程式码上。
我们真正需要做的其实非常简单!我们只需要加入一个 UIView
,再在 View 内加入一个 UILabel
。这个 Label 将会告知使用者,他们正在展示甚麽脸部表情。
将 UIView
拖拉至 ARSCNView
内,然后来设定条件。将 Width 设定为 240pt,Height 为 120pt,然后再设定 Left 与 Bottom 为 20pt。
为了美观的设计,让我们将 View 的 alpha 设为 0.8。现在,将 UILabel
拉到你刚完成的 view 内,再将所有的边设为 8pt。
最后,把 Label 对齐设置到中间。设置好后,你的 storyboard 应该会像这样:
现在,让我们来设定 IBOutlets
与 ViewController.swift
连接。转换到 Assistant editor 模式,按住 Control 键点选 UIView
和 UILabel
,然后将它们拉到 ViewController.swift
来建立 IBOutlets
。
你应该可以建构两个 outlet:faceLabel
和 labelView
。
建立脸部网格 (Face Mesh)
因为我们选择了 Augmented Reality App 为程式码范本,范本内有些不需要的程式码,所以让我们先把程式码整理一下吧。将 viewDidLoad
方法改成这样:
override func viewDidLoad() {
super.viewDidLoad()
// 1
labelView.layer.cornerRadius = 10
sceneView.delegate = self
sceneView.showsStatistics = true
// 2
guard ARFaceTrackingConfiguration.isSupported else {
fatalError("Face tracking is not supported on this device")
}
}
依照原来的范本,程式码将会读取一个 3D scene;但我们并不需要这个 scene,所以我们删除了它。然后,我们可以在 project navigator 内删除 art.scnassets
资料夹。最后,我们增加两段程式码到 viewDidLoad
方法内:
- 首先,我们会把
labelView
的边角设置为圆角。这其实只是一个设计偏好。 - 接著,我们需要确认机型是否支援
ARFaceTrackingConfiguration
。这是我们用来建立脸部网格的 AR 追踪功能,如果我们没有确认的话,程式就会崩溃;而如果使用的机型不支援设定的话,就将会呈现错误讯息。
接著,我们将在 viewWillAppear
方法内改变一行程式码:将常数 configuration
改为 ARFaceTrackingConfiguration()
。如此一来,你的程式码应该会像这样:
然后,我们需要加入 ARSCNViewDelegate
方法。将下列程式码加到 // MARK: - ARSCNViewDelegate
的下方:
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let faceMesh = ARSCNFaceGeometry(device: sceneView.device!)
let node = SCNNode(geometry: faceMesh)
node.geometry?.firstMaterial?.fillMode = .lines
return node
}
这个程式码将在 ARSCNView
呈现时执行。首先,我们建立 sceneView
的脸部图形资讯,并将它设给一个常数 faceMesh
。然后,我们将此图形资讯指派给 SCNNode
,并设置材料 (material) 为 node
。对于 3D 物件而言,这个材料通常是 3D 物件的颜色或纹理 (texture)。
为了建构脸部网格,你可以使用两种材料:填满材料 (fill material) 或线材料 (lines material)。我比较倾向使用线材料,所以在程式码中设定了 fillMode = .lines
,但你可以自由选用。现在你的程式码应该像是这样:
如果你执行这个 App,你应该会看到这样的画面:
更新脸部网格
你可能会注意到,脸部网格并没有随著你的表情变化(如眨眼、微笑、或打哈欠等)而更新。这是因为我们还需要在 renderer(_nodeFor)
方法下,加入一个 renderer(_didUpdate:)
。
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry {
faceGeometry.update(from: faceAnchor.geometry)
}
}
每当 sceneView
更新,这段程式码就会执行。首先,我们定义一个 faceAnchor
为 sceneView
内脸部被侦测到的锚点 (anchor)。这个锚点是当执行脸部追踪 AR session 时,被侦测脸部的姿态 、拓扑 (topology)、表情的资料。我们也定义一个叫 faceGeometry
的常数,这是为被侦测脸部的拓扑资料。利用这两个常数,我们就可以每次都更新 faceGeometry
。
再次执行程式码,现在每当你改变脸部表情时,脸部网格就会以 60 帧率 (fps) 更新。
分析脸部表情变化
首先,在档案最顶部建立一个变数:
var analysis = ""
接著,在档案最下方建立下列函式:
func expression(anchor: ARFaceAnchor) {
// 1
let smileLeft = anchor.blendShapes[.mouthSmileLeft]
let smileRight = anchor.blendShapes[.mouthSmileRight]
let cheekPuff = anchor.blendShapes[.cheekPuff]
let tongue = anchor.blendShapes[.tongueOut]
self.analysis = ""
// 2
if ((smileLeft?.decimalValue ?? 0.0) + (smileRight?.decimalValue ?? 0.0)) > 0.9 {
self.analysis += "You are smiling. "
}
if cheekPuff?.decimalValue ?? 0.0 > 0.1 {
self.analysis += "Your cheeks are puffed. "
}
if tongue?.decimalValue ?? 0.0 > 0.1 {
self.analysis += "Don't stick your tongue out! "
}
}
上面函式以一个 ARFaceAnchor
为一个函数。
blendShapes
是一个命名系数的字典,根据特定脸部表情变化,表示检测到的脸部表情。Apple 提供超过 50 种以上的系数,来侦测不同的脸部表情变化。以我们的需求而言,我们仅会使用 4 种:mouthSmileLeft
、mouthSmileRight
、cheekPuff
、和tongueOut
。- 我们使用系数来确认脸部执行这些表情的概率。为了检测微笑,我们加入嘴巴的右侧和左侧的概率。我发现,设定微笑的概率为 0.9、脸颊和舌头的概率为 0.1 的效果最好。
我们採用可能的值,并将文本添加到 analysis
字串中。
我们已经建立好方法了,现在来更新 renderer(_didUpdate:)
方法吧!
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry {
faceGeometry.update(from: faceAnchor.geometry)
expression(anchor: faceAnchor)
DispatchQueue.main.async {
self.faceLabel.text = self.analysis
}
}
}
现在每当 sceneView
更新,就会执行 expression
方法。因为这个函数设定 analysis
字串,所以我们终于可以设定 faceLabel
的文本到 analysis
字串上。
我们完成所有的程式码囉!执行程式码,你应该会得到文章开头的成果。
总结
利用 ARKit 开发基于脸部的使用者体验这个功能,背后还有很多可能性。游戏和 App 可以将原深感测镜头用于各种用途。我最爱的其中一个 App 就是 Hawkeye Access,这是一个让使用者可以用眼睛来控制的浏览器。
如果你想了解更多关于原深感测镜头的相关资讯,你可以看看 Apple 的官方影片 Face Tracking with ARKit。你也可以在 GitHub 上下载本次教学的专案。