0fb3fb98fd483a7a8c287e34c8b1cdf1
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 也是苹果推荐的最佳使用方式:

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
 }

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(扣图),关键代码如下:

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 类如下:

class ARMatteGenerator: NSObject {
 func generateMatte(from: ARFrame,commandBuffer: MTLCommandBuffer) -> MTLTexture
}

但是,到这里还没做完。和前面讲的一样, estimatedDepthData 也是一个低分辨率的图像,如果我们只是简单地放大,然后与扣图后的画面相比,边缘就会出现很多不匹配的现象。

因为扣图后的画面包含了我们需要的细节,所以我们不能简单修改 matte 图像的 alpha 值,而应该修改深度缓冲(depth buffer)。

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

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:

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
top Created with Sketch.