0

How do I get orientation data of an ARImageAnchor in relation to a given SCNNode in ARKit? This is the basic scenario.

A node placed at some point after the AR session has been started. Let's say its named originNode. Then ARKit is used to scan for image placed in the environment. When the camera detects an image, I want to calculate the X, Y, Z and the orientation (tilt, pan, roll) of the detected image relative to the originNode. Please note that the device is fixed at UIDeviceOrientation.landscapeRight from the start.

So the user can either scan for images while holding the device horizontally and camera pointing forward, or device is horizontal and camera is pointing directly down.

When I scan an image placed flat on a table, the values I'm getting with my current code is accurate I believe

Configuration:

let configuration = ARWorldTrackingConfiguration()

guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources",
                                                                   bundle: nil)
else { fatalError("Missing expected asset catalog resources.") }

configuration.detectionImages = referenceImages
configuration.maximumNumberOfTrackedImages = 100

Delegate:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

    if let imageAnchor = anchor as? ARImageAnchor {

        let anchorPositionRelativeToOrigin = node.convertPosition(SCNVector3Zero, to: originNode)
      
        let euler2 = node.eulerAngles
        print("Tilt: \(euler2.x.rad2deg()), Pan: \(euler2.y.rad2deg()), Roll: \(euler2.z.rad2deg())")
        
        let anchorTransformRelativeToHome = node.convertTransform(SCNMatrix4(), to: originNode)
        let rotation = simd_float4x4(anchorTransformRelativeToHome)

        let direction = SIMD3<Float>(-rotation.columns.2.x, -rotation.columns.2.y, -rotation.columns.2.z)
        
        let tiltAngle = atan2(direction.y, direction.z)
        let panAngle = atan2(direction.x, direction.z)
        let rollAngle = atan2(-rotation.columns.1.x, rotation.columns.0.x)
        print("Tilt: \(tiltAngle.rad2deg()) Pan: \(panAngle.rad2deg()) roll: \(rollAngle.rad2deg())")  
    }          
}

These are the two print results I get no matter which orientation I place the image.

Tilt: 0.0, Pan: -0.0, Roll: 0.0
Tilt: -180.0 Pan: -180.0 roll: -0.0

Any help would be much appreciated.

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
Ryan Aluvihare
  • 225
  • 1
  • 2
  • 9

1 Answers1

1

renderer(_:didUpdate:for:) instance method

Getting precise values for pitch, yaw and roll angles are hardly possible here, but +/-5 degrees in this situation is quite acceptable. By the way, you do not even need to create any originNode, since ARKit knows the world orientation of the ARImageAnchor relative to the initial orientation of the grid when the scene is being built. For the purity of the experiment, put a tracking picture on the table and run the app. If you are tracking a picture on a wall, then the +Y anchor's axis is directed towards the viewer (when the image is horizontal, the +Y axis is directed upwards). Try to move/rotate a picture.

import ARKit
import SceneKit

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode,
                                                for anchor: ARAnchor) {
        
    guard let _ = anchor as? ARImageAnchor else { return }
        
    let cylinderNode = SCNNode()
    cylinderNode.geometry = SCNCylinder(radius: 0.05, height: 0.4)
    node.addChildNode(cylinderNode)
        
    let ea: SCNVector3 = node.eulerAngles  // anchor's base node orientation
        
    print("Next value...")
    print(convert(ea.x), convert(ea.y), convert(ea.z))
}
    
func convert(_ radiansToDegrees: Float) -> Float {
    return radiansToDegrees * 180 / Float.pi
}

ARImageAnchor's base node has the same orientation as the anchor itself.


renderer(_:didAdd:for:) instance method

Or you can use renderer(_:didAdd:for:) delegate's method with async DispatchQueue.

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode,
                                             for anchor: ARAnchor) {
        
    guard let _ = anchor as? ARImageAnchor else { return }
        
    DispatchQueue.main.async {

        let cylinderNode = SCNNode()
        cylinderNode.geometry = SCNCylinder(radius: 0.05, height: 0.4)
        node.addChildNode(cylinderNode)
            
        let ea: SCNVector3 = node.eulerAngles  // registers a single value
            
        print(self.convert(ea.x), self.convert(ea.y), self.convert(ea.z))
    }
}
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • 1
    Thanks for the explanation Andy! I noticed the values that get printed out are changing slightly each time didUpdate is called even if I do not move the image. Is this expected behaviour ? or is is supposed to be a constant value x, y, z unless the image is moved physically ? – Ryan Aluvihare Apr 10 '23 at 12:50
  • 1
    Delegate's method is called 60 times per second while ARKit detects the image (even phone and/or image are not moving). – Andy Jazz Apr 10 '23 at 12:52
  • 1
    Okay. So depending on the phone location the values will change, am i correct ? – Ryan Aluvihare Apr 10 '23 at 12:59
  • 1
    Depending on the phone and/or tracking image movement/orientation. – Andy Jazz Apr 10 '23 at 13:00
  • 1
    is there a way to calculate the above data relative to a fixed location/node, So it doesn't change even when the device is moved? – Ryan Aluvihare Apr 10 '23 at 13:05
  • Use `renderer(_:didAdd:for:)` delegate's method (it just registers an initial value). – Andy Jazz Apr 10 '23 at 13:06
  • 1
    I did, but it is giving 0.0 -0.0 0.0 as the values. That's why I was wondering if we need to use something like 'originNode' or so. Any idea Andy? You have been really helpful so far. I'm grateful for you. – Ryan Aluvihare Apr 10 '23 at 13:11
  • 1
    works like a charm! Thanks Andy! One more request. :) Is it possible to do that relative to a different node which might have a different orientation to the initial scene. Say like, if the node was added after scene is run initially, the device was moved/oriented differently before scanning the image? Is it complicated or easily doable ? – Ryan Aluvihare Apr 10 '23 at 13:33
  • Just create one more question, I'll answer it )). – Andy Jazz Apr 10 '23 at 13:34
  • 1
    Sure, I will and update here. Thanks ! – Ryan Aluvihare Apr 10 '23 at 13:37
  • 1
    I have added it as a new question. https://stackoverflow.com/questions/75978284/arimageanchor-orientation-in-relation-to-a-node please take a look if you can. Thanks! – Ryan Aluvihare Apr 10 '23 at 15:11