去年 Apple 推出的新功能中,其中一個最創新的功能就是原深感測鏡頭 (True Depth Camera)。原深感測鏡頭對軟硬體工程師來說,支援了 FaceID 這個安全的面部識別系統;而對於開發者,原深感測鏡頭就開創了許多可能性,尤其是在基於臉部互動 (face-base interactions) 這方面。
在開始這篇 ARKit 教學之前,讓我快速說明一下相機的不同部分。原深感測鏡頭和大部分的 iPhone/ iPad 的前置相機一樣,有內嵌麥克風、具七百萬像素的相機、環境光源偵測器 (ambient light sensor)、距離感應器 (proximity sensor)、與揚聲器。而原深感測鏡頭最獨特的地方,就是獨有的測繪點投射器 (dot projector)、泛光照射器 (flood illuminator)、與紅外線鏡頭 (infrared camera)。
測繪點投射器會投射超過 3 萬個隱形測繪點到使用者的臉上,以建構成一個面部測繪圖(文章後部會提到);而紅外線相機就會讀取測繪點,擷取成一張紅外線影像,並傳送資料到 Apple A12 Bionic 處理器作比對;最後,泛光照射器就會利用紅外線,方便在黑暗環境辨識容貌。
這些不同的感測元件整合在一起,就可以創造如 Animojis 和 Memojis 這種神奇體驗。有了原深感測鏡頭,我們就可以利用使用者臉部和頭部的 3D 模型,為 App 建立更多特殊效果。
教學範例專案
我相信對於開發者而言,最重要的就是學會使用原深感測鏡頭,應用臉部追蹤功能,為使用者建構令人驚艷的臉部辨識體驗。在本篇教學中,我將會說明如何利用 ARKit 框架中的 ARFaceTrackingConfiguration
,去透過 3 萬個測繪點辨識不同的臉部動作。
最終成果會是這樣的:
讓我們開始吧!
你需要在 iPhone X、XS、XR、或 iPad Pro(第三代)上執行這個專案,因為只有這些機型才支援原深感測鏡頭。文章中,我們會使用 Swift 5 和 Xcode 10.2。
建立 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 上下載本次教學的專案。