【iOS ARKit】BlendShapes
BlendShapes 基础介绍
利用前置摄像头采集到的用户面部表情特征,ARKit 提供了一种更加抽象的表示面部表情的方式,这种表示方式叫作 BlendShapes,BlendShapes 可以翻译成形状融合,在3ds Max 中也叫变形器,这个概念原本用于描述通过参数控制模型网格的位移,苹果公司借用了这个概念,在ARKit 中专门用于表示通过人脸表情因子驱动模型的技术。
BlendShapes 在技术上是一组存储了用户面部表情特征运动因子的字典,共包含52 组特征运动数据,ARKit会根据摄像机采集的用户表情特征值实时地设置对应的运动因子。利用这些远动因子可以驱动2D 或者3D人脸模型,这些模型即可呈现与用户一致的表情。
ARKit实时提供全部52组运动因子,这52组运动因子中包括7组左眼运动因子数据、7组右眼运动因子数据、27组嘴与下巴运动因子数据、10组眉毛脸颊鼻子运动因子数据、1组舌头运动因子数据。1在使用时可以选择利用全部或者只利用其中的一部分,如只关注眼睛运动,则只利用眼睛相关运动因数据即可。
每一组运动因子表示一个 ARKit识别的人脸表情特征,每一组运动因子都包括一个表示人脸特定表情的定位符与一个表示表情程度的浮点类型值,表情程度值的的围为[0,1],其中0表示没有表情,1表示完全表情。
ARKit 会实时捕提到这些运动因子,利用这些运动因子我们可以驱动 2D、3D人脸模型,这些模型会同步用户的面部表情,当然,我们可以只取其中的一部分所关注的运动因子,但由于人脸表情通常与著干组表情因子相关联,如果想精确地模拟用户的表情,建议使用全部运动因子数据。
BlendShapes 技术原理
在 ARKit 中,对人脸表情特征信息定义了52组运动因子数据,其使用 BlendShapeLocation 作为表情定位符,表情定位符定义了特定表情,如 mouthSmileLeft、mouthSmileRight 等,与其对应的运动因子则表示表情程度,这 52 组运动因子数据如下表 所示。
| 区域 | 表情定位符 | 描述 | 
| 
 
 
 
 
 Left Eye(7) 
 
 
 | eyeBlinkLeft | 左眼眨眼 | 
| eyeLookDownLeft | 左眼目视下方 | |
| eyeLookInLeft | 左眼注视鼻尖 | |
| eyeLookOutLeft | 左眼向左看 | |
| eyeLookUpLeft | 左眼目视上方 | |
| eyeSquintLeft | 左眼眯眼 | |
| eye WideLeft | 左眼睁大 | 
| 区域 | 表情定位符 | 描述 | 
| 
 
 
 
 
 Right Eye (7) 
 
 
 | eyeBlinkRight | 右眼眨眼 | 
| eyeLookDownRight | 右眼目视下方 | |
| eyeLookInRight | 右眼注视鼻尖 | |
| eyeLookOutRight | 右眼向左看 | |
| eyeLookUpRight | 右眼目视上方 | |
| eyeSquintRight | 右眼眯眼 | |
| eye WideRight | 右眼睁大 | |
| 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 Mouth and Jaw(27) 
 
 
 
 
 
 
 
 
 
 
 
 
 | jaw Forward | 努嘴时下巴向前 | 
| jawLeft | 撇嘴时下巴向左 | |
| jawRight | 撇嘴时下巴向有 | |
| jawOpen | 张哦时下巴向下 | |
| mouthClose | 闭嘴 | |
| mouthFunnel | 稍张嘴并双唇张开 | |
| mouthPucker | 抿嘴 | |
| mouthLeft | 向左撇嘴 | |
| mouthRight | 向右撇嘴 | |
| mouthSmileLeft | 左撇嘴笑 | |
| mouthSmileRight | 右撇嘴笑 | |
| mouthFrownLeft | 左嘴唇下压 | |
| mouthFrownRight | 右嘴唇下压 | |
| mouthDimpleLeft | 左嘴唇向后 | |
| mouthDimpleRight | 右嘴唇向后 | |
| mouthStretchLeft | 左嘴角向左 | |
| mouthStretchRight | 右嘴角向右 | |
| mouthRollLower | 下嘴唇卷向里 | |
| mouthRollUpper | 下嘴唇卷向上 | |
| mouthShrugLower | 下嘴唇向下 | |
| mouthShrug Upper | 上嘴脣向上 | |
| mouthPressLeft | 下嘴唇压向左 | |
| mouthPressRight | 下嘴唇压向右 | |
| mouthLowerDownLeft | 下嘴唇压向左下 | |
| mouthLowerDownRight | 下嘴唇压向右下 | |
| mouthUpperUpLeft | 上嘴唇压向左上 | |
| mouthUpperUpRight | 上嘴唇压向右上 | |
| 
 
 
 
 Eyebrowa (5) 
 
 | browDownLeft | 左眉向外 | 
| browDownRight | 右眉向外 | |
| browinnerUp | 蹙眉 | |
| brow OuterUpLeft | 左眉向左上 | |
| brow OuterUpRight | 右眉向有上 | |
| 
 
 Cheeks (3) 
 | cheekPuff | 脸颊向外 | 
| cheekSquintLeft | 左脸颊向上并回旋 | |
| cheekSquintRight | 右脸颊间上并回旋 | 
| 区域 | 表情定位符 | 描 | 
| 
 Nose (2) | noseSneerLeft | 左蹙鼻子 | 
| noseSneerRight | 右蹙鼻子 | |
| Tongue (1) | tongueVut | 吐舌头 | 
需要注意的是,在表中表情定位符的命名是基于人脸方向的,如 eyeBlinkRight 定义的是人脸右眼眨眼,但在呈现3D模型时我们镜像了模型,看到的人脸模型右眼其实在左边。有了表情特征运动因子后,就可以使用 SceneKit 中的SCNMorpher. SetWeight()方法进行网格融合,该方法原型为:setWeight(_ weight: CGFloat, forTargetNamed targetName: Int) ;该方法有两个参数,for TargetNamed 参数需要融合的网格变形器名,即上文中的 BlendShapeLocation 名;weight 参数为需要设置的BlendShape 权重值,取值范围为[0,1]。
BlendShapes 代码示例
使用 ARKit 的 BlendShapes 功能需要满足两个条件:第一是有一个配备有深度相机或者 A12 及以上处理器的移动设备;第二是有一个 BlendShapes 已定义好的模型,为简化操作,这个模型的 BlendShapes 名称定义应与表5-5完全对应。为模型添加 BlendShapes 可以在3ds Max 软件中定义变形器,并做好对应的网格变形。在满足以上两个条件后,使用 BlendShapes 就变得相对简单了,实现的思路如下:
(1)获取 ARKit 表情特征运动因子。这可以通过检查 ARFaceAnchor 获取相应数据,在检测到人脸时,ARFaceAnchor 会返回一个 blendShapes 集合,该集合包含所有52组表情特征运动因子数据。
(2)绑定 ARKit 的表情特征定位符与模型中的变形器,使其保持一致。
(3)当人脸 ARFaceAnchor 发生更新时,实时更新所有与表情特征运动因子相关联的模型变形器。
核心示例代码如下:
//
//  BlendShapeView.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/1/25.
//
import SwiftUI
import ARKit
import RealityKit
struct BlendShapeView: View {
    var body: some View {
        BlendShapeViewContainer().edgesIgnoringSafeArea(.all).navigationTitle("BlendShape")
    }
}
struct BlendShapeViewContainer :UIViewRepresentable{
    
    
    func makeUIView(context: Context) -> ARSCNView {
        let arSCNView = ARSCNView(frame: .zero)
        return arSCNView
    }
    func updateUIView(_ uiView: UIViewType, context: Context) {
        guard ARFaceTrackingConfiguration.isSupported else {
            return
        }
        let config = ARFaceTrackingConfiguration()
        config.isWorldTrackingEnabled = false
        config.isLightEstimationEnabled = true
        config.maximumNumberOfTrackedFaces = 1
        config.providesAudioData = false
        
        uiView.delegate = context.coordinator
        uiView.autoenablesDefaultLighting = true
        uiView.allowsCameraControl = true
        uiView.session.run(config, options: [])
        
        
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator: NSObject, ARSCNViewDelegate{
        var contentNode: SCNReferenceNode? = nil
        private lazy var head = contentNode?.childNode(withName: "head", recursively: true)
        func modelSetup() {
            if let filePath = Bundle.main.path(forResource: "BlendShapeFace", ofType: "scn") {
                let referenceURL = URL(fileURLWithPath: filePath)
                self.contentNode = SCNReferenceNode(url: referenceURL)
                self.contentNode?.load()
                self.head?.morpher?.unifiesNormals = true
                self.contentNode?.scale = SCNVector3(0.01,0.01,0.01)
                self.contentNode?.position.x += 0.2
            }
        }
        
        func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
            guard anchor is ARFaceAnchor else{
                return
            }
            modelSetup()
            if let contentNode = contentNode {
                node.addChildNode(contentNode)
            }
            
            
        }
        func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
            
            guard let faceAnchor = anchor as? ARFaceAnchor else { return }
            DispatchQueue.main.async {
                for (key, value) in faceAnchor.blendShapes {
                    if let fValue = value as? Float {
                        self.head?.morpher?.setWeight(CGFloat(fValue), forTargetNamed: key.rawValue)
                    }
                }
            }
        }
        
    }
    
}
实现 BlendShapes 核心逻辑很清晰,即使用检测到的人脸表情驱动模型对应的表情,因此人脸表情与模型变形器(BlendShapes)必须建立一一对应关系,我们可以选择手动逐个绑定,也可以在建模时将变形器名与 ARKit 中的BlendShapeLocation 定位符名按表5-5所示完全对应以简化手动绑定。为实现实时驱动的效果,需要实时地更新 ARKit 检测到的人脸表情因子到模型变形器。
运行本示例,AR应用启动后会自动开启前置摄像头,当检测到人脸时就会在该人脸位置挂载虚拟头像,当人脸表情发生变化时,虚拟头像模型对应表情也会发生变化,BlendShapes 效果如上图 所示。
具体代码地址:
 
                    
                     
                    
                 
                    
                


 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号