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 的高级使用案例