Как преобразовать систему координат vision framework в ARKit?

Я использую ARKit (с SceneKit) для добавления виртуального объекта (например, шара). Я отслеживаю объект реального мира (например, ногу) с помощью Vision framework и получаю его обновленную позицию в методе обработчика завершения запроса vision.

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

Я хочу заменить отслеживаемый объект реального мира виртуальным (например, заменить ногу кубом), но я не уверен, как заменить boundingbox rect (который мы получаем в завершении запроса vision) в узел набора сцен в качестве координаты системы разные.

ниже приведен код обработчика завершения запроса vision:

 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 углах и использовать 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

кроме того, вам нужно только 3 из 4 углов, чтобы определить размеры, положение и ориентацию прямоугольника, поэтому все в порядке, если один угол не возвращает результатов теста попадания. Взгляните здесь за то, как я это сделал.

вы можете рассчитать ширину прямоугольника от расстояния между левым и правым углами (для верхней или нижней). Точно так же вы можете рассчитать высоту прямоугольника по расстоянию между верхним и нижним углами (для левого или правого).

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
    }
}

главное учитывать, что ограничивающий прямоугольник находится в 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