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

Posted

技术标签:

【中文标题】视觉框架坐标系如何转化为ARKit?【英文标题】:How to transform vision framework coordinate system into ARKit? 【发布时间】:2017-12-10 04:56:11 【问题描述】:

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

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

我想用虚拟替换跟踪的现实世界对象(例如用立方体替换脚),但我不确定如何将 boundingBox rect(我们在视觉请求完成中收到)替换为场景套件节点,因为坐标系是不同。

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

 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,

    
  

请指导我转移坐标系。

【问题讨论】:

【参考方案1】:

假设矩形位于水平面上,您可以对场景的所有 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 个来识别矩形的尺寸、位置和方向,因此如果一个角不返回任何命中测试结果也没关系。看看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
    

【讨论】:

太棒了。这应该是公认的答案@Abhijeet Banarase。【参考方案2】:

要考虑的主要问题是边界矩形在 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

【讨论】:

这真的有效吗?视觉观察返回的boundingBox的原点和大小在[0, 1]范围内,不是uikit坐标,而hitTest需要uikit坐标

以上是关于视觉框架坐标系如何转化为ARKit?的主要内容,如果未能解决你的问题,请参考以下文章

带有 ARkit 和 CoreML 的视觉框架

无法获取 QR 码的正确坐标,ARKit - Swift

在gps数据中,如何能将经纬度数据转化为平面坐标?

arcgis10.2怎么把地理坐标系转化为投影坐标系 arcmap 10.2

将 ARKit 面部跟踪 3D 网格投影到 2D 图像坐标

GPS经纬度坐标如何转化为图上坐标