iOS开发 · iOS音视频开发 - ARKit 教学:利用 ARKit 侦测与追踪脸部动作 建立绚丽的使用者体验

1,822 阅读7分钟

在开始这篇 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 万个测绘点辨识不同的脸部动作。

最终成果会是这样的:

true-depth-camera-demo

让我们开始吧!

你需要在 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

arkit-face-tracking-1

前往 Main.storyboard, 这裡应该有一个 Single View,而当中的 ARSCNView 应该已经连接到程式码上。

arkit-face-tracking-2

我们真正需要做的其实非常简单!我们只需要加入一个 UIView,再在 View 内加入一个 UILabel。这个 Label 将会告知使用者,他们正在展示甚麽脸部表情。

UIView 拖拉至 ARSCNView 内,然后来设定条件。将 Width 设定为 240pt,Height 为 120pt,然后再设定 Left 与 Bottom 为 20pt。

arkit-face-tracking-3

为了美观的设计,让我们将 View 的 alpha 设为 0.8。现在,将 UILabel 拉到你刚完成的 view 内,再将所有的边设为 8pt

arkit-face-tracking-4

最后,把 Label 对齐设置到中间。设置好后,你的 storyboard 应该会像这样:

arkit-face-tracking-5

现在,让我们来设定 IBOutletsViewController.swift 连接。转换到 Assistant editor 模式,按住 Control 键点选 UIViewUILabel,然后将它们拉到 ViewController.swift 来建立 IBOutlets

arkit-face-tracking-6

你应该可以建构两个 outlet:faceLabellabelView

arkit-face-tracking-7

建立脸部网格 (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 方法内:

  1. 首先,我们会把 labelView 的边角设置为圆角。这其实只是一个设计偏好。
  2. 接著,我们需要确认机型是否支援 ARFaceTrackingConfiguration。这是我们用来建立脸部网格的 AR 追踪功能,如果我们没有确认的话,程式就会崩溃;而如果使用的机型不支援设定的话,就将会呈现错误讯息。

接著,我们将在 viewWillAppear 方法内改变一行程式码:将常数 configuration 改为 ARFaceTrackingConfiguration()。如此一来,你的程式码应该会像这样:

face-mesh-1

然后,我们需要加入 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,但你可以自由选用。现在你的程式码应该像是这样:

face-mesh-2

如果你执行这个 App,你应该会看到这样的画面:

face-mesh-demo

更新脸部网格

你可能会注意到,脸部网格并没有随著你的表情变化(如眨眼、微笑、或打哈欠等)而更新。这是因为我们还需要在 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 更新,这段程式码就会执行。首先,我们定义一个 faceAnchorsceneView 内脸部被侦测到的锚点 (anchor)。这个锚点是当执行脸部追踪 AR session 时,被侦测脸部的姿态 、拓扑 (topology)、表情的资料。我们也定义一个叫 faceGeometry 的常数,这是为被侦测脸部的拓扑资料。利用这两个常数,我们就可以每次都更新 faceGeometry

再次执行程式码,现在每当你改变脸部表情时,脸部网格就会以 60 帧率 (fps) 更新。

face-mesh-update

分析脸部表情变化

首先,在档案最顶部建立一个变数:

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 为一个函数。

  1. blendShapes 是一个命名系数的字典,根据特定脸部表情变化,表示检测到的脸部表情。Apple 提供超过 50 种以上的系数,来侦测不同的脸部表情变化。以我们的需求而言,我们仅会使用 4 种:mouthSmileLeftmouthSmileRightcheekPuff、和 tongueOut
  2. 我们使用系数来确认脸部执行这些表情的概率。为了检测微笑,我们加入嘴巴的右侧和左侧的概率。我发现,设定微笑的概率为 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 字串上。

我们完成所有的程式码囉!执行程式码,你应该会得到文章开头的成果。

true-depth-camera-demo

总结

利用 ARKit 开发基于脸部的使用者体验这个功能,背后还有很多可能性。游戏和 App 可以将原深感测镜头用于各种用途。我最爱的其中一个 App 就是 Hawkeye Access,这是一个让使用者可以用眼睛来控制的浏览器。

如果你想了解更多关于原深感测镜头的相关资讯,你可以看看 Apple 的官方影片 Face Tracking with ARKit。你也可以在 GitHub 上下载本次教学的专案。

文末推荐:iOS热门文集