【问题标题】:ARKit ImageDetection - get reference image when tapping 3D objectARKit ImageDetection - 点击 3D 对象时获取参考图像
【发布时间】:2018-07-31 13:35:12
【问题描述】:

我想创建一个检测参考图像的应用程序,然后出现一个 3D (SCNScene) 对象(1 个相机中的多个图像/对象是可能的)。这已经在运行了。

现在,当用户点击对象时,我需要知道 referenceImage 的文件名,因为应该显示图像。


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

}

【问题讨论】:

    标签: ios augmented-reality arkit


    【解决方案1】:

    由于您的ARReferenceImage 存储在Assets.xcassets 目录中,您可以使用UIImage 的以下初始化方法简单地加载您的图像:

    init?(named name: String)
    

    供您参考:

    如果这是第一次图像 加载后,该方法会在 应用程序的主包。对于 PNG 图像,您可以省略文件名 扩大。对于所有其他文件格式,始终包含文件名 扩展名。

    在我的示例中,我有一个名为 TargetCardARReferenceImage

    所以要将其加载为UIImage,然后将其应用为SCNNode 或在screenSpace 中显示,您可以这样:

    //1. Load The Image Onto An SCNPlaneGeometry
    if let image = UIImage(named: "TargetCard"){
    
        let planeNode = SCNNode()
        let planeGeometry = SCNPlane(width: 1, height: 1)
        planeGeometry.firstMaterial?.diffuse.contents = image
        planeNode.geometry = planeGeometry
        planeNode.position = SCNVector3(0, 0, -1.5)
        self.augmentedRealityView.scene.rootNode.addChildNode(planeNode)
    }
    
    //2. Load The Image Into A UIImageView
    if let image = UIImage(named: "TargetCard"){
    
        let imageView = UIImageView(frame: CGRect(x: 10, y: 10, width: 300, height: 150))
        imageView.image = image
        imageView.contentMode = .scaleAspectFill
        self.view.addSubview(imageView)
    }
    

    在您的上下文中:

    每个 SCNNode 都有一个 name 属性:

    var name: String? { get set }
    

    因此,我建议您在创建与 ARImageAnchor 相关的内容时,提供您的 ARReferenceImage 的名称,例如:

    //---------------------------
    // MARK: -  ARSCNViewDelegate
    //---------------------------
    
    extension ViewController: ARSCNViewDelegate{
    
        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 else { return}
    
            //2. Create An SCNNode With An SCNPlaneGeometry
            let nodeToAdd = SCNNode()
            let planeGeometry = SCNPlane(width: 1, height: 1)
            planeGeometry.firstMaterial?.diffuse.contents = UIColor.cyan
            nodeToAdd.geometry = planeGeometry
    
            //3. Set It's Name To That Of Our ARReferenceImage
            nodeToAdd.name = targetName
    
            //4. Add It To The Hierachy
            node.addChildNode(nodeToAdd)
    
        }
    }
    

    那么以后很容易获得对 Image 的引用,例如:

    /// Checks To See If We Have Hit A Named SCNNode
    ///
    /// - Parameter gesture: UITapGestureRecognizer
    @objc func handleTap(_ gesture: UITapGestureRecognizer){
    
        //1. Get The Current Touch Location
        let currentTouchLocation = gesture.location(in: self.augmentedRealityView)
    
        //2. Perform An SCNHitTest To See If We Have Tapped A Valid SCNNode & See If It Is Named
        guard let hitTestForNode = self.augmentedRealityView.hitTest(currentTouchLocation, options: nil).first?.node,
              let nodeName = hitTestForNode.name else { return }
    
        //3. Load The Reference Image
        self.loadReferenceImage(nodeName, inAR: true)
    }
    
    /// Loads A Matching Image For The Identified ARReferenceImage Name
    ///
    /// - Parameters:
    ///   - fileName: String
    ///   - inAR: Bool
    func loadReferenceImage(_ fileName: String, inAR: Bool){
    
        if inAR{
    
            //1. Load The Image Onto An SCNPlaneGeometry
            if let image = UIImage(named: fileName){
    
                let planeNode = SCNNode()
                let planeGeometry = SCNPlane(width: 1, height: 1)
                planeGeometry.firstMaterial?.diffuse.contents = image
                planeNode.geometry = planeGeometry
                planeNode.position = SCNVector3(0, 0, -1.5)
                self.augmentedRealityView.scene.rootNode.addChildNode(planeNode)
            }
    
        }else{
    
            //2. Load The Image Into A UIImageView
            if let image = UIImage(named: fileName){
    
                let imageView = UIImageView(frame: CGRect(x: 10, y: 10, width: 300, height: 150))
                imageView.image = image
                imageView.contentMode = .scaleAspectFill
                self.view.addSubview(imageView)
            }
        }
    
    }
    

    重要:

    我刚刚发现的一件事是,如果我们加载 ARReferenceImage 例如:

    let image = UIImage(named: "TargetCard")
    

    然后图片显示在GrayScale,这正是你不想要的!

    因此,您可能需要做的是将ARReferenceImage 复制到Assets Catalogue 并给它一个prefix,例如颜色目标卡...

    然后您需要通过使用前缀命名节点来稍微更改功能,例如:

    nodeToAdd.name = "Colour\(targetName)"
    

    希望对你有帮助...

    【讨论】:

      猜你喜欢
      • 2022-11-24
      • 2017-12-14
      • 2018-09-17
      • 2020-05-19
      • 2018-03-27
      • 2021-12-26
      • 2018-08-06
      • 2019-03-06
      • 1970-01-01
      相关资源
      最近更新 更多