【iOS ARKit】3D人体姿态估计实例

      与2D人体姿态检测一样,在ARKit 中,我们不必关心底层的人体骨骼关节点检测算法,也不必自己去调用这些算法,在运行使用 ARBodyTrackingConfiguration 配置的 ARSession 之后,基于摄像头图像的3D人体姿态估计任务也会启动,我们可以通过 session(_ session: ARSession, didUpdate anchors:[ARAnchor])代理方法直接获取检测到的ARBodyAnchor。

     在 ARKit 中,与检测2D图像或者 3D物体一样,在检测到3D 人体后会生成一个ARBodyAnchor 用于在现实世界和虚拟空间之间建立关联关系,绑定虚拟元素到检测的人体上。在获取 ARBodyAnchor 后,就可以通过 ARBodyAnchor. skeleton. definition. jointNames 获取所有3D人体骨骼关节点名称,通过ARBodyAnchor. skeleton. modelTransform(for:)方法取指定关节点相对 ARBodyAnchor 的位置姿态信息,通过 ARBodyAnchor. skeleton. localTransform(for: ARSkeleton. JointName)方法获取指定关节相对于其父节点的位置姿态信息。示例代码如下代码所示。

 func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
            guard let anchor = anchors.first as? ARBodyAnchor else {
                return
            }
            
           
            if !isPrinted {
                isPrinted = true
                
                //获取root节点在世界坐标系中的姿态
                let hipWordPosition = anchor.transform
                print("root transform: \(hipWordPosition)")
                //获取3d骨骼对象
                let skeleton = anchor.skeleton
                //获取相对于root节点所有节点的姿态信息数组
                let jointTranforms = skeleton.jointModelTransforms
                //获取在世界空间坐标系中所有节点的姿态信息数组
                let localTransform = skeleton.jointLocalTransforms
                //遍历姿态信息数字,通过下标遍历
                for (i, jointTransform) in jointTranforms.enumerated() {
                      
                    
                    let name = anchor.skeleton.definition.jointNames[i]
                    let parentIndex = skeleton.definition.parentIndices[i]
                    
                    guard parentIndex != -1 else {
                        continue
                    }
                    let parentJointTransform = jointTranforms[parentIndex]
                    let parentName = anchor.skeleton.definition.jointNames[parentIndex]
                    
                    
                    print("name: \(name),index: \(i), transform: \(String(describing: jointTransform)), parent name: \(parentName),parent index: \(parentIndex) parent transform: \(String(describing: parentJointTransform))")
                    
                }
                
                //通过名字遍历
                let jointNames = anchor.skeleton.definition.jointNames
                for name in jointNames {
                    let landmark = anchor.skeleton.modelTransform(for: ARSkeleton.JointName(rawValue: name))
                    let index = anchor.skeleton.definition.index(for: ARSkeleton.JointName(rawValue: name))
                    
                    print("\(name),\(String(describing: landmark)),the index is \(index) parent index is  \(anchor.skeleton.definition.parentIndices[index])")
                }
                
                
                
            }
            
        }
        
        

     代码演示了如何获取 ARKit 生成的 ARBodyAnchor;如何获取3D人体所有骨骼关节点名字集合,以及各关节点及其父节点索引;如何利用关节点名字获取该关节点相对 ARBodyAnchor 的位置信息。捕捉人体3D 姿态信息后除了进行运动姿态分析最重要的用途就是驱动3D 模型,在理解ARKit 提供的3D人体骨骼关节点数据结构信息及关联关系之后,我们就可以利用这些数据实时驱动三维模型,基本思路如下:

    (1)建立一个与关节点表一致,拥有相同人体骨骼关节点的三维模型。

    (2) 开启 3D人体姿态估计功能。

    (3)建立 ARKit 3D 人体姿态估计骨骼关节点与三维模型骨骼关节点的对应关系,并利用3D人体姿态估计骨骼关节点数据驱动三维模型骨骼关节点。

     如前文所述,我们可以从生成的 ARBodyAnchor 中获取所有骨骼关节点的位置信息,利用这些位息,就可以将模型关节点与检测到的人体骨骼关节点关联起来。为了简单起见,下面我们演示利用检的人体 ARBodyAnchor,在人眼处绘制两个球体。代码如下所示。

 func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
            guard let anchor = anchors.first as? ARBodyAnchor else {
                return
            }
            
            let bodyPosition = simd_make_float3(anchor.transform.columns.3) //位置平移信息
            robotAnchor.position = bodyPosition + robotOffset
            robotAnchor.orientation = Transform(matrix: anchor.transform).rotation
            
            if let robotCharacter = robotCharacter,robotCharacter.parent == nil {
                robotAnchor.addChild(robotCharacter)
            }
            
            //更新眼睛小球位置,
            guard let leftMatrix = anchor.skeleton.modelTransform(for: ARSkeleton.JointName(rawValue: "left_eye_joint")),
                  let rightMatrix = anchor.skeleton.modelTransform(for: ARSkeleton.JointName(rawValue: "right_eye_joint")) else {
                return
            }
            
            leftEye.position = simd_make_float3(  leftMatrix.columns.3)
            rightEye.position = simd_make_float3(rightMatrix.columns.3)
            //跟节点的位置付值给anchor
            eyeAnchor.position = simd_make_float3(anchor.transform.columns.3)
            
        }
        
        

      在代码中,我们首先创建了两个球体,代表人体的左右两只眼睛,然后在 session (: didUipdateanchors:)方法中检查 ARBodyAnchor,利用检测到的3D人体骨骼左右眼关节点(left_eye_joint 和 righ.eye_joint)信息设置并实时更新两个球体的位置及方向。需要注意的是,在实际使用人体骨骼关节点位置信息时,通过 modelTransform(for:)方法获取的关节点位置是相对于 ARBodyAnchor的位置,并不是世界坐标空间中的坐标。在上述代码中,获取某特定关节点位置信息我们使用了 modelTransform(for:)方法,通过关节点名字获取该关节点位置数据,因为关节点的位置数据存储在数组中,使用bodyAnchor.skeleton.jointModelTransforms[index]的方式效率更高,如左眼索引为54,直接将 54作为参数传递即可以获取人体左眼位置数据。上节表列出了所有91 个骨骼关节点的索引值,可以直接使用。运行该示例,在ARKit 检测到人体时,会在人体双眼处放置两个球体,效果如图所示。

      采用同样的方法,可以将获取的所有人体3D骨骼关节点数据绑定到3D模型中的骨骼关节点上,并以此来驱动3D模型的运动,这是以手工的方式绑定检测到的骨骼关节点与模型。在 RealityKit 中,使用了一个名为 BodyTrackedEntity 的实体类描述带骨骼绑定的人体模型,如果模型骨骼关节点命名与相互之间的关系与上节表所示一致,也可以直接通过使用 Body TrackedEntity.joint Transforms [3] = Transform (matrix: body Anchor. skeleton. model Transtorm (for: ARSkeleton. JointName.head)!)语句将检测到的人体关节点位置信息赋给人体模型,从而达到驱动模型的目的。

      ARKit检测到的3D人体骨骼关节点有91个,采用人工绑定骨骼关节点的工作量很大且很容易出错,为此,RealityKit 会自动检测场景中加载的 BodyTrackedEntity 实体对象,并尝试自动执行将检测到的人体骨骼关节点与模型骨骼关节点匹配,如果模型骨骼关节点命名和相互之间的关系与表7-3所示一致,则无须人工手动绑定,RealityKit会自动进行关节点绑定。因此,在模型骨骼完全符合要求的情况下,利用ARKit检测到的3D人体关节点驱动模型变得格外简单,只需要加载模型为 BodyTrackedEntity 实体对象,并添加到 AnchorEntity 中。代码如下所示。

//
//  BodyTracking3DView.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/2/1.
//

import SwiftUI

import SwiftUI
import ARKit
import RealityKit
import Combine


struct BodyTracking3DView: View {
    var body: some View {
        BodyTracking3DViewContainer().edgesIgnoringSafeArea(.all).navigationTitle("人体骨架3D检测")
    }
}

struct BodyTracking3DViewContainer:UIViewRepresentable {
   
    
    
    func makeUIView(context: Context) ->ARView {
        let arView = ARView(frame: .zero)
        return arView
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
        guard ARBodyTrackingConfiguration.isSupported else {
            return
        }
        
        context.coordinator.arView = uiView
        let config = ARBodyTrackingConfiguration()
        
        config.frameSemantics = .bodyDetection
        config.automaticSkeletonScaleEstimationEnabled = true
        
        uiView.session.delegate = context.coordinator
        
        uiView.session.run(config)
        
        
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator: NSObject,ARSessionDelegate {
        var arView : ARView? = nil
        var isPrinted = false
        var robotCharacter: BodyTrackedEntity?
        let robotOffset: SIMD3<Float> = [-0.1, 0, 0]
        let robotAnchor = AnchorEntity()
        func loadRobot(){
            var cancellable: AnyCancellable? = nil
            cancellable = Entity.loadBodyTrackedAsync(named: "robot.usdz").sink { completion in
                if case let .failure(error) = completion {
                    print("无法加载模型,错误:\(error.localizedDescription)")
                }
                cancellable?.cancel()
            } receiveValue: { body in
                body.scale = [1.0,1.0,1.0]
                
                self.robotCharacter = body
                self.arView?.scene.addAnchor(self.robotAnchor)
                cancellable?.cancel()
            }

        }
        
        func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
            guard let anchor = anchors.first as? ARBodyAnchor else {
                return
            }
//            createSphere()
            loadRobot()
        }
        
        func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
            guard let anchor = anchors.first as? ARBodyAnchor else {
                return
            }
            
            let bodyPosition = simd_make_float3(anchor.transform.columns.3) //位置平移信息
            robotAnchor.position = bodyPosition + robotOffset
            robotAnchor.orientation = Transform(matrix: anchor.transform).rotation
            
            if let robotCharacter = robotCharacter,robotCharacter.parent == nil {
                robotAnchor.addChild(robotCharacter)
            }
            
            
            
        }
        
        
    }
    
    
    
}

      在代码中,我们首先使用异步的方式加载3D人体模型,并对模型中的骨骼信息进行检查,如果模型骨骼都符合要求则生成可供驱动的3D 模型对象,然后在 session(:didUpdate anchors:)方法中实时更新模型的姿态信息。上述代码对 robotAnchor 位置进行了偏移处理,这是因为我们获取的ARBodyAnchor 所在位置为检测到的3D人体关节点的Root 位置,如果不进行偏移,则模型与人体会重合显示,代码中我们将模型向X轴负方向移动了 1m(ARBodyAnchor 位置三维空间中的位置,可以向任何方向偏移),我们也可以不加这个偏移。编译运行代码,将设备摄像头对准真实人体,在检测到人体时,加载一个机器人,并且人体姿态可以实时驱动机器人模型同步运动,效果如下图所示。

       

       经过测试,目前 ARKit 可以正确检测追踪人体正面或背面站立姿态,对坐姿也能比较好地跟踪,但不能检测跟踪倒立、俯卧姿态。并且我们在测试中发现,实时跟踪一个真实人体与跟踪显示器上视频中的人体跟踪精度似乎没有区别,使用iPad Pro 与iPhone 跟踪精度也似乎没有区别。

      在人体尺寸估计方面,使用纯图像处理时,虚拟模型有时会出现跳跃或者突然改变大小的现象。在配备了 LiDAR 传感器的设备上,由于可以直接从 LiDAR 传感器中采集到人体深度信息,因此在人体尺寸估计方面有很大提升,相比使用纯图像方式,估计的尺寸精度更高,对虚拟模型的大小控制更合理。

      从本节与2D检测实例可以看到,在运行 ARSession 进行人体检测跟踪时,将 ARBody TrackingConfiguration.frameSemantics 设置为 bodyDetection(即默认值),既可以检测2D人体骨骼关节点,也可以检测3D人体骨骼关节点,区别是检测的2D 人体骨骼关节点是在屏幕空间中,而检测的3D人体骨骼关节点是在世界空间中,因此,我们一般会在 session(:didUpdate frame:)代理方法中处理2D人体检测,在 session(:didUpdate 提示也可以在 session(:didUpdate anchors:)代理方法中处理2D人体检测,在使用 session(:didUpdateanchors:)方法处理2D人体检测时,由于获取的ARBodyAnchor 是在世界空间中,因此需要按照 3D人体检测的步骤进行处理。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/370721.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode:292.Nim 游戏

大一开学到现在&#xff0c;我不禁思考一个问题&#xff1a;代码重要吗&#xff1f; 我的答案是&#xff0c;根本不重要&#xff0c;或者说&#xff0c;是次要的。我认为分析问题&#xff0c;和画图是写题的开始&#xff0c;方法的学习&#xff0c;和灵活运用是目的。代码从来…

canvas设置图形各种混合模式,类似photoshop效果

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

(6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理

目录 一、为什么要使用Adaboost建模? 二、泰坦尼克号分析(工作环境) (插曲)Python可以引入任何图形及图形可视化工具 三、数据分析 四、模型建立 1、RandomForestRegressor预测年龄 2、LogisticRegression建模 引入GridSearchCV 引入RandomizedSearchCV 3、Deci…

《区块链简易速速上手小册》第2章:区块链的工作原理(2024 最新版)

文章目录 2.1 分布式账本技术&#xff08;DLT&#xff09;2.1.1 DLT基础知识2.1.2 主要案例&#xff1a;供应链管理2.1.3 拓展案例 1&#xff1a;数字身份2.1.4 拓展案例 2&#xff1a;投票系统 2.2 加密和安全性2.2.1 加密技术基础2.2.2 主要案例&#xff1a;比特币交易2.2.3 …

【DC渗透系列】DC-2靶场

arp先扫 ┌──(root㉿kali)-[~] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:6b:ed:27, IPv4: 192.168.100.251 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.100.1 00:50:56:c0:00:08 VMware, In…

Macbook 安装金铲铲之战等 IOS 游戏

前言 Macbook 现在可以玩一下 IOS 系统上的游戏啦&#xff0c;以笔者的 M1 Pro 芯片为例 步骤 一、安装 PlayCover 推荐 Sonama 安装 Nightly 版本 官网地址&#xff1a; https://playcover.io/ Nightly: https://nightly.link/playcover/playcover/workflows/2.nightly_re…

SQL 函数(十二)

SQL 函数&#xff08;十二&#xff09; 一、函数分类 1.1 单行函数 单行函数仅对单个行进行运算&#xff0c;并且每行返回一个结果。 常见的函数类型&#xff1a; 字符、数字、日期、转换 1.2 多行函数 多行函数能够操纵成组的行&#xff0c;每个行组给出一个结果&#x…

通过 editplus 批量转换文本编码

有时候需要对文本的编码进行批量转换&#xff0c;文本编辑器 notepad 中的“编码”菜单可以用来转换单个的文档编码&#xff0c;当文档数量多的时候&#xff0c;一个个操作比较繁琐&#xff0c;通过文本编辑器 editplus 软件&#xff0c;可以方便快速地批量修改文本文件的编码&…

帕鲁存档跨云迁服教程

近期一款名为幻兽帕鲁的游戏爆火&#xff0c;以迅雷不及掩耳之势拳打csgo&#xff0c;脚踢dota2&#xff0c;登顶steam同时在线第一名。 由于其独特的个人服务器机制&#xff0c;各大云厂商纷纷响应&#xff0c;腾讯云原价330的4核16G的轻量应用服务器新用户现在最低只要66元一…

GLIP:零样本学习 + 目标检测 + 视觉语言大模型

GLIP 核心思想GLIP 对比 BLIP、BLIP-2、CLIP 主要问题: 如何构建一个能够在不同任务和领域中以零样本或少样本方式无缝迁移的预训练模型&#xff1f;统一的短语定位损失语言意识的深度融合预训练数据类型的结合语义丰富数据的扩展零样本和少样本迁移学习 效果 论文&#xff1a;…

SSL证书的验证过程

HTTPS是工作于SSL层之上的HTTP协议&#xff0c;SSL&#xff08;安全套接层&#xff09;工作于TCP层之上&#xff0c;向应用层提供了两个基本安全服务&#xff1a;认证和保密。SSL有三个子协议&#xff1a;握手协议&#xff0c;记录协议和警报协议。其中握手协议实现服务器与客户…

问题:根据全面推进国防和军队现代化的战略安排,_____把人民军队全面建成世界一流军队。 #经验分享#媒体

问题&#xff1a;根据全面推进国防和军队现代化的战略安排&#xff0c;_____把人民军队全面建成世界一流军队。 A、2020年 B、2035年 C、本世纪中叶 D、2045年 参考答案如图所示 问题&#xff1a;判断题&#xff1a;高处作业传递物件应使用绳索&#xff0c;在确认作业下方…

Qt QGraphicsScene 基于视频的绘图

需求&#xff1a; 基于视频进行 图形的绘制。 方案&#xff1a; 上一篇文章分享了如何将视频实时渲染到QGraphicsScene 系统里&#xff0c;并简单讲述了如何进行绘图&#xff0c;但在实际使用时还是发现了一些技巧&#xff0c;现在总结一下。 Qt 基于海康相机 的视频标绘-CSD…

人类的本性,逃不开党同伐异

近几年以来&#xff0c;不知道大家有没有感受到&#xff0c;网络上越来越充满戾气。 无论哪个网站&#xff0c;只要打开评论区&#xff0c;充斥在眼前的总是一片乌烟瘴气。 一言不合就「对线」&#xff0c;动不动一顶帽子扣过去&#xff0c;说话前先「站队」「找友军」&#xf…

博途PLC限幅器(SCL代码)

PLC限幅器详细介绍,可以参考下面文章: https://rxxw-control.blog.csdn.net/article/details/128701050https://rxxw-control.blog.csdn.net/article/details/128701050三菱PLC限幅器 https://rxxw-control.blog.csdn.net/article/details/135212965

C++入门的基础

幸福比傲慢更容易蒙住人的眼睛。 ——大仲马 C入门 1、属于C的关键字1、1、C从何而来1、2、C关键字(C98) 2、命名空间2、1、命名空间的定义2、2、命名空间使用 3、C输入和输出4、缺省参数4、1、缺省参数概念4、2、缺省参数分类 5、函数重载5、1、函数重载概念 6、引用6、1、引用…

PHP框架详解 - symfony框架

首先说一下为什么要写symfony框架&#xff0c;这个框架也属于PHP的一个框架&#xff0c;小编接触也是3年前&#xff0c;原因是小编接触Golang&#xff0c;发现symfony框架有PHP框架的东西也有Golang的东西&#xff0c;所以决定总结一下&#xff0c;有需要的同学可以参看小编的G…

yolov1到v8的变化

目录 1.YOLO介绍&#xff1a;1.变化&#xff1a;小结&#xff1a; 1.YOLO介绍&#xff1a; YOLO&#xff08;You Only Look Once&#xff09;是一种流行的目标检测算法&#xff0c;它的版本从YOLOv1到YOLOv8经历了多次改进。以下是YOLOv1到YOLOv8的一些不同之处和改变&#xf…

快来使用Portainer让测试环境搭建飞起来吧

Portainer是Docker的图形化管理工具&#xff0c;提供状态显示面板、应用模板快速部署、容器镜像网络数据卷的基本操作&#xff08;包括上传下载镜像&#xff0c;创建容器等操作&#xff09;、事件日志显示、容器控制台操作、Swarm集群和服务等集中管理和操作、登录用户管理和控…

PM圆桌派:同事不愿意告诉你的职场套路有哪些?

职场是社会的缩影&#xff0c;想要崭露头角&#xff0c;获得更多升职加薪的机会&#xff0c;就不要做着和多数人一样的事情&#xff0c;却期待着不一样的结果。 职场上有很多潜在的规则&#xff0c;要会做事&#xff0c;也要会说话&#xff0c;更要会做人。如果不懂规则&#…