【iOS ARKit】同时开启前后摄像头BlendShapes

2024-01-27 07:12

本文主要是介绍【iOS ARKit】同时开启前后摄像头BlendShapes,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

      在上一节中已经了解了 iOS ARkit 进行BlendShapes的基本操作,这一小节继续实践同时开启前后摄像头进行人脸捕捉和世界追踪。

      iOS设备配备了前后两个摄像头,在运行AR 应用时,需要选择使用哪个摄像头作为图像输人。最常见的AR 体验使用设备后置摄像头进行世界跟踪、虚实融合,通常使用 ARWorldTrackingConfiguration 配置跟踪使用者的真实环境。除了进行虚实融合,我们通常还利用后置摄像头采集的图像信息评估真实世界中的光照情况、对真实环境中的2D图像或者3D物体进行检测等。

       对具备前置深度相机(TrueDepth Camera)或者A12及以上处理器的设备,使用 ARFaceTrackingConfiguration配置可以实时进行人脸检测跟踪,实现人脸姿态和表情的捕捉。拥有前置深度相机或 A12及以上处理器硬件的iPhone/iPad,在运行iOS 13及以上系统时,还可以同时开启设备前后摄像头,即同时进行人脸检测和世界跟踪。这是一项非常有意义且实用的功能,意味着使用者可以使用表情控制场景中的虚拟物体,实现除手势与语音之外的另一种交互方式。

      在 RealityKit 中,同时开启前后摄像头需要使用 ARFaceTrackingConfiguration 配置或者ARWorldTrackingConfiguration 配置之一。使用 ARFaceTracking Configuration 配置时将其 supportsWorldTracking属性设置为 true,使用 ARWorldTrackingConfiguration 配置时将其 userFaceTrackingEnabled 属性设置为true 都可以在支持人脸检测的设备上同时开启前后摄像头。

     同时开启前后摄像头后,RealityKit 会使用后置摄像头跟踪现实世界,同时也会通过前置摄像头实时检测人脸信息,包括人脸表情信息。

     需要注意的是,并不是所有设备都支持同时开启前后摄像头,只有符合前文所描述的设备才支持该功能,因此,在使用之前也应当对该功能的支持情况进行检查。在不支持同时开启前后摄像头的设备上应当执行另外的策略,如提示用户进行只使用单个摄像头的操作。

     在下面的演示中,我们会利用后置摄像头的平面检测功能,在检测到的水平平面上放置机器头像模型,然后利用从前置摄像头中捕获的人脸表情信息驱动头像模型。核心代码如代码如下所示。

//
//  BlendShapeRobot.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/1/25.
//import SwiftUI
import ARKit
import RealityKitstruct BlendShapeRobot: View {var body: some View {BlendShapeRobotContainer().edgesIgnoringSafeArea(.all)}
}struct BlendShapeRobotContainer :UIViewRepresentable{func makeUIView(context: Context) -> ARView {let arView = ARView(frame: .zero)return arView}func updateUIView(_ uiView: UIViewType, context: Context) {guard ARFaceTrackingConfiguration.isSupported else {return}let config = ARWorldTrackingConfiguration()config.userFaceTrackingEnabled = trueconfig.isLightEstimationEnabled = trueconfig.worldAlignment = .gravityconfig.planeDetection = .horizontaluiView.session.delegate = context.coordinatoruiView.automaticallyConfigureSession = falseuiView.session.run(config, options: [])let planeAnchor = AnchorEntity(plane:.horizontal)planeAnchor.addChild(context.coordinator.robotHead)uiView.scene.addAnchor(planeAnchor)}func makeCoordinator() -> Coordinator {Coordinator()}class Coordinator: NSObject, ARSessionDelegate{var robotHead = RobotHead()func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {for anchor in anchors {guard  let anchor = anchor as? ARFaceAnchor else {continue}robotHead.update(with: anchor)}}}}

       在代码中,我们首先对设备支持情况进行检查,在确保设备支持同时开启前后摄像头功能时使用 ARWorldTrackingConfiguration 配置并运行 AR进程,然后在检测到平面时将机器头像模型放置于平面上,最后利用 session(didUpdate frame:) 代理方法使用实时捕获到的人脸表情数据更新机器头像模型,从而达到了使用人脸表情驱动场景中模型的目的。需要注意的是代码中 userFaceTrackingEnabled 必须设置为true,并且开启平面检测功能,另外,为更好地组织代码,我们将与模型及表情驱动相关的代码放到了RobotHead类中。RobotHead类用于管理机器头像模型加载及使用表情数据驱动模型的工作,关键代码如下所示。

//
//  RobotHead.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/1/25.
//import RealityKit
import ARKitclass RobotHead: Entity, HasModel {// Default color valuesprivate let eyeColor: SimpleMaterial.Color = .blueprivate let eyebrowColor: SimpleMaterial.Color = .brownprivate let headColor: SimpleMaterial.Color = .greenprivate let lipColor: SimpleMaterial.Color = .lightGrayprivate let mouthColor: SimpleMaterial.Color = .grayprivate let tongueColor: SimpleMaterial.Color = .redprivate let clearColor: SimpleMaterial.Color = .clearprivate var originalJawY: Float = 0private var originalUpperLipY: Float = 0private var originalEyebrowY: Float = 0private lazy var eyeLeftEntity = findEntity(named: "eyeLeft")!private lazy var eyeRightEntity = findEntity(named: "eyeRight")!private lazy var eyebrowLeftEntity = findEntity(named: "eyebrowLeft")!private lazy var eyebrowRightEntity = findEntity(named: "eyebrowRight")!private lazy var jawEntity = findEntity(named: "jaw")!private lazy var upperLipEntity = findEntity(named: "upperLip")!private lazy var headEntity = findEntity(named: "head")!private lazy var tongueEntity = findEntity(named: "tongue")!private lazy var mouthEntity = findEntity(named: "mouth")!private lazy var jawHeight: Float = {let bounds = jawEntity.visualBounds(relativeTo: jawEntity)return (bounds.max.y - bounds.min.y)}()private lazy var height: Float = {let bounds = headEntity.visualBounds(relativeTo: nil)return (bounds.max.y - bounds.min.y)}()required init() {super.init()if let robotHead = try? Entity.load(named: "robotHead") {robotHead.position.y += 0.05addChild(robotHead)} else {fatalError("无法加载模型.")}originalJawY = jawEntity.position.yoriginalUpperLipY = upperLipEntity.position.yoriginalEyebrowY = eyebrowLeftEntity.position.ysetColor()}func setColor(){headEntity.color = headColoreyeLeftEntity.color = eyeColoreyeRightEntity.color = eyeColoreyebrowLeftEntity.color = eyebrowColoreyebrowRightEntity.color = eyebrowColorupperLipEntity.color = lipColorjawEntity.color = lipColormouthEntity.color = mouthColortongueEntity.color = tongueColor}// MARK: - Animations/// - Tag: InterpretBlendShapesfunc update(with faceAnchor: ARFaceAnchor) {// Update eyes and jaw transforms based on blend shapes.let blendShapes = faceAnchor.blendShapesguard let eyeBlinkLeft = blendShapes[.eyeBlinkLeft] as? Float,let eyeBlinkRight = blendShapes[.eyeBlinkRight] as? Float,let eyeBrowLeft = blendShapes[.browOuterUpLeft] as? Float,let eyeBrowRight = blendShapes[.browOuterUpRight] as? Float,let jawOpen = blendShapes[.jawOpen] as? Float,let upperLip = blendShapes[.mouthUpperUpLeft] as? Float,let tongueOut = blendShapes[.tongueOut] as? Floatelse { return }eyebrowLeftEntity.position.y = originalEyebrowY + 0.03 * eyeBrowLefteyebrowRightEntity.position.y = originalEyebrowY + 0.03 * eyeBrowRighttongueEntity.position.z = 0.1 * tongueOutjawEntity.position.y = originalJawY - jawHeight * jawOpenupperLipEntity.position.y = originalUpperLipY + 0.05 * upperLipeyeLeftEntity.scale.z = 1 - eyeBlinkLefteyeRightEntity.scale.z = 1 - eyeBlinkRightlet cameraTransform = self.parent?.transformMatrix(relativeTo: nil)let faceTransformFromCamera = simd_mul(simd_inverse(cameraTransform!), faceAnchor.transform)let rotationEulers = faceTransformFromCamera.eulerAngleslet mirroredRotation = Transform(pitch: rotationEulers.x, yaw: -rotationEulers.y + .pi, roll: rotationEulers.z)self.orientation = mirroredRotation.rotation}
}extension Entity {var color: SimpleMaterial.Color? {get {if let model = components[ModelComponent.self] as? ModelComponent,let color = (model.materials.first as? SimpleMaterial)?.color.tint {return color}return nil}set {if var model = components[ModelComponent.self] as? ModelComponent {if let color = newValue {model.materials = [SimpleMaterial(color: color, isMetallic: false)]} else {model.materials = []}components[ModelComponent.self] = model}}}
}extension simd_float4x4 {// Note to ourselves: This is the implementation from AREulerAnglesFromMatrix.// Ideally, this would be RealityKit API when this sample gets published.var eulerAngles: SIMD3<Float> {var angles: SIMD3<Float> = .zeroif columns.2.y >= 1.0 - .ulpOfOne * 10 {angles.x = -.pi / 2angles.y = 0angles.z = atan2(-columns.0.z, -columns.1.z)} else if columns.2.y <= -1.0 + .ulpOfOne * 10 {angles.x = -.pi / 2angles.y = 0angles.z = atan2(columns.0.z, columns.1.z)} else {angles.x = asin(-columns.2.y)angles.y = atan2(columns.2.x, columns.2.z)angles.z = atan2(columns.0.y, columns.1.y)}return angles}
}

     在代码中,我们首先从 ARFaceAnchor 中获取 BendShapes 表情运动因子集合,并从中取出感兴趣的运动因子,然后利用这些表情因子对机器头像模型中的子实体对象相关属性进行调整,最后处理了人脸与模型旋转关系的对应问题。

    在支持同时开启前置与后置摄像头的设备上编译运行,当移动设备在检测到的水平平面时放置好机器头像模型,将前置摄像头对准人脸,可以使用人脸表情驱动机器头像模型,当人体头部旋转时,机器头像模理也会相应地进行旋转,实现效果如图 所示。

   以上演示的是一个简单的实例,完整实现了利用前置摄像头采集的人脸表情信息控制后置摄像头模型的功能。在使用前置摄像头时,后置摄像头可以进行世界追踪。 由于Realiy Kit 目前沒有控制网格变形的函数,要实现利用人脸表情控制驱动模型的功能,需要手动进行人脸表情与模型状态变化的绑定,人工计算模型中各因子对应的位置与方问,这是一个比较容易出错的过程。经过测试发现,ARKit 对人脸表情的捕捉还是比较准确的,在使用配备深度相机的设备时,捕捉精度较高,可以应付一般应用需求。

具体代码地址:https://github.com/duzhaoquan/ARkitDemo.git

这篇关于【iOS ARKit】同时开启前后摄像头BlendShapes的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/649439

相关文章

Vue 调用摄像头扫描条码功能实现代码

《Vue调用摄像头扫描条码功能实现代码》本文介绍了如何使用Vue.js和jsQR库来实现调用摄像头并扫描条码的功能,通过安装依赖、获取摄像头视频流、解析条码等步骤,实现了从开始扫描到停止扫描的完整流... 目录实现步骤:代码实现1. 安装依赖2. vue 页面代码功能说明注意事项以下是一个基于 Vue.js

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

一文详解kafka开启kerberos认证的完整步骤

《一文详解kafka开启kerberos认证的完整步骤》这篇文章主要为大家详细介绍了kafka开启kerberos认证的完整步骤,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、kerberos安装部署二、准备机器三、Kerberos Server 安装1、配置krb5.con

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

开启mysql的binlog日志步骤详解

《开启mysql的binlog日志步骤详解》:本文主要介绍MySQL5.7版本中二进制日志(bin_log)的配置和使用,文中通过图文及代码介绍的非常详细,需要的朋友可以参考下... 目录1.查看是否开启bin_log2.数据库会把日志放进logs目录中3.查看log日志总结 mysql版本5.71.查看

idea如何开启菜单栏

《idea如何开启菜单栏》文章介绍了如何通过修改IntelliJIDEA的样式文件`ui.lnf.xml`来重新显示被关闭的菜单栏,并分享了解决问题的步骤... 目录ijsdea开启菜单栏第一步第二步总结idea开启菜单栏手贱关闭了idea的js菜单栏,花费了半个小时终于解决,记录并分享一下第一步找

hadoop开启回收站配置

开启回收站功能,可以将删除的文件在不超时的情况下,恢复原数据,起到防止误删除、备份等作用。 开启回收站功能参数说明 (1)默认值fs.trash.interval = 0,0表示禁用回收站;其他值表示设置文件的存活时间。 (2)默认值fs.trash.checkpoint.interval = 0,检查回收站的间隔时间。如果该值为0,则该值设置和fs.trash.interval的参数值相等。

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

4-4.Andorid Camera 之简化编码模板(获取摄像头 ID、选择最优预览尺寸)

一、Camera 简化思路 在 Camera 的开发中,其实我们通常只关注打开相机、图像预览和关闭相机,其他的步骤我们不应该花费太多的精力 为此,应该提供一个工具类,它有处理相机的一些基本工具方法,包括获取摄像头 ID、选择最优预览尺寸以及打印相机参数信息 二、Camera 工具类 CameraIdResult.java public class CameraIdResult {