第5章 人脸检测跟踪 在计算机人工智能(Artificial Intelligence,AI)物体检测识别领域,最先研究的是人脸检测识别,目前技术发展最成熟的也是人脸检测识别。人脸检测识别已经广泛应用于安防、机场、车站、闸机、人流控制、安全支付等众多社会领域,也广泛应用于直播特效、美颜、Animoji等娱乐领域。 5.1人脸检测基础 ARKit支持人脸检测,并且支持多人脸同时检测,还支持表情属性和BlendShapes。但需要注意的是,ARKit人脸检测跟踪需要配备前置深度摄像头(TrueDepth Camera)或者A12及以上处理器的设备,因此,并不是所有iPhone/iPad设备都支持人脸检测,目前支持ARKit人脸检测的设备如表51所示。 表51支持人脸检测的设备 移 动 设 备支持的设备 iPhone iPhone X iPhone Xr、iPhone Xs、iPhone Xs Max iPhone 11、iPhone 11 Pro、iPhone 11 Pro Max iPhone SE2 iPhone 12mini、iPhone 12、iPhone 12 Pro、iPhone 12 Pro Max iPad iPad Pro 12.9英寸第1代、第2代、第3代、第4代 iPad Pro 11英寸 iPad Pro 10.5英寸 iPad Pro 9.7英寸 5.1.1人脸检测概念 人脸检测(Face Detection)是利用计算机视觉处理技术在数字图像或视频中自动定位人脸的过程,人脸检测不仅检测人脸在图像或视频中的位置,还应该检测出其大小与方向(姿态)。人脸检测是有关人脸图像分析应用的基础,包括人脸识别和验证、监控场合的人脸跟踪、面部表情分析、面部属性识别(性别、年龄、微笑、痛苦)、面部光照调整和变形、面部形状重建、图像视频检索等。近几年,随着机器学习技术的发展,人脸检测成功率与准确率大幅度提高,并开始大规模实用,如机场和火车站人脸验票、人脸识别身份认证等。 人脸识别(Face Recognition)是指利用人脸检测技术确定两张人脸是否对应同一个人,人脸识别技术 是人脸检测技术的扩展和应用,也是很多其他应用的基础。目前,ARKit仅提供人脸检测,而不提供人脸识别功能。 人脸跟踪(Face Tracking)是指将人脸检测扩展到视频序列,跟踪同一张人脸在视频序列中的位置。理论上讲,任何出现在视频中的人脸都可以被跟踪,也即是说,在连续视频帧中检测到的人脸可以被识别为同一个人。人脸跟踪不是人脸识别的一种形式,它是根据视频序列中人脸的位置和运动推断不同视频帧中的人脸是否为同一人的技术。 人脸检测属于模式识别的一类,但人脸检测成功率受到很多因素的影响,影响人脸检测成功率的因素主要有表52中所述情形。 表52影响人脸检测成功率的部分因素 术语描 述 说 明 图像大小人脸图像过小会影响检测效果,人脸图像过大会影响检测速度,图像大小反映在实际应用场景中就是人脸离摄像头的距离 图像分辨率越低的图像分辨率越难检测,图像大小与图像分辨率直接影响摄像头识别距离。目前4K摄像机看清人脸的最远距离是10m左右,移动手机检测距离更小一些 光照环境过亮或过暗的光照环境都会影响人脸检测效果 模糊程度实际场景中主要是运动模糊,人脸相对于摄像机的移动经常会产生运动模糊 遮挡程度五官无遮挡、脸部边缘清晰的图像有利于人脸检测。有遮挡的人脸会对人脸检测成功率造成影响 采集角度人脸相对于摄像机角度不同也会影响人脸检测效果。正脸最有利于检测,偏离角度越大越不利于检测 随着人工智能技术的持续发展,在全球信息化、云计算、大数据的支持下,人脸检测识别技术也会越来越成熟,同时应用面会越来越广,可以预见,以人脸检测为基础的人脸识别将会呈现网络化、多识别融合、云互联的发展趋势。 5.1.2人脸检测技术基础 人体头部是一个三维结构体,而眼、嘴、额头在这个三维结构体中又有比较固定的位置,因此在ARKit中使用了两个坐标系来处理与人体头部相关的工作,一个是世界坐标系(World Coordinates Space),这个坐标系就是ARKit启动时建立的以启动时设备所在位置为原点的坐标系,而另一个称为人脸坐标系(Face Coordinate Space)。 在ARKit检测到人脸后会生成一个ARFaceAnchor,其transform属性指定了相对于世界坐标系的人脸位置与方向,利用该属性就可以在人脸上挂载虚拟元素。除此之外,ARKit还会生成一个相对于人体头部的坐标系,该坐标系也以米为测量单位,利用该坐标系可以更精细地定位眼、嘴、鼻等位置从而实现更好的虚拟元素定位效果。ARKit人脸坐标系也采用右手坐标系,如图51所示。 图51ARKit人脸坐标系示意图 在人脸坐标系中,X轴正向指向观察者的右侧(检测到人脸的左侧),Y轴向上(这里的向上相对于人脸而不是世界坐标系),Z轴指向人脸面向方向。 人脸检测技术的复杂性之一是人体头部是一个三维结构体,并且是一个动态的三维结构体,摄像机捕捉到的人脸图像很多时候都不是正面,而是有一定角度且时时处于变化中的侧面。当然,人脸检测的有利条件是人脸有很多特征,如图52所示,可以利用这些特征做模式匹配。但需要注意的是,在很多人脸检测算法中,人脸特征并不是人脸轮廓检测的前提,换句话说,人脸检测是独立于人脸特征的,且通常是先检测出人脸轮廓再进行特征检测,因为特征检测需要花费额外的时间,会对人脸检测效率产生影响。 图52人脸特征点示意图 人脸结构具有对称性,人脸特征会分布在Y轴两侧一定角度内,通常来说,人脸特征分布情况符合表53所示规律。 表53人脸特征分布情况 Y欧拉角人 脸 特 征Y欧拉角人 脸 特 征 小于-36°左眼、左嘴角、左耳、鼻底、左脸颊12°~36°右眼、右嘴角、鼻底、下嘴唇、右脸颊 -36°~-12°左眼、左嘴角、鼻底、下嘴唇、左脸颊大于36°右眼、右嘴角、右耳、鼻底、右脸颊 -12°~12°左嘴角、右嘴角、上下嘴唇、鼻底 人脸检测不仅需要找出人脸轮廓,还需要检测出人脸姿态(包括人脸位置和面向方向)。为了解决人脸姿态问题,一般的做法是制作一个三维人脸正面“标准模型”,这个模型需要非常精细,因为它将影响到人脸姿态估计的精度。有了这个三维标准模型之后,对人脸姿态检测的思路是在检测到人脸轮廓后对标准模型进行旋转,以期标准模型上的特征点与检测到的人脸特征点重合匹配。从这个思路可以看到,对姿态的检测其实是个不断尝试的过程,选取特征点吻合得最好的标准模型姿态作为人脸姿态。形象地说,就是先制作一个人皮面具,努力尝试将人皮面具套在人脸上,如果成功则人皮面具的姿态必定是人脸姿态。 如前所述,虽然人脸的结构具有稳定性,还有很多特征点可供校准,但由于姿态和表情的变化、不同人的外观差异、光照、遮挡等影响,准确地检测处于各种条件下的人脸仍然是较为困难的事情。幸运的是,随着深度神经网络的发展,在一般环境条件下,目前人脸检测准确率有了非常大的提高,甚至在某些条件下超过了人类。 ARKit人脸检测由于使用了深度摄像头(或者带神经处理单元NPU的设备),检测精度非常高,除了具有通常意义下的人脸检测功能,还具备一些独特的功能特性,具体功能特性如表54所示。 表54ARKit人脸检测跟踪功能 序号描述 1检测人脸位置与方向 2提供检测到人脸的几何网络(ARFaceGeometry),因此可以渲染人脸模型 3提供双眼姿态,因此可以独立地跟踪每一只眼睛 4基于人脸位置,可以在检测到的人脸上挂载虚拟元素(贴纸或者模型) 续表 序号描述 5可以从检测到的人脸评估环境光照信息 6支持BlendShape,因此可以实现利用表情驱动模型功能 7提供人眼凝视(LookAtPoint),因此可以实现眼动控制 8允许在人脸检测跟踪的同时启用世界跟踪(World Tracking) 5.2人脸检测配置 ARKit可以使用人脸跟踪(ARFaceTrackingConfiguration)和世界跟踪(ARWorldTrackingConfiguration)两种配置方式开启人脸检测跟踪功能。 ARWorldTrackingConfiguration配置中有一个userFaceTrackingEnabled属性,该属性为布尔值,默认为false,如果设置为true,则可以在进行世界跟踪的同时启动人脸检测跟踪。 ARFaceTrackingConfiguration是专为人脸检测跟踪优化的配置,其中,maximumNumberOfTrackedFaces属性用于设置最大同时检测跟踪的人脸数,当前最大值为3; isWorldTrackingEnabled设置是否在人脸检测跟踪的同时启动世界跟踪,isLightEstimationEnabled设置是否启用环境光照评估。 典型的启动人脸检测跟踪功能的代码如代码清单51所示。 代码清单51 1.guard ARFaceTrackingConfiguration.isSupported else { return } 2.let configuration = ARFaceTrackingConfiguration() 3.configuration.isLightEstimationEnabled = true 4.arView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors]) 图53人脸网格示意图 由于并非所有支持ARKit的设备都支持人脸检测跟踪,因此在开启人脸检测跟踪之前,首先应当检测用户设备是否支持人脸检测,如果支持,再设置诸如isLightEstimationEnabled、maximumNumberOfTrackedFaces等属性,然后启动ARSession。 5.2.1人脸网格 除了人脸姿态,ARKit还提供了每个已检测到的人脸网格(ARFaceGeometry),该网络包含1220个顶点,网格数据包括顶点(vertices)、索引(triangleIndices)、三角形数量(triangleCount)、纹理坐标(textureCoordinates)等相关信息,如图53所示。利用人脸网格,开发者就可以渲染出人脸形状,或者对人脸网络进行自定义贴图等。 提示 人脸网格顶点总数不会变,每个检测到的人脸都会生成一张1220个顶点的网格,因此vertexCount、textureCoordinateCount、triangleCount都不会变,对每一张特定的人脸网格,变化的只是顶点(vertices)的位置,因为ARKit会根据用户面部形状与表情调整网格。 到目前为止,RealityKit并不支持人脸网格几何生成与渲染,本节我们将使用SceneKit进行演示,但是由于本书的主题,我们只关注与人脸网格相关处理,其他SceneKit相关技术细节,需读者自行查阅SceneKit资料。 ARKit会根据每个检测到的人脸提供与之相应形状、尺寸、表情的网格信息,在使用SceneKit渲染人脸网格时,有3个类非常重要: ARFaceAnchor、ARFaceGeometry、ARSCNFaceGeometry。 ARFaceAnchor继承自ARAnchor,是专门用于锚定人脸的锚点,其transform属性指定相对于世界坐标系的人脸位置与方向,利用它就可以锚定生成的人脸网格。 ARFaceGeometry包含ARKit生成的人脸网格信息,包括顶点、索引、UV坐标等所有信息。 ARSCNFaceGeometry则是利用ARFaceGeometry网格数据生成SCNGeometry,可以直接作为SceneKit场景中的节点。 检测与渲染人脸网格的典型代码如代码清单52所示。 代码清单52 1.import UIKit 2.import SceneKit 3.import ARKit 4. 5.class ViewController: UIViewController { 6.@IBOutlet var sceneView: ARSCNView! 7.override func viewDidLoad() { 8.super.viewDidLoad() 9.guard ARFaceTrackingConfiguration.isSupported else { 10.fatalError("当前设备不支持人脸检测!") 11.} 12.sceneView.delegate = self 13.} 14. 15.override func viewWillAppear(_ animated: Bool) { 16.super.viewWillAppear(animated) 17.let configuration = ARFaceTrackingConfiguration() 18.configuration.isLightEstimationEnabled = true 19.configuration.providesAudioData = false 20.configuration.isWorldTrackingEnabled = false 21.configuration.maximumNumberOfTrackedFaces = 1 22.sceneView.session.run(configuration) 23.} 24. 25.override func viewWillDisappear(_ animated: Bool) { 26.super.viewWillDisappear(animated) 27.sceneView.session.pause() 28.} 29.} 30. 31.extension ViewController: ARSCNViewDelegate { 32.func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? { 33.guard let device = sceneView.device else {return nil } 34.let faceGeometry = ARSCNFaceGeometry(device: device) 35.let node = SCNNode(geometry: faceGeometry) 36.node.geometry?.firstMaterial?.fillMode = .lines 37.return node 38.} 39. 40.func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode,for anchor: ARAnchor) { 41.guard let faceAnchor = anchor as? ARFaceAnchor, 42.let faceGeometry = node.geometry as? ARSCNFaceGeometry else { 43.return 44.} 45.faceGeometry.update(from: faceAnchor.geometry) 46.} 47. 48.} 在代码清单52中,首先检查了当前设备对人脸检测的支持情况,然后使用ARFaceTrackingConfiguration配置并运行了人脸检测ARSession,当ARKit检测到人脸时,我们将从ARSCNFaceGeometry对象得到的人脸几何网格并使用线框的渲染模式进行渲染,检测效果如图54左图所示。 图54分别使用线框与纹理贴图渲染检测到的人脸网格示意图 在AR应用运行时,ARKit会根据检测到的人脸方向、表情实时更新人脸网格,为显示出人脸网格的实时变化,我们使用renderer(_:didUpdate:for:)代理方法对人脸网格进行了实时更新。 检测到的人脸网格不仅包括几何顶点信息,也包括UV坐标信息,因此,我们不仅可以以线框模式渲染网格,还可以使用静态、动态的纹理贴图进行渲染,只需要将代码清单52中renderer(: nodeFor:)代理方法中线框渲染模式变更为使用材质纹理,典型代码如代码清单53所示。 代码清单53 1.guard let device = sceneView.device else {return nil } 2.let faceGeometry = ARSCNFaceGeometry(device: device) 3.let node = SCNNode(geometry: faceGeometry) 4.let material = node.geometry?.firstMaterial! 5.material?.diffuse.contents = "face.scnassets/face.png" 6.node.geometry?.firstMaterial?.fillMode = .fill 使用线框模式与使用纹理贴图模式渲染的人脸网格如图54所示,利用ARKit人脸网格贴图可以实现很多有意思的贴纸效果,如腮红、口红、额纹等,在电子商务试妆方面也可以应用。 ARKit只提供人脸检测功能,并不支持人脸识别,如果需要此功能,还需要结合其他技术共同完成。 5.2.2挂载虚拟元素 在RealityKit中,在检测到的人脸面部挂载虚拟元素的实现方式有两种: 一种是通过遵循ARSessionDelegate协议,执行session(_ session: ARSession, didAdd anchors: [ARAnchor])方法,在获取的ARFaceAnchor上挂载虚拟元素; 另一种是与Reality Composer结合使用。 在使用第一种方式时,可以利用ARFaceAnchor初始化一个AnchorEntity类型实例,这样,ARFaceAnchor的姿态信息就可以直接被使用,典型的使用代码如代码清单54所示。 代码清单54 1.public func session(_ session: ARSession, didAdd anchors: [ARAnchor]) { 2.for anchor in anchors { 3.guard let anchor = anchor as? ARFaceAnchor else { continue } 4.do { 5.let faceEntity =try Entity.load(named: "toy_biplane.usdz") 6.let faceAnchor = AnchorEntity(anchor: anchor) 7.faceAnchor.addChild(faceEntity) 8.self.scene.addAnchor(faceAnchor) 9.} catch { 10.print("找不到文件") 11.} 12.} 13.} 在检测到的人脸上挂载虚拟元素使用RealityKit与Reality Composer结合的方式更方便直观,特别是需要在很多虚拟元素之间进行切换时,可以大大简化代码逻辑。使用第二种方式的操作步骤如下: (1) 打开Reality Composer,并创建一个锚定到人脸的工程(Reality Composer具体操作参阅第10章),如图55所示。 (2) 导入需要挂载的USDZ或者Reality模型文件并调整到参考人脸理想的位置,然后给场景命名(命名时建议使用英文字母或者英文字母与数字组合,方便在RealityKit中调用),如图56所示。 (3) 在Reality Composer菜单中依次选择“文件”→“保存”(或者使用快捷键Command+S)保存工程为FaceMask.rcproject文件(工程名根据需要自行命名)。 (4) 使用RealityKit加载工程文件到内存,直接获取工程文件中的锚点信息并将其作为ARAnchor添加到ARVeiw.scene场景中即可。这里需要注意的是,ARKit会在检测到人脸后自动在指定的位置挂载虚拟元素,但ARKit并不会自动运行人脸检测的ARSession,因此,需要手动运行人脸检测的ARSession以开启人脸检测功能,典型代码如代码清单55所示。 图55在Reality Composer中创建锚定到人脸的工程 图56调整模型到参考人脸合适位置并重命名场景 代码清单55 1.struct ARViewContainer: UIViewRepresentable { 2.func makeUIView(context: Context) -> ARView { 3.let arView = ARView(frame: .zero) 4.if ARFaceTrackingConfiguration.isSupported { 5.let faceConfig = ARFaceTrackingConfiguration() 6.faceConfig.maximumNumberOfTrackedFaces = 1 7.arView.session.delegate = arView 8.let faceAnchor = try! FaceMask.loadGlass1() 9.arView.addGuesture() 10.arView.scene.addAnchor(faceAnchor) 11.arView.session.run(faceConfig, options: [.resetTracking,.removeExistingAnchors]) 12.} 13.return arView 14.} 15.func updateUIView(_ uiView: ARView, context: Context) { 16.uiView.scene.anchors.removeAll() 17.} 18.} 19.var faceMaskCount = 0 20.let numberOfMasks = 6 21. 22.extension ARView : ARSessionDelegate{ 23.func addGuesture(){ 24.let gesture = UISwipeGestureRecognizer() 25.gesture.addTarget(self, action: #selector(changeGlass(gesture:))) 26.self.addGestureRecognizer(gesture) 27.} 28. 29.@objc func changeGlass(gesture: UISwipeGestureRecognizer){ 30.faceMaskCount += 1 31.faceMaskCount %= numberOfMasks 32.switch faceMaskCount { 33.case 0: 34.let g = try! FaceMask.loadGlass2() 35.self.scene.anchors.removeAll() 36.self.scene.addAnchor(g) 37.case 1: 38.let g = try! FaceMask.loadIndian() 39.self.scene.anchors.removeAll() 40.self.scene.addAnchor(g) 41.... 42.case 5: 43.let g = try! FaceMask.loadFaceMesh() 44.self.scene.anchors.removeAll() 45.self.scene.addAnchor(g) 46.default: 47.break 48.} 49.} 50. } 在代码清单55中,首先检查设备对人脸检测的支持情况,在设备支持时运行人脸检测配置开启人脸检测功能,然后加载由Reality Composer配置好的虚拟模型。本示例我们在Reality Composer中创建了多个场景,每一个场景使用了一个虚拟元素,为方便切换不同的虚拟元素,我们使用了滑动手势控制场景切换,实现的效果如图57所示。 图57在检测到的人脸上挂载虚拟元素效果图 5.3BlendShapes 苹果公司在iPhone X及后续机型上增加了一个深度相机(TrueDepth Camera),利用这个深度相机可以更加精准捕捉用户的面部表情,提供更详细的面部特征点信息。在ARKit 4后,利用机器学习技术对前置摄像头捕获的图像进行处理,使不具备深度相机的设备也可以进行人脸检测和BlendShapes(需要A12及以上处理器)。 5.3.1BlendShapes基础 利用前置摄像头采集到的用户面部表情特征,ARKit提供了一种更加抽象的表示面部表情的方式,这种表示方式叫作BlendShapes,BlendShapes可以翻译成形状融合,在3ds Max中也叫变形器,这个概念原本用于描述通过参数控制模型网格的位移,苹果公司借用了这个概念,在ARKit中专门用于表示通过人脸表情因子驱动模型的技术。BlendShapes在技术上是一组存储了用户面部表情特征运动因子的字典,共包含52组特征运动数据,ARKit会根据摄像机采集的用户表情特征值实时地设置对应的运动因子。利用这些运动因子可以驱动2D或者3D人脸模型,这些模型即可呈现与用户一致的表情。 ARKit实时提供全部52组运动因子,这52组运动因子中包括7组左眼运动因子数据、7组右眼运动因子数据、27组嘴与下巴运动因子数据、10组眉毛脸颊鼻子运动因子数据、1组舌头运动因子数据。但在使用时可以选择利用全部或者只利用其中的一部分,如只关注眼睛运动,则只利用眼睛相关运动因子数据即可。 每一组运动因子表示一个ARKit识别的人脸表情特征,每一组运动因子都包括一个表示人脸特定表情的定位符与一个表示表情程度的浮点类型值, 图58运动因子对人脸表情的影响 表情程度值的范围为[0,1],其中0表示没有表情,1表示完全表情。如图58所示,在图中,这个表情定位符为mouthSmileRight,代表右嘴角的表情定位,左图中表情程度值为0,即没有任何右嘴角表情,右图中表情值为1,即为最大的右嘴角表情运动,而0到1之间的中间值则会对网格进行融合,形成一种过渡表情,这也是BlendShapes名字的由来。ARKit会实时捕捉到这些运动因子,利用这些运动因子我们可以驱动2D、3D人脸模型,这些模型会同步用户的面部表情,当然,我们可以只取其中的一部分所关注的运动因子,但由于人脸表情通常与若干组表情因子相关联,如果想精确地模拟用户的表情,建议使用全部运动因子数据。 5.3.2BlendShapes技术原理 在ARKit中,对人脸表情特征信息定义了52组运动因子数据,其使用BlendShapeLocation作为表情定位符,表情定位符定义了特定表情,如mouthSmileLeft、mouthSmileRight等,与其对应的运动因子则表示表情程度,这52组运动因子数据如表55所示。 表55BlendShapeLocation表情定位符及其描述 区域表情定位符描述 Left Eye(7) eyeBlinkLeft左眼眨眼 eyeLookDownLeft左眼目视下方 eyeLookInLeft左眼注视鼻尖 eyeLookOutLeft左眼向左看 eyeLookUpLeft左眼目视上方 eyeSquintLeft左眼眯眼 eyeWideLeft左眼睁大 续表 区域表情定位符描述 Right Eye(7) eyeBlinkRight右眼眨眼 eyeLookDownRight右眼目视下方 eyeLookInRight右眼注视鼻尖 eyeLookOutRight右眼向左看 eyeLookUpRight右眼目视上方 eyeSquintRight右眼眯眼 eyeWideRight右眼睁大 Mouth and Jaw(27) jawForward努嘴时下巴向前 jawLeft撇嘴时下巴向左 jawRight撇嘴时下巴向右 jawOpen张嘴时下巴向下 mouthClose闭嘴 mouthFunnel稍张嘴并双唇张开 mouthPucker抿嘴 mouthLeft向左撇嘴 mouthRight向右撇嘴 mouthSmileLeft左撇嘴笑 mouthSmileRight右撇嘴笑 mouthFrownLeft左嘴唇下压 mouthFrownRight右嘴唇下压 mouthDimpleLeft左嘴唇向后 mouthDimpleRight右嘴唇向后 mouthStretchLeft左嘴角向左 mouthStretchRight右嘴角向右 mouthRollLower下嘴唇卷向里 mouthRollUpper下嘴唇卷向上 mouthShrugLower下嘴唇向下 mouthShrugUpper上嘴唇向上 mouthPressLeft下嘴唇压向左 mouthPressRight下嘴唇压向右 mouthLowerDownLeft下嘴唇压向左下 mouthLowerDownRight下嘴唇压向右下 mouthUpperUpLeft上嘴唇压向左上 mouthUpperUpRight上嘴唇压向右上 Eyebrows(5) browDownLeft左眉向外 browDownRight右眉向外 browInnerUp蹙眉 browOuterUpLeft左眉向左上 browOuterUpRight右眉向右上 Cheeks(3) cheekPuff脸颊向外 cheekSquintLeft左脸颊向上并回旋 cheekSquintRight右脸颊向上并回旋 续表 区域表情定位符描述 Nose(2) noseSneerLeft左蹙鼻子 noseSneerRight右蹙鼻子 Tongue(1)tongueOut吐舌头 需要注意的是,在表55中表情定位符的命名是基于人脸方向的,如eyeBlinkRight定义的是人脸右眼眨眼,但在呈现3D模型时我们镜像了模型,看到的人脸模型右眼其实在左边。 有了表情特征运动因子后,就可以使用SceneKit中的SCNMorpher.SetWeight()方法进行网格融合,该方法原型为: setWeight(_ weight: CGFloat, forTargetNamed targetName: Int); 该方法有两个参数,forTargetNamed参数为需要融合的网格变形器名,即上文中的BlendShapeLocation名; weight参数为需要设置的BlendShape权重值,取值范围为[0,1]。 该方法主要功能是用于设置指定网格变形器的BlendShape权重值,这个值表示从源网格到目标网格的过渡(源网格与目标网格拥有同样的拓扑结构,但顶点位置两者有差异),最终值符合以下公式: vfin=(1-value)*vsrc+value*vdes 因此,通过设置网格的BlendShape权重值可以将网格从源网格过渡到目标网格,如图59所示。 图59BlendShape权重值对网格的影响 5.3.3BlendShapes使用 使用ARKit的BlendShapes功能需要满足两个条件: 第一是有一个配备有深度相机或者A12及以上处理器的移动设备; 第二是有一个BlendShapes已定义好的模型,为简化操作,这个模型的BlendShapes名称定义应与表55完全对应。为模型添加BlendShapes可以在3ds Max软件中定义变形器,并做好对应的网格变形。 在满足以上两个条件后,使用BlendShapes就变得相对简单了,实现的思路如下: (1) 获取ARKit表情特征运动因子。这可以通过检查ARFaceAnchor获取相应数据,在检测到人脸时,ARFaceAnchor会返回一个blendShapes集合,该集合包含所有52组表情特征运动因子数据。 (2) 绑定ARKit的表情特征定位符与模型中的变形器,使其保持一致。 (3) 当人脸ARFaceAnchor发生更新时,实时更新所有与表情特征运动因子相关联的模型变形器。 核心示例代码如代码清单56所示。 代码清单56 1.import UIKit 2.import SceneKit 3.import ARKit 4. 5.class ViewController: UIViewController { 6.@IBOutlet var sceneView: ARSCNView! 7.var contentNode: SCNReferenceNode? 8.private lazy var head = contentNode!.childNode(withName: "head", recursively: true)! 9. 10.override func viewDidLoad() { 11.super.viewDidLoad() 12.guard ARFaceTrackingConfiguration.isSupported else { 13.fatalError("当前设备不支持人脸检测!") 14.} 15.sceneView.delegate = self 16.} 17. 18.override func viewWillAppear(_ animated: Bool) { 19.super.viewWillAppear(animated) 20.let configuration = ARFaceTrackingConfiguration() 21.configuration.isLightEstimationEnabled = true 22.configuration.providesAudioData = false 23.configuration.isWorldTrackingEnabled = false 24.configuration.maximumNumberOfTrackedFaces = 1 25.sceneView.autoenablesDefaultLighting = true 26.sceneView.allowsCameraControl = false 27.sceneView.session.run(configuration) 28. 29.} 30.override func viewWillDisappear(_ animated: Bool) { 31.super.viewWillDisappear(animated) 32.sceneView.session.pause() 33.} 34.} 35. 36.extension ViewController: ARSCNViewDelegate { 37.func modelSetup() { 38.if let filePath = Bundle.main.path(forResource: "BlendShapeFace", ofType: "scn") { 39.let referenceURL = URL(fileURLWithPath: filePath) 40.self.contentNode = SCNReferenceNode(url: referenceURL) 41.self.contentNode?.load() 42.self.head.morpher?.unifiesNormals = true 43.self.contentNode?.scale = SCNVector3(0.01,0.01,0.01) 44.self.contentNode?.position.y += 0.02 45.} 46.} 47.func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { 48.guard let faceAnchor = anchor as? ARFaceAnchor else { return } 49.modelSetup() 50.node.addChildNode(self.contentNode!) 51.} 52. 53.func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode,for anchor: ARAnchor) { 54.guard let faceAnchor = anchor as? ARFaceAnchor else { return } 55.DispatchQueue.main.async { 56.for (key, value) in faceAnchor.blendShapes { 57.if let fValue = value as? Float { 58.self.head.morpher?.setWeight(CGFloat(fValue), forTargetNamed: key.rawValue) 59.} 60.} 61.} 62.} 63.} 实现BlendShapes核心逻辑很清晰,即使用检测到的人脸表情驱动模型对应的表情,因此人脸表情与模型变形器(BlendShapes)必须建立一一对应关系,我们可以选择手动逐个绑定,也可以在建模时将变形器名与ARKit中的BlendShapeLocation定位符名按表55所示完全对应以简化手动绑定。为实现实时驱动的效果,需要实时地更新ARKit检测到的人脸表情因子到模型变形器。 运行本示例,AR应用启动后会自动开启前置摄像头,当检测到人脸时就会在该人脸位置挂载虚拟头像,当人脸表情发生变化时,虚拟头像模型对应表情也会发生变化,BlendShapes效果如图510所示。 图510BlendShapes效果图 注意 由于目前USDZ及Reality模型格式并不支持变形器(morpher target,blendShapes),RealityKit也不支持直接使用Morpher.SetWeight()进行网格变形,所以本节中我们使用了SceneKit,可以看到,当模型变形器命名与表55中命名完全一致时,使用BlendShapes比较简单。5.4节将演示使用RealityKit获取表情因子驱动模型。 5.4同时开启前后摄像头 iOS设备配备了前后两个摄像头,在运行AR应用时,需要选择使用哪个摄像头作为图像输入。最常见的AR体验使用设备后置摄像头进行世界跟踪、虚实融合,通常使用ARWorldTrackingConfiguration配置跟踪使用者的真实环境。除了进行虚实融合,我们通常还利用后置摄像头采集的图像信息评估真实世界中的光照情况、对真实环境中的2D图像或者3D物体进行检测等。 对具备前置深度相机(TrueDepth Camera)或者A12及以上处理器的设备,使用ARFaceTrackingConfiguration配置可以实时进行人脸检测跟踪,实现人脸姿态和表情的捕捉。 拥有前置深度相机或A12及以上处理器硬件的iPhone/iPad,在运行iOS 13及以上系统时,还可以同时开启设备前后摄像头,即同时进行人脸检测和世界跟踪。这是一项非常有意义且实用的功能,意味着使用者可以使用表情控制场景中的虚拟物体,实现除手势与语音之外的另一种交互方式。 在RealityKit中,同时开启前后摄像头需要使用ARFaceTrackingConfiguration配置或者ARWorldTrackingConfiguration配置之一。使用ARFaceTrackingConfiguration配置时将其supportsWorldTracking属性设置为true,使用ARWorldTrackingConfiguration配置时将其userFaceTrackingEnabled属性设置为true都可以在支持人脸检测的设备上同时开启前后摄像头。 同时开启前后摄像头后,RealityKit会使用后置摄像头跟踪现实世界,同时也会通过前置摄像头实时检测人脸信息,包括人脸表情信息。 需要注意的是,并不是所有设备都支持同时开启前后摄像头,只有符合前文所描述的设备才支持该功能,因此,在使用之前也应当对该功能的支持情况进行检查。在不支持同时开启前后摄像头的设备上应当执行另外的策略,如提示用户进行只使用单个摄像头的操作。 在下面的演示中,我们会利用后置摄像头的平面检测功能,在检测到的水平平面上放置机器头像模型,然后利用从前置摄像头中捕获的人脸表情信息驱动头像模型。核心代码如代码清单57所示。 代码清单57 1.var robotHead = RobotHead() 2.struct ARViewContainer: UIViewRepresentable { 3.func makeUIView(context: Context) -> ARView { 4.let arView = ARView(frame: .zero) 5.if ARWorldTrackingConfiguration.supportsUserFaceTracking { 6.let config = ARWorldTrackingConfiguration() 7.config.userFaceTrackingEnabled = true 8.config.planeDetection = .horizontal 9.config.worldAlignment = .gravity 10.arView.automaticallyConfigureSession = false 11.arView.session.run(config,options: []) 12.arView.session.delegate = arView 13.arView.createRobotHead() 14.} 15.return arView 16.} 17.func updateUIView(_ uiView: ARView, context: Context) { 18.} 19.} 20. 21.extension ARView : ARSessionDelegate{ 22.func createRobotHead(){ 23.let planeAnchor = AnchorEntity(plane:.horizontal) 24.planeAnchor.addChild(robotHead) 25.self.scene.addAnchor(planeAnchor) 26.} 27. 28.public func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) { 29.for anchor in anchors { 30.guard let anchor = anchor as? ARFaceAnchor else { continue } 31.robotHead.update(with: anchor) 32.} 33.} 34.} 在代码清单57中,我们首先对设备支持情况进行检查,在确保设备支持同时开启前后摄像头功能时使用ARWorldTrackingConfiguration配置并运行AR进程,然后在检测到平面时将机器头像模型放置于平面上,最后利用session(didUpdate frame:)代理方法使用实时捕获到的人脸表情数据更新机器头像模型,从而达到了使用人脸表情驱动场景中模型的目的。需要注意的是代码中userFaceTrackingEnabled必须设置为true,并且开启平面检测功能,另外,为更好地组织代码,我们将与模型及表情驱动相关的代码放到了RobotHead类中。 RobotHead类用于管理机器头像模型加载及使用表情数据驱动模型的工作,关键代码如代码清单58所示。 代码清单58 1.func update(with faceAnchor: ARFaceAnchor) { 2.let blendShapes = faceAnchor.blendShapes 3.guard let eyeBlinkLeft = blendShapes[.eyeBlinkLeft] as? Float, 4.let eyeBlinkRight = blendShapes[.eyeBlinkRight] as? Float, 5.let eyeBrowLeft = blendShapes[.browOuterUpLeft] as? Float, 6.let eyeBrowRight = blendShapes[.browOuterUpRight] as? Float, 7.let jawOpen = blendShapes[.jawOpen] as? Float, 8.let upperLip = blendShapes[.mouthUpperUpLeft] as? Float, 9.let tongueOut = blendShapes[.tongueOut] as? Float 10.else { return } 11. 12.eyebrowLeftEntity.position.y = originalEyebrowY + 0.03 * eyeBrowLeft 13.eyebrowRightEntity.position.y = originalEyebrowY + 0.03 * eyeBrowRight 14.tongueEntity.position.z = 0.1 * tongueOut 15.jawEntity.position.y = originalJawY - jawHeight * jawOpen 16.upperLipEntity.position.y = originalUpperLipY + 0.05 * upperLip 17.eyeLeftEntity.scale.z = 1 - eyeBlinkLeft 18.eyeRightEntity.scale.z = 1 - eyeBlinkRight 19. 20.let cameraTransform = self.parent?.transformMatrix(relativeTo: nil) 21.let faceTransformFromCamera = simd_mul(simd_inverse(cameraTransform!), faceAnchor.transform) 22.let rotationEulers = faceTransformFromCamera.eulerAngles 23.let mirroredRotation = Transform(pitch: rotationEulers.x, yaw: -rotationEulers.y + .pi, roll: rotationEulers.z) 24.self.orientation = mirroredRotation.rotation 25.} 在代码清单58中,我们首先从ARFaceAnchor中获取BlendShapes表情运动因子集合,并从中取出感兴趣的表情运动因子,然后利用这些表情因子对机器头像模型中的子实体对象相关属性进行调整,最后还处理了人脸与模型旋转关系的对应问题。 在支持同时开启前置与后置摄像头的设备上编译运行,当移动设备在检测到的水平平面时放置好机器头像模型,将前置摄像头对准人脸,可以使用人脸表情驱动机器头像模型,当人体头部旋转时,机器头像模型也会相应地进行旋转,实现效果如图511所示。 图511使用前置摄像头检测到的人脸姿态控制后置摄像头中的机器头像模型效果 本节演示的是一个简单的案例,但完整实现了利用前置摄像头采集的人脸表情信息控制后置摄像头中机器头像模型的功能,并且我们也看到,在使用前置摄像头时,后置摄像头可以同时进行世界跟踪。 由于RealityKit目前没有控制网格变形的函数,要实现利用人脸表情控制驱动模型的功能,开发者只能手动进行人脸表情与模型状态变化的绑定,人工计算模型中各子对象的位置与方向,这是一个比较麻烦且容易出错的过程。 经过测试发现,ARKit对人脸表情的捕捉还是比较准确的,在使用配备深度相机的设备时,捕捉精度比较高,可以应付一般应用需求。 在本章中,我们对ARKit人脸检测相关知识进行了学习,对人脸网格、人脸贴纸、挂载虚拟物体、BlendShapes进行了演示,但人脸检测的应用远远比演示的要多,例如可以利用人脸特定表情解锁、使用人眼凝视点判断使用者关注点实现人眼凝视交互等。通过学习,我们也发现,目前RealityKit在一些人脸功能支持上还有不足,部分功能需要借助SceneKit或者其他技术才能实现。