iOS11 ARKit:ARKit是否能够捕捉用户脸部的纹理?

10

我详细阅读了所有 ARKit 类的文档,但未发现任何地方描述如何获取用户面部的纹理。

ARFaceAnchor 包含 ARFaceGeometry(由顶点组成的拓扑和几何结构)和 BlendShapeLocation 数组(通过对用户面部顶点进行几何运算允许对个体面部特征进行操作的坐标)。

但我该从哪里获取用户面部的实际纹理呢?例如:真实肤色/颜色/质地、面部毛发以及其他独特特征,如疤痕或胎记?还是完全不可能呢?

3个回答

10
你想为脸部创建一张纹理贴图式的图片?没有专门实现这个功能的API,但是你需要的所有信息都在这里:
- `ARFrame.capturedImage` 可以获取相机图像。 - `ARFaceGeometry` 可以获取脸部的三维网格模型。 - `ARAnchor` 和 `ARCamera` 一起可以告诉你脸部相对于相机的位置,以及相机与图像像素之间的关系。
因此,完全可以使用当前视频帧图像为脸部模型添加纹理。对于网格中的每个顶点...
1. 将顶点位置从模型空间转换为相机空间(使用锚点的变换)。 2. 使用相机投影将该向量乘以来到归一化图像坐标。 3. 除以图像宽度/高度,以获取像素坐标。
这样就可以为每个顶点获取纹理坐标,然后可以使用相机图像为网格添加纹理。可以一次性进行所有数学运算来替换 `ARFaceGeometry` 提供的纹理坐标缓冲区,也可以在渲染过程中使用着色器代码在GPU上完成。如果你正在使用 SceneKit / `ARSCNView` 进行渲染,可以在 `geometry` 着色器修改器中完成此操作。
如果你想知道相机图像中的每个像素与脸部几何体的对应关系,那么稍微有些困难。不能仅通过反转上述数学运算来解决问题,因为缺少每个像素的深度值...但是,如果你不需要映射每个像素,则 SceneKit 的命中测试是获取单个像素几何体的简单方法。
如果你实际上要求的是地标识别-例如眼睛、鼻子、胡须等在相机图像中的位置,则 ARKit 中没有相应的API。可以尝试使用 Vision 框架来解决这个问题。

非常感谢您的出色回答。我正在遵循从ARFaceGeometry操纵纹理坐标缓冲区的方法,看起来很有前途。 - FranticRock
在我提出的两种方法中,你都不需要操作图像——它们都是通过操纵纹理坐标(使用顶点位置数据)来实现的,这样你就可以从图像中采样到与面部网格当前位置匹配的像素。 "一次性" 意味着处理整个顶点缓冲区(可能在 CPU 上);另一种选择是在 GPU 上 渲染时 进行处理,因为修改顶点属性(如位置/纹理坐标)正是顶点着色器的作用。 - rickster
谢谢@rickster。在我的探索中,我首先处理单个帧,这就是为什么我将数据导出为.obj文件以更轻松地查看结果。 - coco
你如何使用仅CPU(而不使用Metal GPU)从ARFrame.capturedImage(CVPixelBuffer)中的每个像素计算RGBA颜色信息? - Luis B
@rickster,我想知道相机图像中每个像素对应脸部几何的哪一部分,请您详细说明一下?我也在这里提出了这个问题(https://stackoverflow.com/q/59145124/1634890)。 - Juan Boero
显示剩余3条评论

7
我已经制作了一个演示iOS应用程序,展示如何完成这个过程。该演示实时捕获面部纹理贴图,并将其应用回ARSCNFaceGeometry,以创建用户面部的纹理三维模型。
下面您可以看到实时纹理三维面部模型位于左上角,覆盖在AR前置摄像头视图之上:

Textured 3D face model in the top left overlaid on top the AR front facing camera view

演示通过渲染一个ARSCNFaceGeometry来实现,但是与其通常的渲染方式不同,它在纹理空间中进行渲染,同时继续使用原始顶点位置来确定从捕获的像素数据中采样的位置。

以下链接是实现相关部分:

几乎所有的工作都在金属渲染通道中完成,因此它可以轻松实时运行。

我还整理了一些笔记,涵盖了演示的限制


如果您想获得用户脸部的2D图像,您可以尝试执行以下操作:
  • 将转换后的ARSCNFaceGeometry渲染到1位缓冲区中,以创建image mask。基本上,您只需要找到面部模型为白色的位置,而其他所有位置都应为黑色。

  • 将遮罩应用于捕获的帧图像。

这样可以给您一个仅包含脸部的图像(虽然您可能需要裁剪结果)。


你好 Matt, 这是一个很棒的示例。但如果我们想使用 ARKit 3.5 中引入的 ARMesh 而不是脸部,我们能实现吗?如果可以,你能否稍微解释一下如何做到呢? - Ali Aeman
1
核心逻辑应该保持不变,只需更新 FaceTextureGenerator.swift 以从跟踪用户面部的 ARMesh 中提取几何信息,而不是从 SceneKit 几何体中提取。 - Matt Bierner
@MattBierner 您能告诉我们您的项目与苹果增强现实面部演示项目之间的区别吗?此外,我们的想法是将纹理传递到面部的 UVW 映射中,以便可以进行双向映射。谢谢! - Juan Boero
苹果视频面部纹理演示使用片段着色器绘制面部几何图形,该着色器从捕获的图像中采样(更像是后处理效果)。我的示例每帧为面部几何图形生成新的纹理映射。然后,您可以将此纹理应用回一个单独的面部几何图形上,该图形可以独立于用户的实际面部进行变换。 - Matt Bierner
@MattBierner,我和Ali Aeman有同样的问题。你能提供一个示例来解决吗? - yaali
@MattBierner ARMeshAnchor没有任何uv.texcoord信息。 - masaldana2

3
您可以按照以下方式计算纹理坐标:
let geometry = faceAnchor.geometry
let vertices = geometry.vertices
let size = arFrame.camera.imageResolution
let camera = arFrame.camera

modelMatrix = faceAnchor.transform

let textureCoordinates = vertices.map { vertex -> vector_float2 in
    let vertex4 = vector_float4(vertex.x, vertex.y, vertex.z, 1)
    let world_vertex4 = simd_mul(modelMatrix!, vertex4)
    let world_vector3 = simd_float3(x: world_vertex4.x, y: world_vertex4.y, z: world_vertex4.z)
    let pt = camera.projectPoint(world_vector3,
        orientation: .portrait,
        viewportSize: CGSize(
            width: CGFloat(size.height),
            height: CGFloat(size.width)))
    let v = 1.0 - Float(pt.x) / Float(size.height)
    let u = Float(pt.y) / Float(size.width)
    return vector_float2(u, v)
}

1
那么,通过这个,我可以知道每个图像像素映射到纹理坐标的位置吗?比如说,我想创建一个采样面部的漫反射贴图,在每个像素上绘制漫反射贴图。 - Juan Boero
1
我已经将这个应用到了SCNGeometry纹理坐标源上,但它产生了奇怪的结果.. 有什么想法吗? - MJ Studio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接