AR refernce image plane was not position properly

2019-05-10 07:38发布

问题:

I am working on business card profile information using ar reference image. When reference detects it will show company ceo details, address, photo and team members information, etc. Initially image detected it plane will move to right using runAction amintion.

My question is detected ar refrence, plane position was stable and its moving here and there. How to fit plane position with ar reference image.

Here is the screenshot my result:[![enter image description here][1]][1]

Here is the code i used:

var weboverlayview: CALayer?
var loadWeb: UIWebView?
override func viewDidLoad() {
    super.viewDidLoad()

    // Set the view's delegate
    sceneView.delegate = self

    // Show statistics such as fps and timing information
    sceneView.showsStatistics = true
    sceneView.autoenablesDefaultLighting = true
    let ARScene = SCNScene()
    sceneView.scene = ARScene

}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // Create a session configuration
    let configuration = ARWorldTrackingConfiguration()
    configuration.planeDetection = [.vertical, .horizontal]
    configuration.detectionImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil)
    // Run the view's session
    sceneView.session.run(configuration)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    // Pause the view's session
    sceneView.session.pause()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Release any cached data, images, etc that aren't in use.
}


func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {

    let anchorNode = SCNNode()
    anchorNode.name = "anchor"
    sceneView.scene.rootNode.addChildNode(anchorNode)
    return anchorNode

}

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

    var labelNode:SCNNode?
    var companyLabelNode: SCNNode?
    var addressLabelNode: SCNNode?
    var webaddressLabelNode: SCNNode?
    var maillabelNode: SCNNode?
    var mobileLabelNode:SCNNode?
    var teamLabelNode:SCNNode?


    guard let imageAnchor = anchor as? ARImageAnchor else {return}
    if let imageName  = imageAnchor.referenceImage.name {

        print(imageName)



        if imageName == "card"{



            let plane = SCNPlane(width: 20,height: 24)
            plane.firstMaterial?.diffuse.contents = UIColor.black.withAlphaComponent(0.75)
            plane.cornerRadius = 0.25

            let planeNodee = SCNNode(geometry: plane)
            planeNodee.eulerAngles.x = -.pi / 2
            planeNodee.runAction(SCNAction.moveBy(x: -5, y: 0, z: 19, duration: 0.75))

            labelNode = self.addLabel(text: "Gowdhaman Kandasamy \nFounder and CEO", anchor: imageAnchor)
            labelNode?.runAction(SCNAction.moveBy(x: -1.3, y: 1, z: 16.8, duration: 0.75))



            companyLabelNode = self.addLabel(text: "CZ Smart Mobility", anchor: imageAnchor)
            companyLabelNode?.runAction(SCNAction.moveBy(x: 1.5, y: 1, z: 22, duration: 0.75))

            addressLabelNode = self.addAddressLabel(text: "Official Address:\n\n1st floor, TBI Office,\nDr.col JEPPIAR Research Park,\nResearch and development center,\nSathyabama University,\nChennai-600119\nTamil nadu, India.", anchor: imageAnchor)
            addressLabelNode?.runAction(SCNAction.moveBy(x: -4.8, y: 1, z: 16.8, duration: 0.75))



            let userImagePlane = SCNPlane(width: 3.5, height: 3.5)
            userImagePlane.firstMaterial?.diffuse.contents = UIImage(named: "gow")
            userImagePlane.cornerRadius = 0.25
            let userPlaneNode = SCNNode(geometry: userImagePlane)
            userPlaneNode.eulerAngles.x = -.pi/2
            userPlaneNode.runAction(SCNAction.moveBy(x: -1, y: 1, z: 9.5, duration: 0.75))


            let webImagePlane = SCNPlane(width: 1, height: 1)
            webImagePlane.firstMaterial?.diffuse.contents = UIImage(named: "web")

            webImagePlane.cornerRadius = 0.25
            let webPlanenode = SCNNode(geometry: webImagePlane)
            webPlanenode.eulerAngles.x = -.pi/2
            webPlanenode.runAction(SCNAction.moveBy(x: -7, y: 1, z: 12.5, duration: 0.75))
            webaddressLabelNode = addAddressLabel(text: "www.czsm.co.in", anchor: imageAnchor)
            webaddressLabelNode?.runAction(SCNAction.moveBy(x: -8.7, y: 1, z: 18.2, duration: 0.75))

            let mailImagePlane = SCNPlane(width: 1, height: 1)
            mailImagePlane.firstMaterial?.diffuse.contents = UIImage(named: "mail")

            mailImagePlane.cornerRadius = 0.25
            let mailPlanenode = SCNNode(geometry: mailImagePlane)
            mailPlanenode.eulerAngles.x = -.pi/2
            mailPlanenode.runAction(SCNAction.moveBy(x: -7, y: 1, z: 17.5, duration: 0.75))
            maillabelNode = addAddressLabel(text: "gowdhaman@czsm.co.in", anchor: imageAnchor)
            maillabelNode?.runAction(SCNAction.moveBy(x: -8.7, y: 1, z: 23.2, duration: 0.75))

            let mobileImagePlane = SCNPlane(width: 1, height: 1)
            mobileImagePlane.firstMaterial?.diffuse.contents = UIImage(named: "mobile")

            mobileImagePlane.cornerRadius = 0.25
            let mobilePlanenode = SCNNode(geometry: mobileImagePlane)
            mobilePlanenode.eulerAngles.x = -.pi/2
            mobilePlanenode.runAction(SCNAction.moveBy(x: -7, y: 1, z: 23.9, duration: 0.75))
            mobileLabelNode = addAddressLabel(text: "+919941123110", anchor: imageAnchor)
            mobileLabelNode?.runAction(SCNAction.moveBy(x: -8.7, y: 1, z: 29.7, duration: 0.75))


            /************Team members*************/

            teamLabelNode = self.addLabel(text: "Team Members", anchor: imageAnchor)
            teamLabelNode?.runAction(SCNAction.moveBy(x: -9.8, y: 1, z: 22, duration: 0.75))


            let sivaImagePlane = SCNPlane(width: 2.7, height: 2.7)
            sivaImagePlane.firstMaterial?.diffuse.contents = UIImage(named: "pic2")

            sivaImagePlane.cornerRadius = 0.25
            let sivaPlanenode = SCNNode(geometry: sivaImagePlane)
            sivaPlanenode.eulerAngles.x = -.pi/2
            sivaPlanenode.runAction(SCNAction.moveBy(x: -11, y: 1, z: 9.5, duration: 0.75))


            let parameshImagePlane = SCNPlane(width: 2.7, height: 2.7)
            parameshImagePlane.firstMaterial?.diffuse.contents = UIImage(named: "pic4")

            parameshImagePlane.cornerRadius = 0.25
            let parameshPlanenode = SCNNode(geometry: parameshImagePlane)
            parameshPlanenode.eulerAngles.x = -.pi/2
            parameshPlanenode.runAction(SCNAction.moveBy(x: -11, y: 1, z: 9.5, duration: 0.75))






            node.addChildNode(planeNodee)
            node.addChildNode(labelNode!)
            node.addChildNode(companyLabelNode!)
            node.addChildNode(userPlaneNode)
            node.addChildNode(addressLabelNode!)
            node.addChildNode(webPlanenode)
            node.addChildNode(webaddressLabelNode!)
            node.addChildNode(mailPlanenode)
            node.addChildNode(maillabelNode!)
            node.addChildNode(mobilePlanenode)
            node.addChildNode(mobileLabelNode!)
            node.addChildNode(teamLabelNode!)

            node.addChildNode(sivaPlanenode)
            node.addChildNode(parameshPlanenode)


       //                node.addChildNode(czwebPlaneNode)
            self.sceneView.scene.rootNode.addChildNode(node)



        }

    }


   }
   func webButton() {






  }


func addLabel(text: String, anchor: ARImageAnchor) -> SCNNode {
    let plane = SCNPlane(width: 10,
                         height: 4)

    let planeNode = SCNNode(geometry: plane)
    planeNode.eulerAngles.x = (-.pi)/2
    planeNode.eulerAngles.y = (-.pi)/2
    //        planeNode.eulerAngles.z = (-.pi)/2



    let skScene = SKScene(size: CGSize(width: 400, height: 100))

    skScene.backgroundColor = UIColor.clear

    let substrings: [String] = text.components(separatedBy: "\n")
    for aSubstring in substrings {
        let lbl = SKLabelNode(text: aSubstring)
        lbl.fontSize = 18
        lbl.numberOfLines = 1
        lbl.fontColor = UIColor.white
        lbl.fontName = "Avenir-medium"
        let y = CGFloat(substrings.index(of: aSubstring)! + 1) * lbl.fontSize
        print("yname::::\(y)")
        lbl.position = CGPoint(x: 0, y: y)
        lbl.horizontalAlignmentMode = .left
        lbl.yScale *= -1
        skScene.addChild(lbl)



    }




    let material = SCNMaterial()
    material.isDoubleSided = false
    material.diffuse.contents = skScene
    plane.materials = [material]

    return planeNode
    }

      func addCompanyLabel(text: String, anchor: ARImageAnchor) -> SCNNode {
        let plane1 = SCNPlane(width: 10,
                         height: 4)

    let planeNode1 = SCNNode(geometry: plane1)
    planeNode1.eulerAngles.x = (-.pi)/2
    planeNode1.eulerAngles.y = (-.pi)/2
    //        planeNode.eulerAngles.z = (-.pi)/2



    let skScene1 = SKScene(size: CGSize(width: 400, height: 100))

    skScene1.backgroundColor = UIColor.clear

    let substrings: [String] = text.components(separatedBy: "\n")
    for aSubstring in substrings {
        let lbl1 = SKLabelNode(text: aSubstring)
        lbl1.fontSize = 20
        lbl1.numberOfLines = 1
        lbl1.fontColor = UIColor.white
        lbl1.fontName = "Avenir-medium"
        let y = CGFloat(substrings.index(of: aSubstring)! + 1) * lbl1.fontSize
        print("ycompanname::::\(y)")
        lbl1.position = CGPoint(x: 0, y: y)
        lbl1.horizontalAlignmentMode = .left
        lbl1.yScale *= -1
        skScene1.addChild(lbl1)



    }

    let material = SCNMaterial()
    material.isDoubleSided = false
    material.diffuse.contents = skScene1
    plane1.materials = [material]

       return planeNode1
     }

    func addAddressLabel(text: String, anchor: ARImageAnchor) -> SCNNode {
    let plane = SCNPlane(width: 10,
                         height: 4)

    let planeNode = SCNNode(geometry: plane)
    planeNode.eulerAngles.x = (-.pi)/2
    planeNode.eulerAngles.y = (-.pi)/2
        //        planeNode.eulerAngles.z = (-.pi)/2



    let skScene = SKScene(size: CGSize(width: 500, height: 200))

    skScene.backgroundColor = UIColor.clear

    let substrings: [String] = text.components(separatedBy: "\n")
    for aSubstring in substrings {
        let lbl = SKLabelNode(text: aSubstring)
        lbl.fontSize = 20
     //            lbl.numberOfLines = 1
        lbl.fontColor = UIColor.white
        lbl.fontName = "Avenir-medium"
        let y = CGFloat(substrings.index(of: aSubstring)! + 1) * lbl.fontSize
        print("yaddress::::\(y)")
        lbl.position = CGPoint(x: 0, y: y)
        lbl.horizontalAlignmentMode = .left
        lbl.yScale *= -1
        skScene.addChild(lbl)



    }

    let material = SCNMaterial()
    material.isDoubleSided = false
    material.diffuse.contents = skScene
    plane.materials = [material]

    return planeNode
   }




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


  //        guard let planeAnchor = anchor as? ARPlaneAnchor else {return}
  //        let planeGeometry = planeAnchor.geometry
   //        guard let device = MTLCreateSystemDefaultDevice() else {return}
  //        let plane  = ARSCNPlaneGeometry(device: device)
   //        plane?.update(from: planeGeometry)
    //        node.geometry = plane
    //        node.geometry?.firstMaterial?.diffuse.contents = UIColor.black
   //        node.geometry?.firstMaterial?.transparency = 1
  //        node.geometry?.firstMaterial?.fillMode = SCNFillMode.lines

     }



  }

Excepted result like this ar reference plane will fit to card https://www.facebook.com/oscarfalmer/videos/10156651667309345/

回答1:

The first thing you need to consider is whether you want to use ARWorldTrackingConfiguration or ARImageTrackingConfiguration (IOS12).

If you use ARImageTrackingConfiguration, you cant make use of PlaneDetection, as this is an Image Only tracking configuration:

which lets you anchor virtual content to known images only when those images are in view of the camera. World tracking with image detection lets you use known images to add virtual content to the 3D world, and continues to track the position of that content in world space even after the image is no longer in view.

This would be your best bet, if you want your content to stay anchored to the image at all times (when in view of the camera) since:

it tracks their movement with six degrees of freedom (6DOF): specifically, the three rotation axes (roll, pitch, and yaw), and three translation axes (movement in x, y, and z).

On the other hand if you want to detect ARPlaneAnchors, as well as ARImageAnchors, but aren't bothered that any content associated with your ARImageAnchor won't track constantly then you should use ARWorldTrackingConfiguration.

As @Trinca also said you need to ensure that the measurements you provide for your image are as accurate as possible, as ARKit uses these to return the physicalSize and physicalWidth of your image which will allow your virtual content to placed more accurately (e.g if you specify a larger size than the actual size of your image in real life, your not going to be able to align your virtual content accurately).

When creating a business card or any imageTarget we must make sure that our dimensions are accurately set in the ARReferenceImage Settings box:

We can then check to see if our imageTarget is detected like so:

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

        //1. Check We Have Detected An ARImageAnchor & Check It's The One We Want
        guard let validImageAnchor = anchor as? ARImageAnchor,
              let targetName = validImageAnchor.referenceImage.name, targetName == "TargetCard" else { return}


        //2. Check To See The Detected Size Of Our Business Card (Should By 5cm*3cm)
        let businessCardWidth = validImageAnchor.referenceImage.physicalSize.width
        let businessCardHeight =  validImageAnchor.referenceImage.physicalSize.height

        print(
             """
              We Have Detected Business Card With Name \(targetName)
              \(targetName)'s Width Is \(businessCardWidth)
              \(targetName)'s Height Is \(businessCardHeight)
             """)

    }

Having checked that our detected size is accurate we can then place whatever content we like in relation to this.

Rather than doing everything programatically, an easier way to achieve the results you are looking for is to create an SCNScene.

Update:

As you have asked for an example project I have created a fully working example for everyone which can be download here: ARKit Business Card

Without going through every Class in detail I will provide you with the basic details.

We will use an SCNScene as a reusable template, which contains a range of SCNNode which are used as buttons and which can perform different actions when they are pressed.

The basic template looks like so:

The BusinessCard Node is initialised with A BusinessCardData Struct which looks like so:

typealias SocialLinkData = (link: String, type: SocialLink)

/// The Information For The Business Card Node & Contact Details
struct BusinessCardData{

    var firstName: String
    var surname: String
    var position: String
    var company: String
    var address: BusinessAddress
    var website: SocialLinkData
    var phoneNumber: String
    var email: String
    var stackOverflowAccount: SocialLinkData
    var githubAccount: SocialLinkData

}

/// The Associates Business Address
struct BusinessAddress{

    var street: String
    var city: String
    var state: String
    var postalCode: String
    var coordinates: (latittude: Double, longtitude: Double)
}

/// The Type Of Social Link
///
/// - Website: Business Website
/// - StackOverFlow: StackOverFlow Account
/// - GitHub: Github Account
enum SocialLink: String{

    case Website
    case StackOverFlow
    case GitHub
}

Whereby all the data provided is mapped to each SCNNode in the template, and helps to perform the necessary functions.

By using a struct we can create multiple interactive business cards simply e.g:

//--------------------------
//MARK: -  ARSessionDelegate
//--------------------------

extension  ViewController: ARSCNViewDelegate{

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

        //1. Check We Have A Valid Image Anchor
        guard let imageAnchor = anchor as? ARImageAnchor else { return }

        //2. Get The Detected Reference Image
        let referenceImage = imageAnchor.referenceImage

        //3. Load Our Business Card
        if let matchedBusinessCardName = referenceImage.name, matchedBusinessCardName == "BlackMirrorz"{

            //4. Create Our Business Card
            let businessCardData = BusinessCardData(firstName: "Josh",
                                                    surname: "Robbins",
                                                    position: "Software Engineer",
                                                    company: "BlackMirrorz",
                                                    address: BusinessAddress(street: "1 Infinite Loop",
                                                                             city: "Cupertino",
                                                                             state: "CA",
                                                                             postalCode: "95015",
                                                                             coordinates: (latittude: 37.3349, longtitude: -122.0090201)),
                                                    website: SocialLinkData(link: "https://www.blackmirrorz.tech", type: .Website),
                                                    phoneNumber: "+821076337633",
                                                    email: "josh.robbins@blackmirroz.tech",
                                                    stackOverflowAccount: SocialLinkData(link: "https://stackoverflow.com/users/8816868/josh-robbins", type: .StackOverFlow),
                                                    githubAccount: SocialLinkData(link: "https://github.com/BlackMirrorz", type: .GitHub))

            //5. Assign It To The Business Card Node
            let businessCard = BusinessCard(data: businessCardData, cardType: .noProfileImage)
            businessCardPlaced = true
            node.addChildNode(businessCard)

        }
    }
}

Since the design is already laid out, we dont need to do any complex calculations. Everything is done for us!

Interaction by the user is done using the following icons:

  • The StackOverFlow Button presents a slide out WKWebView to display the users StackOverFlow Account.
  • The GitHub Button presents a slide out WKWebView to display the users GitHub Account.
  • The Internet Button presents a slide out WKWebView to display the users website.
  • The Phone Button allows the user to call the Business Telephone Number.
  • The SMS Button presents an MFMessageComposeViewController allowing the user to send a text message to the business.
  • The Email Button presents an MFMailComposeViewController allowing the user to email the business.
  • The Contact Button creates a CNMutableContact and saves the business as a new contact on the users device.
  • The Location Button presents a slide out MKMapView to display the users Businesses Location.

Since rendering a WKWebView as an SCNMaterial, I had to look at other ways to allow the content to be fully interactive.

As such I made use of the fabulous repository SideMenu by Jonkykong which is available here: SideMenu

This allows the user to still experience ARKit whilst allowing an almost split screen like effect:

As always, hope it helps you and everyone else who is interested in learning ARKit...



回答2:

I had a similar problem and it was because a mistake setting the size of Reference Images.

If you're manually importing them to your "AR Resources Group", then you need to be sure to set "meters" or "centimetres" when typing the width and height on the right panel.

If you're loading those images from a server and than making them Reference Images, remember the default metric ARKit uses is "meters". In this case, if you set the width as 20.0, ARKit will consider 20 meters instead of 20 centimetres, bringing you a very inaccurate behaviour when tracking the image plane.

Hope it helps.