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

如何利用ARKit获取捕获图像中物体的物理尺寸(宽、高)?

解决ARKit中真实物体物理高度计算问题

你的思路方向是对的,但现有代码存在几个关键问题:直接用屏幕边缘的CGPoint并不对应物体的上下边缘,且仅依赖featurePoint的hitTest稳定性差(特征点可能不在物体表面)。下面提供几个可行的优化方案:

方案1:手动标记物体上下边缘(最直接可靠)

让用户通过触摸操作标记物体的顶部和底部,获取对应屏幕坐标后再做3D位置检测:

// 存储用户标记的点
private var markedPoints: [CGPoint] = []

// 触摸事件处理
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first, markedPoints.count < 2 else {
        markedPoints.removeAll()
        return
    }
    let touchPoint = touch.location(in: self)
    markedPoints.append(touchPoint)
    
    if markedPoints.count == 2 {
        calculateHeightFromMarkedPoints()
    }
}

private func calculateHeightFromMarkedPoints() {
    guard let topPos = getPosition(point: markedPoints[0]), 
          let bottomPos = getPosition(point: markedPoints[1]) else {
        print("无法获取3D位置")
        return
    }
    let distanceM = topPos.distance(to: bottomPos)
    let distanceMm = round(distanceM * 1000)
    print("物体高度:\(distanceMm)mm")
    markedPoints.removeAll()
}

// 替换hitTest为更可靠的raycast(ARKit 3.0+)
private func getPosition(point: CGPoint) -> SCNVector3? {
    guard let session = self.session else { return nil }
    let raycastQuery = session.raycastQuery(from: point, 
                                            allowing: .estimatedPlane, 
                                            alignment: .any)
    guard let result = session.raycast(raycastQuery).first else {
        // 如果平面检测失败, fallback到featurePoint
        let hitTestResults = self.hitTest(point, types: .featurePoint)
        return hitTestResults.first.map { 
            SCNVector3.positionFrom(matrix: $0.worldTransform) 
        }
    }
    return SCNVector3.positionFrom(matrix: result.worldTransform)
}

// 保留你的扩展方法
extension SCNVector3 {
    func distance(to destination: SCNVector3) -> CGFloat {
        let dx = destination.x - x
        let dy = destination.y - y
        let dz = destination.z - z
        return CGFloat(sqrt(dx*dx + dy*dy + dz*dz))
    }
    
    static func positionFrom(matrix: matrix_float4x4) -> SCNVector3 {
        let column = matrix.columns.3
        return SCNVector3(column.x, column.y, column.z)
    }
}

方案2:利用相机内参+已知距离计算(无需标记点)

如果你已经知道相机到物体的距离,可以通过相似三角形原理计算,无需依赖hitTest:

func calculateHeightFromCameraDistance(objectPixelHeight: CGFloat, cameraDistanceM: CGFloat) -> CGFloat {
    guard let camera = session.currentFrame?.camera else { return 0 }
    let intrinsics = camera.intrinsics
    // 获取相机焦距(单位:像素)
    let focalLengthY = CGFloat(intrinsics[1][1])
    
    // 真实高度 = (像素高度 × 距离) / 焦距
    let realHeightM = (objectPixelHeight * cameraDistanceM) / focalLengthY
    return realHeightM * 1000 // 转换为毫米
}

// 使用示例:假设你通过物体检测得到了它在图像中的像素高度
// let objectPixelHeight = 200 // 比如物体占据200像素高度
// let heightMm = calculateHeightFromCameraDistance(objectPixelHeight: objectPixelHeight, cameraDistanceM: 1.5)

注意:需要先通过物体检测(比如Core ML、Vision框架)获取物体在图像中的像素高度objectPixelHeight

方案3:结合平面检测优化hitTest

如果物体放置在平面上(比如桌面),可以先检测平面,再将底部点约束到平面上,顶部点用hitTest物体表面:

private func calculateHeightWithPlane() {
    // 假设已经检测到平面,获取平面锚点
    guard let planeAnchor = session.currentFrame?.anchors.first(where: { $0 is ARPlaneAnchor }) as? ARPlaneAnchor else {
        print("未检测到平面")
        return
    }
    // 物体底部在平面上的屏幕点(需要手动标记或检测)
    let bottomScreenPoint = CGPoint(x: self.bounds.midX, y: self.bounds.maxY - 100)
    // 顶部点
    let topScreenPoint = CGPoint(x: self.bounds.midX, y: self.bounds.minY + 100)
    
    // 底部3D位置:投影到平面上
    let bottomPos = SCNVector3(planeAnchor.center.x, planeAnchor.center.y, planeAnchor.center.z)
    // 顶部3D位置:hitTest获取
    guard let topPos = getPosition(point: topScreenPoint) else { return }
    
    let heightMm = round(topPos.distance(to: bottomPos) * 1000)
    print("物体高度:\(heightMm)mm")
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 21:09:52