You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ARKit人脸追踪:如何精准获取物理屏幕中心3D世界坐标?

ARKit人脸追踪场景下屏幕中心3D坐标获取及2D/3D互转方案

一、精准获取屏幕中心3D坐标的最优方案

在ARFaceTrackingConfiguration模式下,要获取物理屏幕中心的世界空间坐标,核心是基于相机的物理参数(内参/外参)定位屏幕所在平面,而非直接使用相机节点的变换。具体步骤:

  1. 从当前ARFrame中提取相机的投影矩阵,从中获取前置摄像头到屏幕的垂直距离(即相机近平面距离),这个值是设备物理参数,无需手动估算:
    guard let frame = sceneView.session.currentFrame else { return }
    let projMatrix = frame.camera.projectionMatrix
    let nearDistance = -projMatrix.columns.3.z / (projMatrix.columns.2.z + 1)
    
  2. 构建屏幕所在平面的世界空间变换矩阵:屏幕平面位于相机前向方向(-Z轴),距离相机nearDistance处:
    let screenPlaneInCamera = simd_float4x4(translation: SIMD3<Float>(0, 0, -nearDistance))
    let screenPlaneInWorld = frame.camera.transform * screenPlaneInCamera
    
  3. 将屏幕中心点反投影到该平面,得到世界空间坐标:
    let screenSize = sceneView.bounds.size
    let screenMidpoint = CGPoint(x: screenSize.width / 2, y: screenSize.height / 2)
    let worldMidpoint = sceneView.unprojectPoint(screenMidpoint, ontoPlane: screenPlaneInWorld, orientation: .portrait, viewportSize: screenSize)
    

二、手动偏移需求的成因

你之前需要手动偏移0.025米,是因为误用了sceneView.pointOfView!.transform作为平面变换——这个矩阵对应的是前置摄像头在世界空间中的位置,而非屏幕中心的位置。前置摄像头与屏幕中心存在物理间隙(比如iPhone前置摄像头在屏幕顶部,与中心垂直距离约2.5cm),手动偏移刚好补偿了这个物理差,让平面落在屏幕所在位置,因此得到了正确结果。

三、2D与3D坐标的可靠互转

1. 3D世界坐标转2D屏幕坐标

直接使用SceneKit的projectPoint方法,自动处理相机投影与空间变换:

func worldToScreen(worldPoint: SCNVector3) -> CGPoint {
    guard let pov = sceneView.pointOfView else { return .zero }
    let scnScreenPoint = pov.projectPoint(worldPoint)
    // 转换为UIKit坐标系(Y轴翻转)
    return CGPoint(x: scnScreenPoint.x, y: sceneView.bounds.height - scnScreenPoint.y)
}

2. 2D屏幕坐标转3D世界坐标

要实现1:1精准转换,必须将目标平面固定为物理屏幕所在平面,复用前面的近平面距离计算逻辑:

func screenToWorld(screenPoint: CGPoint) -> SCNVector3? {
    guard let frame = sceneView.session.currentFrame else { return nil }
    let camera = frame.camera
    let viewSize = sceneView.bounds.size
    
    // 提取近平面距离
    let projMatrix = camera.projectionMatrix
    let nearDistance = -projMatrix.columns.3.z / (projMatrix.columns.2.z + 1)
    
    // 构建屏幕平面的世界变换
    let screenPlaneInCamera = simd_float4x4(translation: SIMD3<Float>(0, 0, -nearDistance))
    let screenPlaneInWorld = camera.transform * screenPlaneInCamera
    
    // 反投影到屏幕平面
    return sceneView.unprojectPoint(screenPoint, ontoPlane: screenPlaneInWorld, orientation: .portrait, viewportSize: viewSize)
}

四、高精度手动计算方案(进阶)

如果需要规避SceneKit封装带来的微小误差,可以直接用相机内参/外参手动计算:

func screenToWorldPrecise(screenPoint: CGPoint) -> SIMD3<Float>? {
    guard let frame = sceneView.session.currentFrame else { return nil }
    let camera = frame.camera
    let viewSize = sceneView.bounds.size
    
    // 将屏幕坐标归一化并转换为相机坐标系下的点
    let normX = (screenPoint.x / viewSize.width) * 2 - 1
    let normY = 1 - (screenPoint.y / viewSize.height) * 2
    let intrinsics = camera.intrinsics
    let fx = intrinsics[0][0], fy = intrinsics[1][1]
    let cx = intrinsics[2][0], cy = intrinsics[2][1]
    let z = -camera.projectionMatrix.columns.3.z
    let cameraSpacePoint = SIMD3<Float>(
        (normX * viewSize.width / 2 - cx) / fx * z,
        (normY * viewSize.height / 2 - cy) / fy * z,
        z
    )
    
    // 转换为世界空间坐标
    return simd_make_float3(camera.transform * simd_make_float4(cameraSpacePoint, 1))
}

内容的提问来源于stack exchange,提问作者Trey Tuscai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 01:23:13