2017-07-06 2 views
8

J'utilise ARKit (avec SceneKit) pour ajouter l'objet virtuel (par exemple, une balle). Je suis en train de suivre un objet du monde réel (par exemple pied) en utilisant le cadre Vision et en recevant sa position mise à jour dans la méthode du gestionnaire d'achèvement de demande de vision.Comment transformer le système de coordonnées du cadre de vision en ARKit?

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

Je souhaite remplacer l'objet du monde réel suivi avec virtuel (par exemple remplacer le pied avec le cube) mais je ne suis pas sûr de savoir comment remplacer le rect boundingBox (que nous recevons dans l'achèvement de la demande de vision) dans le noeud de kit de scène comme système de coordonnées sont différents.

ci-dessous est le code du gestionnaire d'achèvement de demande de 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, 

    } 
    } 

S'il vous plaît me guider pour transférer le système de coordonnées.

Répondre

3

La principale chose à considérer est que le rectangle de délimitation est dans l'image 2D, tandis que la scène pour ARKit est 3D. Cela signifie que jusqu'à ce que vous choisissiez une profondeur, il n'est pas défini où se trouve le rectangle de délimitation en 3D.

Ce que vous devez faire est de lancer un test de recherche sur la scène pour obtenir de coordonnées 2D à la 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 
+1

Est-ce que cela fonctionne vraiment? l'origine et la taille du boundingBox retourné par l'observation de la vision est dans la plage [0, 1], pas la coordonnée uikit, alors que hitTest a besoin d'une coordonnée uikit –

5

En supposant que le rectangle est sur un plan horizontal, vous pouvez effectuer un test de recherche contre la scène sur tous les 4 coins et utilisez 3 de ces coins pour calculer la largeur, la hauteur, le centre et l'orientation du rectangle.

J'ai une application de démonstration disponible sur GitHub qui fait exactement cela: https://github.com/mludowise/ARKitRectangleDetection

Les coordonnées des coins du rectangle de VNRectangleObservation sera par rapport à la taille de l'image et des coordonnées différentes en fonction de la rotation du téléphone. Vous aurez besoin de les multiplier par la taille de la vue et les inverser en fonction de la rotation du téléphone:

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

Ensuite, vous pouvez effectuer un test de recherche sur les 4 coins. Il est important d'utiliser le type .existingPlaneUsingExtent lors de l'exécution du test de hit afin que ARKit renvoie des hits pour les plans horizontaux.

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) 

Ensuite, il devient un peu plus compliqué ...

Parce que chaque test de recherche pourrait revenir avec 0 à n résultats, vous devez filtrer les tests de succès qui sont contenus dans un plan différent. Vous pouvez le faire en comparant les points d'ancrage pour chaque ARHitTestResult:

hit1.anchor == hit2.anchor 

En outre, il vous suffit 3 sur 4 coins pour identifier les dimensions du rectangle, la position et l'orientation de sorte qu'il est correct si un coin ne retourne pas résultats de test de hit. Jetez un oeil here pour la façon dont je l'ai fait.

Vous pouvez calculer la largeur du rectangle à partir de la distance entre les coins gauche et droit (pour le haut ou le bas). De même, vous pouvez calculer la hauteur du rectangle à partir de la distance entre les coins inférieurs du haut (gauche ou droit).

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) 

On peut calculer sa position en obtenant le point central à partir des coins opposés du rectangle (soit topLeft & bottomRight ou 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) 

Vous pouvez également calculer l'orientation du rectangle (rotation le long de l'axe y) à partir des coins gauche et droit (pour le haut ou le bas):

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

Ensuite, mettez tout cela ensemble et affichez quelque chose en AR superposé sur le rectangle. Voici un exemple d'affichage d'un rectangle virtuel par sous-classe 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 
    } 
}