ARKit图像识别-图像跟踪

时间:2018-08-20 09:53:01

标签: ios swift image scenekit arkit

我尝试构建一个ARKit应用。这个想法是识别图像并将3D对象放置在图像上方。这可以正常工作,但是问题是,对于某些图像,无法跟踪3D对象。 我知道,图像不会被跟踪(图像移动时),但是当我移动iPhone时,对于某些图像,3D对象停留在图像的中间,而对于其他图像,它会“飞来飞去”。我测试了png / jpg,不同的尺寸,good / bad的质量-我不知道为什么有些图像不起作用。有什么想法吗?

import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView!
    private var planeNode: SCNNode?
    private var imageNode: SCNNode?
    private var animationInfo: AnimationInfo?
    private var currentMediaName: String?

    override func viewDidLoad() {
        super.viewDidLoad()

        let scene = SCNScene()
        sceneView.scene = scene
        sceneView.delegate = self

    }

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

        // Load reference images to look for from "AR Resources" folder
        guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else {
            fatalError("Missing expected asset catalog resources.")
        }

        // Create a session configuration
        let configuration = ARWorldTrackingConfiguration()

        // Add previously loaded images to ARScene configuration as detectionImages
        configuration.detectionImages = referenceImages

        // Run the view's session
        sceneView.session.run(configuration)

        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(rec:)))
        //Add recognizer to sceneview
        sceneView.addGestureRecognizer(tap)
    }

    //Method called when tap
    @objc func handleTap(rec: UITapGestureRecognizer) {
    //GET Reference-Image Name
        loadReferenceImage()

        if rec.state == .ended {
            let location: CGPoint = rec.location(in: sceneView)
            let hits = self.sceneView.hitTest(location, options: nil)
            if !hits.isEmpty {
                let tappedNode = hits.first?.node
            }
        }
    }

    func loadReferenceImage() {
        print("CLICK")
    }

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        guard let imageAnchor = anchor as? ARImageAnchor else {
            return
        }
        currentMediaName = imageAnchor.referenceImage.name

        // 1. Load plane's scene.
        let planeScene = SCNScene(named: "art.scnassets/plane.scn")!
        let planeNode = planeScene.rootNode.childNode(withName: "planeRootNode", recursively: true)!

        // 2. Calculate size based on planeNode's bounding box.
        let (min, max) = planeNode.boundingBox
        let size = SCNVector3Make(max.x - min.x, max.y - min.y, max.z - min.z)

        // 3. Calculate the ratio of difference between real image and object size.
        // Ignore Y axis because it will be pointed out of the image.
        let widthRatio = Float(imageAnchor.referenceImage.physicalSize.width)/size.x
        let heightRatio = Float(imageAnchor.referenceImage.physicalSize.height)/size.z
        // Pick smallest value to be sure that object fits into the image.
        let finalRatio = [widthRatio, heightRatio].min()!

        // 4. Set transform from imageAnchor data.
        planeNode.transform = SCNMatrix4(imageAnchor.transform)

        // 5. Animate appearance by scaling model from 0 to previously calculated value.
        let appearanceAction = SCNAction.scale(to: CGFloat(finalRatio), duration: 0.4)
        appearanceAction.timingMode = .easeOut
        // Set initial scale to 0.
        planeNode.scale = SCNVector3Make(0.0, 0.0, 0.0)
        // Add to root node.
        sceneView.scene.rootNode.addChildNode(planeNode)
        // Run the appearance animation.
        planeNode.runAction(appearanceAction)

        self.planeNode = planeNode
        self.imageNode = node
    }

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor, updateAtTime time: TimeInterval) {
        guard let imageNode = imageNode, let planeNode = planeNode else {
            return
        }

        // 1. Unwrap animationInfo. Calculate animationInfo if it is nil.
        guard let animationInfo = animationInfo else {
            refreshAnimationVariables(startTime: time,
                                  initialPosition: planeNode.simdWorldPosition,
                                  finalPosition: imageNode.simdWorldPosition,
                                  initialOrientation: planeNode.simdWorldOrientation,
                                  finalOrientation: imageNode.simdWorldOrientation)
            return
        }

        // 2. Calculate new animationInfo if image position or orientation changed.
        if !simd_equal(animationInfo.finalModelPosition, imageNode.simdWorldPosition) || animationInfo.finalModelOrientation != imageNode.simdWorldOrientation {

            refreshAnimationVariables(startTime: time,
                                  initialPosition: planeNode.simdWorldPosition,
                                  finalPosition: imageNode.simdWorldPosition,
                                  initialOrientation: planeNode.simdWorldOrientation,
                                  finalOrientation: imageNode.simdWorldOrientation)
        }

        // 3. Calculate interpolation based on passedTime/totalTime ratio.
        let passedTime = time - animationInfo.startTime
        var t = min(Float(passedTime/animationInfo.duration), 1)
        // Applying curve function to time parameter to achieve "ease out" timing
        t = sin(t * .pi * 0.5)

        // 4. Calculate and set new model position and orientation.
        let f3t = simd_make_float3(t, t, t)
        planeNode.simdWorldPosition = simd_mix(animationInfo.initialModelPosition, animationInfo.finalModelPosition, f3t)
        planeNode.simdWorldOrientation = simd_slerp(animationInfo.initialModelOrientation, animationInfo.finalModelOrientation, t)
        //planeNode.simdWorldOrientation = imageNode.simdWorldOrientation

        guard let currentImageAnchor = anchor as? ARImageAnchor else { return }

        let name = currentImageAnchor.referenceImage.name!
        print("TEST")
        print(name)
    }

    func refreshAnimationVariables(startTime: TimeInterval, initialPosition: float3, finalPosition: float3, initialOrientation: simd_quatf, finalOrientation: simd_quatf) {
        let distance = simd_distance(initialPosition, finalPosition)
        // Average speed of movement is 0.15 m/s.
        let speed = Float(0.15)
        // Total time is calculated as distance/speed. Min time is set to 0.1s and max is set to 2s.
        let animationDuration = Double(min(max(0.1, distance/speed), 2))
        // Store animation information for later usage.
        animationInfo = AnimationInfo(startTime: startTime,
                                  duration: animationDuration,
                                  initialModelPosition: initialPosition,
                                  finalModelPosition: finalPosition,
                                  initialModelOrientation: initialOrientation,
                                  finalModelOrientation: finalOrientation)
    }

}

1 个答案:

答案 0 :(得分:0)

不是将节点直接添加到scene.rootNode,而是将其添加到与锚点关联的节点:

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        node.addChildNode(yourNode)
    }

由于将其放置在检测到的图像的中心,因此无需为其定位。 在此处了解锚点的重要性:What's the difference between using ARAnchor to insert a node and directly insert a node?

另外两点:

  • 确保在AR资源中设置的图像大小正确
  • 在移动手机之前,先检测一下图像。这使ARKit有机会了解场景并有助于更好地跟踪