ARKit框架详细解析(八)—— 使用AR Face Tracking和TrueDepth相机进行面部跟踪(二)

版本记录

版本号 时间
V1.0 2018.08.21

前言

苹果最近新出的一个API就是ARKit,是在2017年6月6日,苹果发布iOS11系统所新增框架,它能够帮助我们以最简单快捷的方式实现AR技术功能。接下来几篇我们就详细的对ARKit框架进行详细的解析。感兴趣的可以看上面几篇。
1. ARKit框架详细解析(一)—— 基本概览
2. ARKit框架详细解析(二)—— 关于增强现实和ARKit
3. ARKit框架详细解析(三)—— 开启你的第一个AR体验之旅
4. ARKit框架详细解析(四)—— 处理增强现实中的3D交互和UI控件
5. ARKit框架详细解析(五)—— 创建基于面部的AR体验
6. ARKit框架详细解析(六)—— 用Metal展示AR体验
7. ARKit框架详细解析(七)—— 使用AR Face Tracking和TrueDepth相机进行面部跟踪(一)

Emoji Bling - 表情符号Bling

在里面,你会找到一个名为SuperUsefulCode的文件夹,里面有一些Swift文件。 将它们拖到EmojiBlingViewController.swift下面的项目中。 如果需要,请选择Copy items if needed, Create groups,并确保选中Emoji Bling目标

StringExtension.swift包含String的扩展,可以将String转换为UIImage

EmojiNode.swift包含一个名为EmojiNodeSCNNode子类,它可以呈现一个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)
}

在这里,您:

  • 1)搜索节点(node),其名称为“nose”且类型为EmojiNode
  • 2)从ARFaceAnchorARFaceGeometry属性获取索引9处的顶点(vertex)并将其放入数组中。
  • 3)使用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)

在此代码中,您:

  • 1)通过使网格蒙版透明来隐藏它。
  • 2)使用您定义的鼻子选项创建一个EmojiNode
  • 3)命名鼻子节点,以便稍后找到。
  • 4)将节点添加到面节点。
  • 5)调用您的辅助函数,重新定位面部特征。

您会注意到编译器错误,因为未定义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。 您可以通过打开sb右上角的Object Library来找到它。

将其拖动到View控制器中的ARSCNView

Main.storyboard仍在Standard editor中打开的情况下,在Assistant editor中打开EmojiBlingViewController.swift,就像之前一样。 现在按住control不动 - 从Tap Gesture Recognizer拖动到您的主要EmojiBlingViewController类。

释放鼠标并添加名为handleTapAction,其类型为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()
}

在这里,您:

  • 1)在sceneView中获取点按的位置。
  • 2)执行命中测试以获取点击位置下的节点列表。
  • 3)获取点击位置的第一个(顶部)节点,并确保它是一个EmojiNode
  • 4)调用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是您为每个要素指定的节点名称数组,featureIndicesARFaceGeometry中与这些要素对应的顶点索引(还记得幻数吗?)。

你会注意到 mouth 有两个与之相关的索引。 由于开口是网眼罩中的孔,因此定位嘴表情符号的最佳方法是平均顶部和底部唇部的位置。

注意:功能的硬编码索引是技术债务的潜在来源。 目前,ARFaceGeometry有1220个顶点,但如果Apple决定要高分辨率会怎样? 突然间,这些索引可能不再符合您的预期。 一个可能的,强大的解决方案是使用AppleVision框架来初步检测面部特征并将它们的位置映射到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)
}

这看起来非常相似,但有一些变化可以解决。 在此代码中,您:

  • 1)循环遍历在类顶部定义的featuresfeatureIndexes
  • 2)按功能名称查找子节点,并确保它是EmojiNode
  • 3)使用ARFaceAnchorARFaceGeometry属性将索引数组映射到顶点数组。
  • 4)使用这些顶点更新子节点的位置。

开始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! - 用你的脸控制表情符号!

在阅读了关于混合形状系数的上一节之后,您是否想知道是否可以使用它们来操纵脸上显示的表情符号? 答案是肯定的。 是的你可以。

1. 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)保存节点默认为1.0的x-scale
  • 4)获取eyeBlinkLeft的混合形状系数,如果未找到则默认为0.0(未链接)。
  • 5)根据混合形状系数修改节点的y-scale
  • 6)实现默认case以使switch语句详尽无遗。

很简单吧? Build并运行!

2. 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并运行您的应用程序,您应该能够用双眼眨眼!

3. 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和运行您的应用程序。

后记

本篇主要讲述了使用AR Face Tracking和TrueDepth相机进行面部跟踪,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容