如何借助ARFaceAnchor与RealityKit检测人脸是否完全可见且居中?
解决方案:ARFaceAnchor人脸边界与屏幕坐标系转换
一、获取人脸在相机空间的边界范围
ARFaceAnchor没有直接提供minX/maxX这类属性,可通过两种方式计算面部边界:
方式1:基于面部几何顶点计算精确包围盒
利用ARFaceAnchor的geometry属性(对应ARFaceGeometry)遍历所有面部顶点,计算相机空间下的边界极值:
import ARKit func getFaceBoundingBox(from anchor: ARFaceAnchor) -> CGRect { let faceGeometry = anchor.geometry var minX: Float = .greatestFiniteMagnitude var maxX: Float = -.greatestFiniteMagnitude var minY: Float = .greatestFiniteMagnitude var maxY: Float = -.greatestFiniteMagnitude // 将顶点转换到相机空间并计算极值 for vertex in faceGeometry.vertices { let vertexInCameraSpace = anchor.transform * SIMD4<Float>(vertex.x, vertex.y, vertex.z, 1) minX = min(minX, vertexInCameraSpace.x) maxX = max(maxX, vertexInCameraSpace.x) minY = min(minY, vertexInCameraSpace.y) maxY = max(maxY, vertexInCameraSpace.y) } return CGRect(x: CGFloat(minX), y: CGFloat(minY), width: CGFloat(maxX - minX), height: CGFloat(maxY - minY)) }
方式2:基于面部特征点快速估算边界
如果不需要极高精度,可利用ARFaceAnchor提供的特征变换矩阵(如眼、鼻、下巴)快速估算面部范围:
func getFaceApproximateBounds(from anchor: ARFaceAnchor) -> CGRect { // 选取关键面部特征点的位置 let keyPoints = [ anchor.leftEyeTransform.columns.3, anchor.rightEyeTransform.columns.3, anchor.noseTransform.columns.3, anchor.chinTransform.columns.3 ] var minX: Float = .greatestFiniteMagnitude var maxX: Float = -.greatestFiniteMagnitude var minY: Float = .greatestFiniteMagnitude var maxY: Float = -.greatestFiniteMagnitude for point in keyPoints { minX = min(minX, point.x) maxX = max(maxX, point.x) minY = min(minY, point.y) maxY = max(maxY, point.y) } // 扩展边界以覆盖完整面部 let expandRatio: Float = 1.2 let width = (maxX - minX) * expandRatio let height = (maxY - minY) * expandRatio let centerX = (minX + maxX) / 2 let centerY = (minY + maxY) / 2 return CGRect(x: CGFloat(centerX - width/2), y: CGFloat(centerY - height/2), width: CGFloat(width), height: CGFloat(height)) }
二、将相机空间坐标转换为屏幕坐标系
通过ARSession的相机对象,调用projectPoint(_:)方法完成坐标转换,同时适配UI坐标系的Y轴方向:
func convertCameraSpaceBoundsToScreen(_ cameraSpaceBounds: CGRect, session: ARSession, screenSize: CGSize) -> CGRect { guard let camera = session.currentFrame?.camera else { return .zero } // 转换包围盒的左上、右下顶点到屏幕空间 let topLeft = camera.projectPoint(SIMD3<Float>(Float(cameraSpaceBounds.minX), Float(cameraSpaceBounds.maxY), 0)) let bottomRight = camera.projectPoint(SIMD3<Float>(Float(cameraSpaceBounds.maxX), Float(cameraSpaceBounds.minY), 0)) // 适配UIKit的Y轴原点(若用SwiftUI可省略Y轴翻转) let screenMinX = CGFloat(topLeft.x) let screenMaxX = CGFloat(bottomRight.x) let screenMinY = screenSize.height - CGFloat(bottomRight.y) let screenMaxY = screenSize.height - CGFloat(topLeft.y) return CGRect(x: screenMinX, y: screenMinY, width: screenMaxX - screenMinX, height: screenMaxY - screenMinY) }
三、判断人脸是否贴近中央椭圆边界
假设你已获取屏幕中央椭圆的CGRect(如centralEllipseFrame),可通过阈值判断人脸与椭圆边界的距离:
func isFaceNearEllipseBounds(faceScreenBounds: CGRect, centralEllipseFrame: CGRect) -> Bool { // 设置贴近阈值(单位:pt) let threshold: CGFloat = 10 // 检查人脸边界是否在椭圆阈值范围内 let isNearLeft = faceScreenBounds.maxX >= centralEllipseFrame.minX - threshold let isNearRight = faceScreenBounds.minX <= centralEllipseFrame.maxX + threshold let isNearTop = faceScreenBounds.minY >= centralEllipseFrame.minY - threshold let isNearBottom = faceScreenBounds.maxY <= centralEllipseFrame.maxY + threshold return isNearLeft && isNearRight && isNearTop && isNearBottom }
注意事项
- 建议在
ARSessionDelegate的session(_:didUpdate:)方法中实时更新人脸边界并执行判断逻辑。 - 若使用SwiftUI,屏幕坐标系Y轴与ARKit一致(原点左上角),无需额外翻转;UIKit则需保留代码中的Y轴转换。
- 面部深度(Z轴坐标)会影响投影精度,若需要更精准判断,可统一使用面部中心的Z值进行投影计算。
内容的提问来源于stack exchange,提问作者yamski
相关产品推荐
相关产品推荐

