ARKit人脸追踪:如何精准获取物理屏幕中心3D世界坐标?
ARKit人脸追踪场景下屏幕中心3D坐标获取及2D/3D互转方案
一、精准获取屏幕中心3D坐标的最优方案
在ARFaceTrackingConfiguration模式下,要获取物理屏幕中心的世界空间坐标,核心是基于相机的物理参数(内参/外参)定位屏幕所在平面,而非直接使用相机节点的变换。具体步骤:
- 从当前
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) - 构建屏幕所在平面的世界空间变换矩阵:屏幕平面位于相机前向方向(-Z轴),距离相机
nearDistance处:let screenPlaneInCamera = simd_float4x4(translation: SIMD3<Float>(0, 0, -nearDistance)) let screenPlaneInWorld = frame.camera.transform * screenPlaneInCamera - 将屏幕中心点反投影到该平面,得到世界空间坐标:
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
相关产品推荐
相关产品推荐

