在本教程中潜腻,您將學(xué)習(xí)如何:
- 使用AR臉部追蹤功能通過TrueDepth相機(jī)追蹤您的臉部融涣。
- 將表情符號(hào)覆蓋在跟蹤的臉上威鹿。
- 根據(jù)您制作的面部表情操作表情符號(hào)轨香。
需要的前置準(zhǔn)備
首先要安裝好Xcode臂容,然后還需要一臺(tái)帶有前置TrueDepth攝像頭的iPhone。也就意味著最少要使用iPhone X以上的設(shè)備简逮,才能完成這個(gè)demo的功能
開始創(chuàng)建項(xiàng)目
啟動(dòng)Xcode散庶,然后使用快捷鍵command + shift + n
這里我們選擇 Augmented Reality App (增強(qiáng)現(xiàn)實(shí)應(yīng)用)悲龟,然后點(diǎn)擊next
輸入項(xiàng)目名字须教,然后選擇 Swift 語言没卸,Content Technology 選擇 SceneKit秒旋,然后點(diǎn)擊next迁筛,此時(shí)將在您選擇的置頂位置創(chuàng)建好項(xiàng)目
項(xiàng)目中的文件的用處
創(chuàng)建好項(xiàng)目之后细卧,默認(rèn)會(huì)有一個(gè)art.scnassets的文件夾贪庙,在這個(gè)文件夾下面有兩個(gè)文件止邮,正如圖片中的,一個(gè)是ship.scn, 一個(gè)是texture.png屈扎,scn文件是3D模型的一種鹰晨,如果沒有texture.png這個(gè)圖片模蜡,那么將顯示這樣的效果
texture.png被附著到ship.scn這個(gè)3d模型上之后哩牍,就成了下面的這個(gè)樣子膝昆,變的更好看了荚孵,蒙了一層皮,我們稱之為紋理骄呼,實(shí)際上不僅僅能把圖片當(dāng)成ship.scn的”皮“蜓萄,UIColor嫉沽,CALayer俏竞,甚至是AVPlayer魂毁,都可以席楚,暫時(shí)不做過多探究
說了這么多烦秩,感覺有點(diǎn)枯燥闻镶,還是來點(diǎn)代碼吧
代碼展示
現(xiàn)在到了最重要的環(huán)節(jié)铆农,代碼展示環(huán)節(jié)
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// Set the scene to the view
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
// MARK: - ARSCNViewDelegate
/*
// Override to create and configure nodes for anchors added to the view's session.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let node = SCNNode()
return node
}
*/
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}
僅僅以上幾十行代碼猴凹,我們將能體驗(yàn)到一個(gè)非常牛叉的AR飛船的效果郊霎,雖然不知道是什么機(jī)型书劝,但是不明覺厲土至,
是的,就這樣陶因,第一個(gè)AR應(yīng)用就完成了骡苞,但是仍然不知道是怎么回事啊,那么來分析分析是怎么實(shí)現(xiàn)的呢
首先楷扬,需要一個(gè)來顯示AR渲染效果的視圖ARSCNView解幽,
class ARSCNView : SCNView
SCNView是用來干嘛的呢。用于顯示3D SceneKit內(nèi)容的視圖
在macOS中烘苹,SCNView是NSView的子類躲株。 在iOS和tvOS中,SCNView是UIView的子類螟加。 作為這兩種操作系統(tǒng)的視圖層次結(jié)構(gòu)的一部分,SCNView對(duì)象在應(yīng)用程序的用戶界面中為SceneKit內(nèi)容提供了一個(gè)位置捆探。 您可以使用它的init(frame:options :)方法或?qū)⑵涮砑拥絥ib文件或情節(jié)提要中來創(chuàng)建一個(gè)SceneKit視圖然爆。 要為SceneKit視圖提供內(nèi)容,請將SCNScene對(duì)象分配給它的scene屬性黍图。
有關(guān)使用SceneKit視圖的其他重要方法和屬性曾雕,請參見SCNSceneRenderer協(xié)議。 (您還可以使用SCNRenderer類將SceneKit內(nèi)容渲染到任意Metal命令隊(duì)列或OpenGL上下文中助被,或者使用SCNLayer類渲染到macOS上的Core Animation層中剖张。SCNSceneRenderer協(xié)議定義了這三個(gè)SceneKit渲染類共有的功能。)
sceneView.delegate = self
設(shè)置ViewController為sceneView.的代理揩环,那么就可以在ARSCNViewDelegate的代理回調(diào)中處理業(yè)務(wù)上的需求搔弄,
sceneView.showsStatistics = true
設(shè)置是否顯示分析數(shù)據(jù),方便分析和調(diào)試
// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
這個(gè)的作用就是加載我們項(xiàng)目中的3d模型資源丰滑,從而將3d模型在sceneView中展示出來
SCNScene 是節(jié)點(diǎn)層次結(jié)構(gòu)和全局屬性的容器顾犹,它們共同構(gòu)成了可顯示的3D場景。
// Set the scene to the view
sceneView.scene = scene
將我們剛剛創(chuàng)建好的scene 賦值給sceneView的scene
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// Run the view's session
sceneView.session.run(configuration)
}
在視圖將要展示的時(shí)候,創(chuàng)建一個(gè)ARWorldTrackingConfiguration對(duì)象
ARWorldTrackingConfiguration的作用是
一種配置炫刷,用于監(jiān)視iOS設(shè)備的位置和方向擎宝,同時(shí)使您能夠擴(kuò)展用戶面前的環(huán)境。然后 sceneView.session.run(configuration)浑玛,這樣就能將飛船模型展示在相機(jī)拍到的現(xiàn)實(shí)世界中了绍申。
人臉檢測能力
通過上面的簡單分析,我們并不需要做很多的操作顾彰,寫很多的代碼极阅,就能實(shí)現(xiàn)一個(gè)簡單的AR應(yīng)用,暫且將上面的分析作為對(duì)AR的一個(gè)認(rèn)識(shí)拘央,下面將實(shí)現(xiàn)檢測到人臉涂屁,并且在人臉上貼上一些有趣的emoji表情
我們要改變的代碼并不是很多
guard ARFaceTrackingConfiguration.isSupported else {
fatalError("Face tracking is not supported on this device")
}
首先在viewDidLoad方法中加入這段代碼,這段代碼是來檢測您當(dāng)前的設(shè)備是否支持人臉識(shí)別灰伟,不支持的設(shè)備將無法完成本教程的余下功能拆又。
您已經(jīng)看過ARFaceTrackingConfiguration,它用于配置設(shè)備以使用TrueDepth攝像頭跟蹤您的臉部栏账√澹酷啊。
但是您還需要了解有關(guān)面部跟蹤的哪些信息挡爵?
您即將使用的三個(gè)非常重要的類是ARFaceAnchor竖般,ARFaceGeometry和ARSCNFaceGeometry。
ARFaceAnchor繼承自ARAnchor茶鹃。如果您以前使用ARKit做過任何事情涣雕,您就會(huì)知道ARAnchors是使它如此強(qiáng)大和簡單的原因。它們是ARKit跟蹤的現(xiàn)實(shí)世界中的位置闭翩,移動(dòng)手機(jī)時(shí)這些位置不會(huì)移動(dòng)挣郭。 ARFaceAnchors還包括有關(guān)面部的信息,例如拓?fù)浜捅砬椤?/p>
ARFaceGeometry聽起來很像疗韵。這是一張包含頂點(diǎn)和TextureCoordinates的3D描述兑障。
ARSCNFaceGeometry使用來自ARFaceGeometry的數(shù)據(jù)來創(chuàng)建SCNGeometry,該SCNGeometry可用于創(chuàng)建SceneKit節(jié)點(diǎn)-基本上就是您在屏幕上看到的內(nèi)容蕉汪。
好流译,夠了是時(shí)候使用這些類了≌甙蹋回到編碼福澡!
添加 Mesh Mask
從表面上看,您似乎只打開了前置攝像頭驹马。 但是竞漾,您看不到您的iPhone已經(jīng)在跟蹤您的臉眯搭。 令人毛骨悚然的iPhone。
看到iPhone跟蹤的內(nèi)容不是很好嗎业岁? 真是巧合,因?yàn)檫@正是您接下來要做的寇蚊!
在ViewController類定義的大括號(hào)后添加以下代碼:
// 1
extension ViewController: 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
}
}
上面的代碼做了這幾件事
- 聲明ViewController實(shí)現(xiàn)ARSCNViewDelegate協(xié)議笔时。
- 從協(xié)議定義renderer(_:nodeFor :)方法。
- 確保用于渲染的金屬設(shè)備不為零仗岸。
- 創(chuàng)建要由“金屬”設(shè)備渲染的面幾何允耿。
- 根據(jù)面部幾何形狀創(chuàng)建一個(gè)SceneKit節(jié)點(diǎn)。
- 將節(jié)點(diǎn)材質(zhì)的填充模式設(shè)置為僅線扒怖。
- 返回節(jié)點(diǎn)较锡。
在運(yùn)行代碼之前,確保在viewDidLoad中設(shè)置了代理
sceneView.delegate = self
此時(shí)運(yùn)行App你將能夠在自己的手機(jī)里自己的臉上有層遮罩網(wǎng)格盗痒。
更新 Mesh Mask
您是否注意到網(wǎng)格遮罩有點(diǎn)……是靜態(tài)的蚂蕴? 當(dāng)然,當(dāng)您左右移動(dòng)頭部時(shí)俯邓,它會(huì)跟蹤您的面部位置并隨之移動(dòng)骡楼,但是眨眼或張開嘴會(huì)發(fā)生什么呢? 沒有稽鞭。
真令人失望鸟整。
幸運(yùn)的是,這很容易解決朦蕴。 您只需要添加另一個(gè)ARSCNViewDelegate方法篮条!
在您的ARSCNViewDelegate擴(kuò)展的末尾,添加以下方法:
// 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 :)協(xié)議方法的didUpdate版本吩抓。
- 確保要更新的錨是ARFaceAnchor涉茧,并且節(jié)點(diǎn)的幾何是ARSCNFaceGeometry。
- 使用ARFaceAnchor的ARFaceGeometry更新ARSCNFaceGeometry
現(xiàn)在琴拧,在構(gòu)建和運(yùn)行時(shí)降瞳,您應(yīng)該看到網(wǎng)格蒙版表格并進(jìn)行更改以匹配您的面部表情。
再次運(yùn)行程序蚓胸,你會(huì)發(fā)現(xiàn)網(wǎng)格遮罩跟著臉部在運(yùn)動(dòng)了挣饥,很神奇。
添加Emoji表情
下面我們定義了一些有趣的表情
let noseOptions = ["??", "??", "??", " "]
let eyeOptions = ["??", "??", "??", "??", "??", "??", " "]
let mouthOptions = ["??", "??", "??", " "]
let hatOptions = ["??", "??", "??", "?", "??", " "]
let features = ["nose", "leftEye", "rightEye", "mouth", "hat"]
let featureIndices = [[9], [1064], [42], [24, 25], [20]]
以上的表情分別是
- 鼻子上的
- 眼睛上的
- 嘴上的
- 頭上的
下面我們定義一個(gè)類沛膳,用來將String轉(zhuǎn)成UIImage
extension String {
func image() -> UIImage? {
let size = CGSize(width: 20, height: 22)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
UIColor.clear.set()
let rect = CGRect(origin: .zero, size: size)
UIRectFill(CGRect(origin: .zero, size: size))
(self as AnyObject).draw(in: rect, withAttributes: [.font: UIFont.systemFont(ofSize: 15)])
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
然后定義一個(gè)EmojiNode 類扔枫,這個(gè)類是SCNNode的子類,
import SceneKit
class EmojiNode: SCNNode {
var options: [String]
var index = 0
init(with options: [String], width: CGFloat = 0.06, height: CGFloat = 0.06) {
self.options = options
super.init()
let plane = SCNPlane(width: width, height: height)
plane.firstMaterial?.diffuse.contents = (options.first ?? " ").image()
plane.firstMaterial?.isDoubleSided = true
geometry = plane
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Custom functions
extension EmojiNode {
func updatePosition(for vectors: [vector_float3]) {
let newPos = vectors.reduce(vector_float3(), +) / Float(vectors.count)
position = SCNVector3(newPos)
}
func next() {
index = (index + 1) % options.count
if let plane = geometry as? SCNPlane {
plane.firstMaterial?.diffuse.contents = options[index].image()
plane.firstMaterial?.isDoubleSided = true
}
}
}
這個(gè)類的作用是根據(jù)指定的字符串锹安,創(chuàng)建一個(gè)圖片節(jié)點(diǎn)短荐,并且能夠更新位置
在ViewController中添加如下方法
func updateFeatures(for node: SCNNode, using anchor: ARFaceAnchor) {
for (feature, indices) in zip(features, featureIndices) {
let child = node.childNode(withName: feature, recursively: false) as? EmojiNode
let vertices = indices.map { anchor.geometry.vertices[$0] }
child?.updatePosition(for: vertices)
switch feature {
case "leftEye":
let scaleX = child?.scale.x ?? 1.0
let eyeBlinkValue = anchor.blendShapes[.eyeBlinkLeft]?.floatValue ?? 0.0
child?.scale = SCNVector3(scaleX, 1.0 - eyeBlinkValue, 1.0)
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)
case "mouth":
let jawOpenValue = anchor.blendShapes[.jawOpen]?.floatValue ?? 0.2
child?.scale = SCNVector3(1.0, 0.8 + jawOpenValue, 1.0)
default:
break
}
}
}
此方法的作用是更新面部表情
在代理方法中添加嘴倚舀,眼睛,鼻子忍宋,帽子節(jié)點(diǎn)痕貌,并在錨點(diǎn)的更新方法中更新Emoji表情節(jié)點(diǎn)
extension ViewController: ARSCNViewDelegate {
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
guard let faceAnchor = anchor as? ARFaceAnchor,
let device = sceneView.device else { return nil }
let faceGeometry = ARSCNFaceGeometry(device: device)
let node = SCNNode(geometry: faceGeometry)
node.geometry?.firstMaterial?.fillMode = .lines
node.geometry?.firstMaterial?.transparency = 0.0
let noseNode = EmojiNode(with: noseOptions)
noseNode.name = "nose"
node.addChildNode(noseNode)
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)
updateFeatures(for: node, using: faceAnchor)
return node
}
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry else { return }
faceGeometry.update(from: faceAnchor.geometry)
updateFeatures(for: node, using: faceAnchor)
}
}
看看最終的效果
到目前位置,所有主要的代碼都在上面部分糠排,最后運(yùn)行項(xiàng)目舵稠,看看最終的效果
先講到這里了,感興趣的同學(xué)們入宦,可以下載代碼來調(diào)試一下哺徊,本教程參考資料來自于 《ARKit by Tutorials》這本書