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

如何追踪AR平面节点四个顶点的屏幕2D坐标?

如何获取AR人脸追踪平面节点的四个顶点屏幕坐标?

现有代码实现

1. 创建人脸追踪平面节点

func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
    guard anchor is ARFaceAnchor else { return nil }
    
    let plane = SCNPlane(width: 0.1, height: 0.2)
    let planeNode = SCNNode(geometry: plane)
    planeNode.geometry?.firstMaterial?.fillMode = .lines
    planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
    planeNode.name = "Plane Node"
    return planeNode
}

2. 追踪平面中心屏幕坐标

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let faceAnchor = anchor as? ARFaceAnchor else {
        return
    }
    
    node.enumerateChildNodes { childNode, _ in
        guard childNode.name == "Plane Node" else { return }
        let worldPosition = childNode.worldPosition
        let screenPosition = renderer.projectPoint(worldPosition)
        print(CGPoint(x: Int(screenPosition.x), y: Int(screenPosition.y)))
    }
}

3. 尝试推导顶点坐标的扩展(未成功)

extension SCNNode {
    var width: Float {
        return (boundingBox.max.x - boundingBox.min.x) * scale.x
    }
    
    var height: Float {
        return (boundingBox.max.y - boundingBox.min.y) * scale.y
    }
}

问题

目前能成功获取平面中心的屏幕坐标,但无法正确计算平面四个顶点的2D屏幕坐标。尝试通过扩展计算节点宽高,再从中心坐标推导顶点坐标的方法,得到的宽高值无法适配屏幕坐标系,结果不准确。

解决方案

正确思路是先计算平面四个顶点在3D世界空间中的位置,再通过AR场景渲染器的投影方法转换为屏幕坐标,具体实现如下:

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let faceAnchor = anchor as? ARFaceAnchor else { return }
    
    node.enumerateChildNodes { childNode, stop in
        guard let planeNode = childNode as? SCNNode, 
              let plane = planeNode.geometry as? SCNPlane,
              childNode.name == "Plane Node" else { return }
        
        // 获取平面原始半尺寸
        let planeHalfWidth = plane.width / 2
        let planeHalfHeight = plane.height / 2
        
        // 定义平面四个顶点在本地坐标系中的位置(以平面中心为原点)
        let localCorners: [SCNVector3] = [
            SCNVector3(-planeHalfWidth, -planeHalfHeight, 0), // 左下顶点
            SCNVector3(planeHalfWidth, -planeHalfHeight, 0),  // 右下顶点
            SCNVector3(planeHalfWidth, planeHalfHeight, 0),   // 右上顶点
            SCNVector3(-planeHalfWidth, planeHalfHeight, 0)   // 左上顶点
        ]
        
        // 转换为世界坐标并投影到屏幕
        localCorners.enumerated().forEach { index, localCorner in
            let worldCorner = planeNode.convertPosition(localCorner, to: nil)
            let screenVector = renderer.projectPoint(worldCorner)
            let screenPoint = CGPoint(x: Int(screenVector.x), y: Int(screenVector.y))
            print("顶点\(index+1)屏幕坐标:\(screenPoint)")
        }
    }
}

原理说明

之前方法失效的原因:

  • boundingBox计算的是节点的轴对齐包围盒,当平面节点伴随人脸旋转时,包围盒宽高无法反映平面在世界空间的实际尺寸
  • 直接从中心推导顶点坐标未考虑节点旋转、缩放以及相机透视投影关系,导致屏幕坐标偏差

通过本地坐标转世界坐标再投影的方式,会自动处理节点的旋转、缩放变换以及相机透视投影,确保屏幕坐标准确对应平面的四个顶点位置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 03:21:36