首页 文章

如何将视觉框架坐标系转换为ARKit?

提问于
浏览
12

我正在使用ARKit(使用SceneKit)添加虚拟对象(例如球) . 我通过使用Vision框架并在视觉请求完成处理程序方法中接收其更新位置来跟踪真实世界对象(例如,脚) .

let request = VNTrackObjectRequest(detectedObjectObservation: lastObservation, completionHandler: self.handleVisionRequestUpdate)

我想用虚拟替换跟踪的现实世界对象(例如用立方体替换脚)但是我不知道如何将边界框矩形(我们在视觉请求完成中接收)替换为场景套件节点,因为坐标系统是不同的 .

以下是视觉请求完成处理程序的代码:

private func handleVisionRequestUpdate(_ request: VNRequest, error: Error?) {
    // Dispatch to the main queue because we are touching non-atomic, non-thread safe properties of the view controller
    DispatchQueue.main.async {
      // make sure we have an actual result
      guard let newObservation = request.results?.first as? VNDetectedObjectObservation else { return }

      // prepare for next loop
      self.lastObservation = newObservation

      // check the confidence level before updating the UI
      guard newObservation.confidence >= 0.3 else {
        return
      }

      // calculate view rect
      var transformedRect = newObservation.boundingBox

     //How to convert transformedRect into AR Coordinate
  self.node.position = SCNVector3Make(?.worldTransform.columns.3.x,
    ?.worldTransform.columns.3.y,

    }
  }

请指导我转移坐标系 .

2 回答

  • 4

    假设矩形位于水平平面上,您可以对所有4个角上的场景执行命中测试,并使用其中3个角来计算矩形的宽度,高度,中心和方向 .

    我在GitHub上有一个演示应用程序就是这样做的:https://github.com/mludowise/ARKitRectangleDetection

    来自 VNRectangleObservation 的矩形角的坐标将相对于图像的大小,并且根据手机的不同坐标需要将它们乘以视图大小并根据手机的旋转反转它们:

    func convertFromCamera(_ point: CGPoint, view sceneView: ARSCNView) -> CGPoint {
        let orientation = UIApplication.shared.statusBarOrientation
    
        switch orientation {
        case .portrait, .unknown:
            return CGPoint(x: point.y * sceneView.frame.width, y: point.x * sceneView.frame.height)
        case .landscapeLeft:
            return CGPoint(x: (1 - point.x) * sceneView.frame.width, y: point.y * sceneView.frame.height)
        case .landscapeRight:
            return CGPoint(x: point.x * sceneView.frame.width, y: (1 - point.y) * sceneView.frame.height)
        case .portraitUpsideDown:
            return CGPoint(x: (1 - point.y) * sceneView.frame.width, y: (1 - point.x) * sceneView.frame.height)
        }
    }
    

    然后你可以对所有4个角进行命中测试 . 执行命中测试时使用 .existingPlaneUsingExtent 类型非常重要,以便ARKit返回水平平面的命中 .

    let tl = sceneView.hitTest(convertFromCamera(rectangle.topLeft, view: sceneView), types: .existingPlaneUsingExtent)
    let tr = sceneView.hitTest(convertFromCamera(rectangle.topRight, view: sceneView), types: .existingPlaneUsingExtent)
    let bl = sceneView.hitTest(convertFromCamera(rectangle.bottomLeft, view: sceneView), types: .existingPlaneUsingExtent)
    let br = sceneView.hitTest(convertFromCamera(rectangle.bottomRight, view: sceneView), types: .existingPlaneUsingExtent)
    

    然后它变得有点复杂......

    因为每个命中测试都可以返回0到n的结果,所以您需要过滤掉不同平面上包含的任何命中测试 . 你可以通过比较每个 ARHitTestResult 的锚来做到这一点:

    hit1.anchor == hit2.anchor
    

    此外,如果一个角落没有返回任何命中测试结果,您只需要4个角中的3个来识别矩形's dimensions, position, and orientation so it'即可 . 看看我如何做到这一点here .

    您可以根据左右角之间的距离(顶部或底部)计算矩形的宽度 . 同样,您可以根据顶角和底角之间的距离(左侧或右侧)计算矩形的高度 .

    func distance(_ a: SCNVector3, from b: SCNVector3) -> CGFloat {
        let deltaX = a.x - b.x
        let deltaY = a.y - b.y
        let deltaZ = a.z - b.z
    
        return CGFloat(sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ))
    }
    
    let width = distance(right, from: left)
    let height = distance(top, from: bottom)
    

    您可以通过从矩形的相对角(topLeft&bottomRight或topRight&bottomLeft)获取中点来计算其位置:

    let midX = (c1.x + c2.x) / 2
    let midY = (c1.y + c2.y) / 2
    let midZ = (c1.z + c2.z) / 2
    let center = SCNVector3Make(midX, midY, midZ)
    

    您还可以从左右角(顶部或底部)计算矩形的方向(沿y轴旋转):

    let distX = right.x - left.x
    let distZ = right.z - left.z
    let orientation = -atan(distZ / distX)
    

    然后将它们全部放在一起并在矩形上显示AR中的某些内容 . 这是通过子类化 SCNNode 显示虚拟矩形的示例:

    class RectangleNode: SCNNode {
    
        init(center: SCNVector3, width: CGFloat, height: CGFloat, orientation: Float) {
            super.init()
    
            // Create the 3D plane geometry with the dimensions calculated from corners
            let planeGeometry = SCNPlane(width: width, height: height)
            let rectNode = SCNNode(geometry: planeGeometry)
    
            // Planes in SceneKit are vertical by default so we need to rotate
            // 90 degrees to match planes in ARKit
            var transform = SCNMatrix4MakeRotation(-Float.pi / 2.0, 1.0, 0.0, 0.0)
    
            // Set rotation to the corner of the rectangle
            transform = SCNMatrix4Rotate(transform, orientation, 0, 1, 0)
    
            rectNode.transform = transform
    
            // We add the new node to ourself since we inherited from SCNNode
            self.addChildNode(rectNode)
    
            // Set position to the center of rectangle
            self.position = center
        }
    }
    
  • 14

    要考虑的主要是边界矩形在2D图像中,而ARKit的场景是3D . 这意味着在您选择深度之前,未定义边界矩形在3D中的位置 .

    您应该做的是对场景进行命中测试以从2D坐标到3D:

    let box = newObservation.boundingBox
    let rectCenter = CGPoint(x: box.midX, y: box.midY)
    let hitTestResults = sceneView.hitTest(rectCenter, types: [.existingPlaneUsingExtent, .featurePoint])
    // Pick the hitTestResult you need (nearest?), get position via worldTransform
    

相关问题