【iOS ARKit】人脸追踪之挂载虚拟元素

人脸跟踪(Face Tracking)是指将人脸检测扩展到视频序列,跟踪同一张人脸在视频序列中的位置。是论上讲,任何出现在视频中的人险都可以被跟踪,也即是说,在连续视频帧中检测到的人脸可以被识别为同一个人。人脸跟踪不是人脸识别的一种形式,它是根据视频序列中人脸的位置和运动推断不同视频帧中的人脸是否同一人的技术。

挂载虚拟元素

在iOS Realitykit 中,在检测到的人脸面部挂载虚拟元素的实现方式有两种:一种是通过遵循ARSesionDelegate 协议,执行 session(_ session: ARSession, didAdd anchors: LARAnchor」)方法,在获取的ARPaceAnchor 上挂载虚拟元素;另一种是与 Reality Composer结合使用。在使用第一种方式时,可以利用 ARFaceAnchor 初始化一个 AnchorEntity 类型实例,这样,ARFaceAnehor的姿态信息就可以直接被使用,典型的使用代码如下:

Swift 复制代码
  public func session(_ session: ARSession, didAdd anchors: [ARAnchor]){
           guard let pAnchor = anchors[0] as? ARObjectAnchor else {
              return
            }
            

            let objectName =  pAnchor.referenceObject.name == "jinhua" ? "toy_drummer" : "toy_robot_vintage"
            DispatchQueue.main.async {
                do{
                    let myModeEntity = try Entity.load(named: objectName)
                    let objectEntity = AnchorEntity(anchor: pAnchor)
                    objectEntity.addChild(myModeEntity)
                                                          myModeEntity.playAnimation(myModeEntity.availableAnimations[0].repeat())
                    
                    self.arView?.scene.addAnchor(objectEntity)
                } catch {
                    print("加载失败")
                }
                            
            }
            
        }

在检测到的人脸上挂载虚拟元素使用 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 以开启人脸检测功能,典型代码如代码下:

Swift 复制代码
struct FaceMaskContainer : UIViewRepresentable{
    
    
    func makeUIView(context: Context) -> ARView {
        let arView = ARView(frame: .zero)
        
        return arView
    }
    func updateUIView(_ uiView: ARView, context: Context) {
        guard ARFaceTrackingConfiguration.isSupported else {
            return
        }
       
        let config = ARFaceTrackingConfiguration()

        config.isWorldTrackingEnabled = false
        config.providesAudioData = false
        config.maximumNumberOfTrackedFaces =  1
        config.isLightEstimationEnabled = true
//        uiView.session = context.coordinator
        if let faceAnchor = try? FaceMask.loadGlass1() {
            uiView.scene.addAnchor(faceAnchor)
        }
        uiView.session.run(config,options: [.resetTracking, .removeExistingAnchors])
        context.coordinator.arView = uiView
        
        let gesture = UISwipeGestureRecognizer()
        gesture.direction = [.left,.right]
        gesture.addTarget(context.coordinator, action: #selector(context.coordinator.changeGlass(gesture:)))
        uiView.addGestureRecognizer(gesture)
    }
    
    func makeCoordinator() -> FaceMaskContainerCoordinator {
        FaceMaskContainerCoordinator()
    }
    
    class FaceMaskContainerCoordinator: NSObject {
        var arView :ARView?
        var faceMaskCount = 0
        let numberOfMasks = 5
        @MainActor @objc func changeGlass(gesture: UISwipeGestureRecognizer){
            guard let arView = arView else {
                return
            }
            let jian = gesture.direction == .left
            jian ?  (faceMaskCount -= 1) : (faceMaskCount += 1)
            if faceMaskCount < 0 {
                faceMaskCount = 5
            }
            faceMaskCount %= numberOfMasks
            switch faceMaskCount {
            case 0:
                if let g = try? FaceMask.loadGlass2(){
                    arView.scene.anchors.removeAll()
                    arView.scene.addAnchor(g)
                }
                
            case 1:
                if let g = try? FaceMask.loadIndian() {
                    arView.scene.anchors.removeAll()
                    arView.scene.addAnchor(g)
                }
                
            case 2:
                if let g = try? FaceMask.loadRabbit() {
                    arView.scene.anchors.removeAll()
                    arView.scene.addAnchor(g)
                }
                
            case 3:
                if let g = try? FaceMask.loadHelicopterPilot() {
                    arView.scene.anchors.removeAll()
                    arView.scene.addAnchor(g)
                }
                
            case 4:
                if let g = try? FaceMask.loadGlass1() {
                    arView.scene.anchors.removeAll()
                    arView.scene.addAnchor(g)
                }
                
            default:
                break
            }
        }
    }
    
   
}
struct  FaceCheckingContainer: UIViewRepresentable {
    
    @Binding var faceMetre: Bool
    
    func makeUIView(context: Context) -> ARSCNView {
        let arView = ARSCNView(frame: .zero)
        return arView
    }
    
    func updateUIView(_ uiView: ARSCNView, context: Context) {
        guard ARFaceTrackingConfiguration.isSupported else {
            return
        }
        if faceMetre {}
       
        let config = ARFaceTrackingConfiguration()

        config.isWorldTrackingEnabled = false
        config.providesAudioData = false
        config.maximumNumberOfTrackedFaces =  1
        config.isLightEstimationEnabled = true
        uiView.delegate = context.coordinator
        
        uiView.session.run(config,options: [.resetTracking, .removeExistingAnchors])
    }
    
    func makeCoordinator() -> FaceCheckingContainerCoordinator {
        FaceCheckingContainerCoordinator(self)
    }
    
    class FaceCheckingContainerCoordinator: NSObject, ARSessionDelegate,ARSCNViewDelegate {
        
        
        var parent : FaceCheckingContainer
        init(_ parent: FaceCheckingContainer) {
            self.parent = parent
        }
        
        func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
            guard  let device = renderer.device  else {
                return nil
            }
            let faceGeometry = ARSCNFaceGeometry(device: device)
            let node = SCNNode(geometry: faceGeometry)
            
            if parent.faceMetre {
                //显示图片面具
                let matrial = node.geometry?.firstMaterial
                matrial?.diffuse.contents =  "face.scnassets/face.png"
                node.geometry?.firstMaterial?.fillMode = .fill
            }else {
                //显示网格
                node.geometry?.firstMaterial?.fillMode = .lines
            }
          
            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)
        }
    }
    
    
}

在代码中,首先检查设备对人脸检测的支持情况,在设备支持时运行人脸检测配置开启测功能,然后加载由 Reality Composer 配置好的虚拟模型。本示例我们在 Reality Composer 中创建场景,每一个场景使用了一个虚拟元素,为方便切换不同的虚拟元素,我们使用了滑动手势控制场实现的效果如下图所示。

相关推荐
️ 邪神12 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】文本点击事件
flutter·ios·鸿蒙·reactnative·anroid
️ 邪神13 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】文本Text显示
flutter·ios·鸿蒙·reactnative·anroid
袁代码14 小时前
Swift 开发教程系列 - 第11章:内存管理和 ARC(Automatic Reference Counting)
开发语言·ios·swift·ios开发
海绵不是宝宝81714 小时前
IOS开发之MapKit定位国内不准的问题
ios
那就可爱多一点点16 小时前
如何处理 iOS 客户端内 Webview H5 中后台播放的音视频问题
ios·音视频
crasowas16 小时前
iOS问题记录 - 503 Service Temporarily Unavailable
ios·fastlane
货拉拉技术18 小时前
货拉拉是如何实现symbolic demangle?
ios·性能优化
hairenjing112319 小时前
适用于 Windows 11/10 电脑 的 13 个最佳文件恢复软件
人工智能·windows·macos·ios·电脑·ipad
海绵不是宝宝8171 天前
IOS开发之AR问题汇总
ios·ar
袁代码1 天前
Swift 开发教程系列 - 第8章:协议与扩展
开发语言·ios·swift·ios开发