36-AR 中的人物

ARKit系列文章目录
2019年WWDC的《 Session 607 - Bringing People into AR 》 主要内容速览:

  • 人体遮挡
  • 工作原理
  • 动作捕捉
  • 如何使用

AR 中关于人物的主要有两个功能:人体遮挡和动作捕捉

人体遮挡

在以前版本的 ARKit 中,没有人体遮挡功能,那么 AR 内容在显示时,就会出错,如下图 人体遮挡的作用,就是让人体能正确遮挡住 AR 物体,产生更加真实的效果

工作原理

下面我们以其中的一帧为例,来讲解其中的原理。

下图中,我们可以看到,不同的物体会显示在不同的深度平面上,不论是虚拟物体还是真实物体。 这是怎么做到的呢?虚拟物体的话,可以通过深度缓冲(Depth buffer)直接获得深度信息。而对于真实物体,要想理解人体在场景中的位置,就需要引入另外两个缓冲:分割缓冲(segmentation buffer) 和深度缓冲(depth buffer)。 分割缓冲用来告诉我们,场景中的哪些像素是一个人;而深度缓冲则用来告诉我们,这个人在场景上的深度是多少。 要明白的是,这两个缓冲是在 A12 芯片上,通过机器学习的方式,仅仅通过摄像头捕捉到的画面,而自动生成的。ARFrame 中引入的两个缓冲:

具体的工作原理如下: 为了让神经网络能在 60FPS 下顺利工作,它只能看到低分辨率的图片。 就是说,如果你将神经网络的输出结果放大,你会看到神经网络的输出丢失了很多细节。为了补偿这些细节,我们需要做一些额外的处理,这里我们用 matting(扣图)。 matting 就是用 segmentationBuffer 作为参考,然后查看相机画面,来计算出缺失的细节到底是什么。 拿到扣图完成的画面后,我们就可以同时根据 estimatedDepthData 一起,从场景中抽出人物并根据深度信息正确排列好层次关系。最终混合(Composition)成场景画面,显示出来。

可以看到,整个过程用到了很多技术,但我们努力让开发者易于上手使用它。你可以在 RealityKit,SceneKit 和 Metal 中使用人体遮挡功能。

RealityKit 中使用

下面是 RealityKit 中使用人体遮挡的简单示例代码,RealityKit 也是苹果推荐的最佳使用方式:

swift 复制代码
override func viewDidLoad() {
super.viewDidLoad()
// Check If Supported
guard let config = arView.session.config as? ARWorldTrackingConfiguration,
ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentationWithDepth) else {
return
}
 // Enable Frame Semantics
 config.frameSemantics = .personSegmentationWithDepth
 }
swift 复制代码
enum ARFrameSemantics {
case PersonSegmentation // Only People, No Depth
case PersonSegmentationWithDepth
}
class ARWorldTrackingConfiguration: ARConfiguration {
 var frameSemantics: ARFrameSemantics { get set }
}

比如,下面这个 swift 的小游戏,就用到了人体遮挡效果:

SceneKit 中使用

如果你的项目中已经使用了 SceneKit,那也可以使用人体遮挡效果。

  • ARSCNView中同样添加支持
  • 只需要启用 frame semantics
  • 需要注意,composition 是使用 post-process 的
  • 所以可能在半透明物体上效果不好

Metal 中自定义混合

最后,如果我使用了自定义的渲染引擎或第三方引擎,那可以使用 Metal 技术来完成同样效果。

Metal 提供了一个新的类,以便用神经网络输出的低分辨率 segmentationBuffer 图片中生成 matte(扣图),关键代码如下:

swift 复制代码
func compositeFrame(_ frame : ARFrame!, commandBuffer : MTLCommandBuffer!) {
// Composition Part of the Rendering Code
guard ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentationWithDepth) else {
return
}
// Schedule Matting
let matte = matte.generateMatte(from: frame, commandBuffer: commandBuffer)
// Custom composition code
 // Done
 commandBuffer.commit()
 }

其中 ARMatteGenerator 类如下:

swift 复制代码
class ARMatteGenerator: NSObject {
 func generateMatte(from: ARFrame,commandBuffer: MTLCommandBuffer) -> MTLTexture
}

但是,到这里还没做完。和前面讲的一样, estimatedDepthData 也是一个低分辨率的图像,如果我们只是简单地放大,然后与扣图后的画面相比,边缘就会出现很多不匹配的现象。 因为扣图后的画面包含了我们需要的细节,所以我们不能简单修改 matte 图像的 alpha 值,而应该修改深度缓冲(depth buffer)。

现在让我们回去刚才的代码中,我们需要添加一行新的代码,调用generateDilatedDepth方法来产生一个新的纹理图片。

swift 复制代码
func compositeFrame(_ frame : ARFrame!, commandBuffer : MTLCommandBuffer!) {
// Composition part of the rendering code
guard ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentationWithDepth) else
{
return
}
 
// Schedule matting
let matte = matte.generateMatte(from: frame, commandBuffer: commandBuffer)
let dilatedDepth = matte.generateDilatedDepth(from: frame, commandBuffer: commandBuffer) // <-----注意添加的这行
// Custom composition shader
 // Done
 commandBuffer.commit()
 

拿到所有需要的纹理后,就可以混合(composition)了,这个过程一般是在 GPU 上完成了,所以我们需要写一个 shader:

c++ 复制代码
fragment half4 customComposition(...)
{
half4 camera = cameraTexture.sample(s, in.uv);
half4 rendered = renderedTexture.sample(s, in.uv);
float renderedDepth = renderedDepthTexture.sample(s, in.uv);
half4 scene = mix(rendered, camera, rendered.a);

half matte = matteTexture.sample(s, in.uv);
float dilatedDepth = dilatedDepthTexture.sample(s, in.uv);

if (dilatedDepth < renderedDepth) { // People in front of rendered
return mix(scene, camera, matte);
} else {
return scene;
}

}

最终就完成了自定义的人体遮挡效果的渲染。

人体遮挡总结

  • 可以遮挡人体和被渲染的内容
  • 支持 RealityKit 中的 ARView
  • 向后兼容 ARSCNView
  • 可通过 ARMatteGenerator 来使用自定义分割

动作捕捉

示意图

基本原理就是捕捉人体的 3D 结点,然后驱动 AR 模型中的骨骼(skeleton),模型外表(mesh)就会跟着动起来。

如何使用

RealityKit 中使用

RealityKit 中内置了非常好用的 API,让大家在使用动作捕捉时,只需几行代码。 首先需要说明一下BodyTrackedEntity

  • 它代表了一个人体
  • 包含骨骼和位置
  • 每帧都会更新
  • 将骨骼系统应用到.usdz 模型上
swift 复制代码
// Load Rigged Mesh and Tracked Person
Entity.loadBodyTrackedAsync(named: "robot")
.sink(receiveCompletion : { // For catching failure/error },
receiveValue: { (character) in
guard let character = character as? BodyTrackedEntity
else { return }
// Get the Location Where You Want to Put Your Character
let personAnchor = AnchorEntity(.body)
arView.scene.addAnchor(personAnchor)
 // Add the Character to that Location
 personAnchor.addChild(character)

你可能想知道,如何自定义一个模型呢?其实只需要参照上面模型中的数据结构,建立模型,RealityKit 就会自动驱动模型。

swift 复制代码
Entity.loadBodyTrackedAsync(named: "robot")

包含的关节点如下,共有 91 个,只要你的模型中包含这些场景名称,RealityKit 就会自动应用:

3D

下面我们来讲讲底层的相关知识。所有的基础就是ARBodyAnchor ,它包含了自身的 Transform 和内部的骨骼系统Skeleton 。整个骨骼系统中髋关节Hip joint 就是根节点Root 如下图箭头所示,每一个节点包含内部的父子关系及关节名称Joint Name 。其中绿色节点是被追踪出来的,而黄色节点,如手指,则是固定在离它最近的绿色节点上,跟随运动的。 如果要获取右手处的位置,有两种方法: 相对于父节点的 localTransform 和相对于根节点的 modleTransform

使用时的代码如下:

swift 复制代码
// Look for the bodyAnchor
for anchor in anchors {
 guard let bodyAnchor = anchor as? ARBodyAnchor else { return }
 // Access to the Position of Root Node
 let hipWorldPosition = bodyAnchor.transform
 // Accessing the Skeleton Geometry
 let skeleton = bodyAnchor.skeleton
 // Accessing List of Transforms of all Joints Relative to Root
 let jointTransforms = skeleton.jointModelTransforms
 // Iterating over All Joints
 for (i, jointTransform) in jointTransforms.enumerated() {
  // Extract Parent Index from Definition
  let parentIndex = skeleton.definition.parentIndices[ i ]
  // Check If It's Not Root
  guard parentIndex != -1 else { continue }
 
  // Find Position of Parent Joint
  let parentJointTransform = jointTransforms[parentIndex.intValue]
 ...
 }
}

如果你需要 2D 的骨骼系统的话,我们也提供了相关的 API。

2D

类似的,2D 的结构基础是ARBody2D 。层级结构也类似于 3D 的

使用时代码如下:

swift 复制代码
func session(_ session ARSession, didUpdate frame: ARFrame){
 // Accessing ARBody2D Object from ARFrame
let person = frame.detectedBody
}

但是,如果你已经用了 3D 的骨骼系统,还想同时使用 2D 的,那就需要从 ARBodyAnchor 中获取 2D 信息

swift 复制代码
guard let bodyAnchor = anchor as? ARBodyAnchor else {continue}
 // Accessing ARBody2D Object from referenceBody Property
 let body2D = bodyAnchor.referenceBody

需要注意的是,2D 骨骼系统的坐标,是规范化坐标空间的,如下图,左上角为(0,0) 所有的点的坐标数值范围都在[0,1]之间。 在 2D 系统中,总共有 16 个关节点。

关节点的父子关系也类似于 3D 版本,如下图箭头所示: 完整的 2D 使用代码如下:

swift 复制代码
func session(_ session ARSession, didUpdate frame: ARFrame) {
    
 // Accessing ARBody2D Object from ARFrame
 let  person = frame.detectedBody
 // Use Skeleton Property to Access the Skeleton
 let skeleton2D = person.skeleton
 let definition = skeleton2D.definition
 let jointLandmarks = skeleton2D.jointLandmarks


 // Iterate over All the  Landmarks
 for (i, joint) in jointLandmarks. enumerated() {
  // Find Index of Parent
  let parentIndex = definition. parentIndices[i]
  // Check If It' s Not the Root
 guard parentIndex != 1 else { continue }

  // Find Position of Parent Index
  let parentJoint = jointLandmarks[parentIndex. intValue]
  ...
 }
}

AR 中的动作捕捉总结

  • 能访问追踪的人体
  • 提供 3D 和 2D 骨骼
  • 可使用角色动画
  • 可使用 RealityKit API 快速驱动一个角色,进行动画
  • ARKit API 的高级使用案例

参考资料

相关推荐
jimumeta24 天前
虚拟现实(VR)与增强现实(AR)有什么区别?
ar·vr·虚拟现实·增强现实
JovaZou2 个月前
Meta 发布 Quest 3S 头显及 AR 眼镜原型:开启未来交互新视界
ai·ar·交互·虚拟现实·增强现实
虹科数字化与AR3 个月前
安宝特分享 | AR技术重塑工业:数字孪生与沉浸式培训的创新应用
ar·数字孪生·ar眼镜·增强现实·工业ar
JovaZou3 个月前
Snap 发布新一代 AR 眼镜,有什么特别之处?
ai·ar·虚拟现实·华为snap·增强现实
学步_技术3 个月前
利用AI增强现实开发:基于CoreML的深度学习图像场景识别实战教程
人工智能·深度学习·ar·增强现实·coreml
斯裕科技3 个月前
新升级|优化航拍/倾斜模型好消息,支持处理多套贴图模型!
unity·ue5·3dsmax·虚拟现实·maya·增强现实
Successssss~3 个月前
【高校主办,EI稳定检索】2024年人机交互与虚拟现实国际会议(HCIVR 2024)
计算机视觉·人机交互·vr·虚拟现实·增强现实
学步_技术4 个月前
增强现实系列—深入探索ARKit:平面检测、三维模型放置与增强现实交互
机器学习·计算机视觉·交互·增强现实·虚拟现实技术·平面检测·vr/ar
Uncertainty!!5 个月前
初识增强现实(AR)
ar·增强现实
VRARvrnew3d5 个月前
轨道交通AR交互教学定制公司优选深圳华锐视点
ar·增强现实·轨道交通·ar公司·ar教学