【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
在上一节中已经了解了 iOS ARkit 进行BlendShapes的基本操作,这一小节继续实践同时开启前后摄像头进行人脸捕捉和世界追踪。 iOS设备配备了前后两个摄像头,在运行AR 应用时,需要选择使用哪个摄像头作为图像输人。最常见的AR 体验使用设备后置…...
Vue3动态插入组件
一、使用<component>is实现动态组件插入 <component>:一个用于渲染动态组件或元素的“元组件”。 :is : 要渲染的实际组件,当 is 是字符串,它既可以是 HTML 标签名也可以是组件的注册名。 <script> import Foo from ./F…...
介绍一下OpenCV中常用的图像处理函数
OpenCV中常用的图像处理函数有很多,以下是其中一些函数的介绍: - cvLoadImage():读入图像函数。 - imshow():显示图像函数。 - imwrite():保存图像函数。 - Mat srcImage imread():读入图像函数。 - …...
vscode vim 快捷键汇总
需满足操作: 上下移动按照 word 移动选中增删改查找字符/变量移动、增加、复制、删除 行选中多个相同的变量/字符屏幕移动增加多个光标快速注释 上下左右移动 CommandDescription🔢 hleft (also: CTRL-H, BS, or Left key)🔢 lright (also…...
npm官方注册表和淘宝镜像切换
1.切换到淘宝镜像 加快npm包的下载速度, //已失效 //npm config set registry https://registry.npm.taobao.org/ npm config set registry https://registry.npmmirror.com这会将npm的注册表设置为淘宝镜像 查看: npm config get registry如果返回的…...

LFU算法
LFU算法 Least Frequently Used(最不频繁使用) Leetcode有原题,之前手写过LRU,数据结构还是习惯于用java实现,实现是copy的评论题解。 题解注释写的很清楚 大致就是说LFUCache类维护一个存放node的map,同…...

JVM系列-7内存调优
👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家📕系列专栏:Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理🔥如果感觉博主的文…...

[UI5 常用控件] 01.Text
文章目录 前言1. 普通文本2. 长文本:3. 设置最大显示行数 ( maxLines3 )4. 单行显示 ( wrappingfalse )5. 显示空白符 ( renderWhitespacetrue )6. 使用 - 连接单词:只适用于英文 ( wrappingTypeHyphenated )7. 空白时使用 - 代替 ( emptyIndicatorModeOn )8. JSON数…...

C语言之指针的地址和指向的内容总结(八十四)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...
1月25日,每日信息差
第一、中国和新加坡互免签证,新加坡酒店搜索量较发布前增长4倍。去哪儿数据显示,新加坡酒店搜索量较发布前增长4倍,仍在持续增长中。同程旅行数据显示,消息发布半小时内,同程旅行平台新加坡相关搜索热度较前日同一时段…...
前端工程化之:webpack1-3(模块化兼容性)
一、模块化兼容性 由于 webpack 同时支持 CommonJs 和 ES6 module ,因此需要理解它们互操作时 webpack 是如何处理的。 二、同模块化标准 如果导出和导入使用的是同一种模块化标准,打包后的效果和之前所说的模块化没有任何差异。 CommonJSÿ…...
JDK8新特性(一)
一、概述 JDK8,又称为JDK 1.8,是Java语言开发的里程碑版本。这个版本引入了众多令人兴奋的新特性,让Java更加灵活和强大。其中,最引人注目的新特性包括Lambda表达式、方法引用、默认方法、Stream API、新的日期和时间API以及Optio…...
java实现ftp协议远程网络下载文件
引言 在开发过程中,偶尔会遇到网络文件在FTP服务上存储着,对于这种情况想要下载到本地还有些麻烦,我们直接上世界上最简单的代码。 How to do 1.提前引入包 <!--hutool万能工具包--><dependency><groupId>cn.hutool<…...

深入浅出理解目标检测的NMS非极大抑制
一、参考资料 物体检测中常用的几个概念迁移学习、IOU、NMS理解 目标定位和检测系列(3):交并比(IOU)和非极大值抑制(NMS)的python实现 Pytorch:目标检测网络-非极大值抑制(NMS) …...

HbuilderX报错“Error: Fail to open IDE“,以及运行之后没有打开微信开发者,或者运行没有反应的解决办法
开始 问题:HbuilderX启动时,打开微信开发者工具报错"Error: Fail to open IDE",以及运行之后没有打开微信开发者,或者运行没有反应的解决办法! 解决办法: 按照步骤一步一步完成分析,除非代码报错,否则都是可以启动的 第一步:检查HbuildX是否登录账号 第二步:检查微信…...
【Go 快速入门】基础语法 | 流程控制 | 字符串
文章目录 基础语法值变量常量运算符指针new 和 make 区别 字符串byte 和 rune 类型 流程控制for 循环If else 分支switch 分支 基础语法 项目代码地址:02-basicgrammar 值 基本类型值 Go 最基础的数据类型,比如整型、浮点型、布尔型。 复合类型值 …...

腾讯云轻量应用Ubuntu服务器如何一键部署幻兽帕鲁Palworld私服?
幻兽帕鲁/Palworld是一款2024年Pocketpair开发的开放世界生存制作游戏,在帕鲁的世界,玩家可以选择与神奇的生物“帕鲁”一同享受悠闲的生活,也可以投身于与偷猎者进行生死搏斗的冒险。而帕鲁可以进行战斗、繁殖、协助玩家做农活,也…...
Redis的SDS你了解吗?
初识SDS: Redis的String和其他很多编程语言中的语义相似,它能够表达3种值的类型: 1.字符串 2.整数 3.浮点数 三种类型根据具体场景由Redis完成相互之间的自动转换,并且根据需要选取底层的承载方式,Redis内部&#x…...
C#中常见的软件设计模式及应用场景
文章目录 前言1、单例模式 (Singleton)1.1 详细说明1.2 应用场景示例 2、工厂模式 (Factory Method)2.1 详细说明2.2 应用场景示例 3、观察者模式 (Observer)3.1 详细说明3.2 应用场景示例 4、策略模式 (Strategy)4.1 详细说明4.2 应用场景示例 5、适配器模式 (Adapter)5.1 详细…...
字符串相关函数和文件操作
文章目录 1. C/C 字符串概述1.1 字符串常量1.2 字符数组 2. 字符串函数2.1 拷贝赋值功能相关函数(覆盖)2.1.1 strcpy2.1.2 strncpy2.1.3 memcpy2.1.4 memmove2.1.5 memset2.1.6 注意小点2.1.7 【函数区别】 2.2 追加功能相关函数2.2.1 strcat2.2.2 strnc…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...