【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…...
【c++学习】数据结构中的栈
c栈 栈代码用线性表实现栈用链表实现栈 栈 栈:先进后出 只对栈顶元素进行操作,包括新元素入栈、栈顶元素出栈和查看栈顶元素(只支持对栈顶的增、删、查)。 代码 下述代码实现了栈及其接口 包括对栈顶的增、删、查以及查看栈的大…...
新建react项目,react-router-dom配置路由,引入antd
提示:reactrouter6.4版本,与reactrouter5.0的版本用法有区别,互不兼容需注意 文章目录 前言一、创建项目二、新建文件并引入react-router-dom、antd三、配置路由跳转四、效果五、遇到的问题六、参考文档总结 前言 需求:新建react项…...
Transformer and Pretrain Language Models3-6
Pretrain Language Models预训练语言模型 content: language modeling(语言模型知识) pre-trained langue models(PLMs)(预训练的模型整体的一个分类) fine-tuning approaches GPT and BERT(…...
Linux系统中编写bash脚本进行mysql的数据同步
一、为何要用脚本做数据同步 (一)、问题 我们的视频监控平台云服务器,需要向上级的服务器定期同步一些数据表的数据,前期做了个程序,可以实现同步。但是,现在数据库的结构改了,结果又需要该程序…...
光耦驱动继电器电路图大全
光耦驱动继电器电路图(一) 注: 1U1-1脚可接12V,也可接5V,1U1导通,1Q1导通,1Q1-30V,线圈两端电压为11.7V. 1U1-1脚不接或接地,1U1不通,1Q1截止,1…...
【AI量化分析】小明在量化中使用交叉验证原理深度分析解读
进行交叉验证好处 提高模型的泛化能力:通过将数据集分成多个部分并使用其中的一部分数据进行模型训练,然后使用另一部分数据对模型进行测试,可以确保模型在未见过的数据上表现良好。这样可以降低模型过拟合或欠拟合的风险,提高模…...
2024最新版Visual Studio Code安装使用指南
2024最新版Visual Studio Code安装使用指南 Installation and Usage Guide for the Latest Visual Studio Code in 2024 By JacksonML Visual Studio Code最新版1.85已经于2023年11月由其官网 https://code.visualstudio.com正式发布,这是微软公司2024年发行的的最…...
接口请求重试八种方法
请求三方接口需要加入重试机制 一、循环重试 在请求接口的代码块中加入循环,如果请求失败则继续请求,直到请求成功或达到最大重试次数。 int retryTimes 3; for(int i 0;i < retryTimes;i){try{//请求接口的代码break;}catch(Exception e){//处理…...
【Linux 基础】常用基础指令(上)
文章目录 一、 创建新用户并设置密码二、ls指令ls指令基本概念ls指令的简写操作 三、pwd指令四、cd指令五、touch指令六、rm指令七、mkdir指令八、rmdir 指令 一、 创建新用户并设置密码 ls /home —— 查看存在多少用户 whoami —— 查看当前用户名 adduser 用户名 —— 创建新…...
【RT-DETR有效改进】EfficientFormerV2移动设备优化的视觉网络(附对比试验效果图)
前言 大家好,我是Snu77,这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进,内容持续更新,每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本,同时修改内容也支持Re…...
临朐县哪里有做网站的/网站优化包括哪些内容
进程间的通信:传递一些变量来相互沟通; #管道 #有名管道 #信号量 #信号 #共享内存 #消息队列 #套接字 # 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关…...
网站开发工程师绩效/站长工具seo综合查询怎么使用的
linux开发感受最深的莫过于linux的动态库加载了,仅仅凭借这一点这支撑起了公司的灵活多变的业务。一个嵌入式Linux系统粗略的可以划分为系统层和应用层;应用层中基本都是10个以上的App来支撑起一个功能完备的嵌入式产品,这么多代码维护起来必…...
百度网站权重/青岛seo排名公司
等价类划分方法 一.方法简介 1.定义 是把所有可能的输入数据,即程序的输入域划分成若干部分(子集),然后从每一个子集中选取少数具有代表性的数据作为测试用例。该方法是一种重要的,常用的黑盒测试用例设计方法。 2.划分等价类: 等…...
制作精美网站建设售后完善/西安专业seo
很多人会质疑10天能不能跑1万公里,感觉不是运输很不容易,但如果是远程自驾游就很有可能了。按每小时90公里计算,开上7个小时,平均每天就跑600多公里,如果两个司机换着开,每天跑1200公里也很正常,…...
网站除了做流量还需要什么软件/亿驱动力竞价托管
File f new File("~"); //创建源InputStream is new FileInputStream(f); //选择流is.read() 读取单个数据,并使游标下移 //操作(读取)is.close() //释放资源,输入流读取后必须释放资源public class test{public static void main(String[]args){//创建源…...
销售网站怎么做的/优化系统软件
select *from emp where months_between(sysdate,hhiredate与当前时间差距超过25年,月份/12表示一年 sysdate为当前日期 months_between(sysdate,hiredate)计算当前日期 - hiredate 的月份 months_between(sysdate,hiredate)/12计算当前日期 - hiredate 的年数 mont…...