检查相机视图中是否不再显示ARReferenceImage

时间:2018-04-24 08:37:58

标签: swift xcode scenekit arkit aranchor

我想检查相机视图中是否不再显示ARReferenceImage。目前我可以检查图像的节点是否在摄像机的视图中,但是当ARReferenceImage被另一个图像覆盖或者图像被移除时,该节点仍然可以在摄像机的视图中看到。

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    guard let node = self.currentImageNode else { return }

    if let pointOfView = sceneView.pointOfView {
        let isVisible = sceneView.isNode(node, insideFrustumOf: pointOfView)
        print("Is node visible: \(isVisible)")
    }
}

所以我需要检查图像是否不再可见而不是图像的节点可见性。但我不知道这是否可行。第一个屏幕截图显示了找到下方图像时添加的三个框。当覆盖找到的图像时(见截图2),我想删除这些框。

Found image with boxes

Convered image with boxes

7 个答案:

答案 0 :(得分:10)

我设法解决了这个问题!使用了一点MaybeI的代码和他的概念来解决问题,但以不同的方式。以下代码行仍用于重新激活图像识别。

// Delete anchor from the session to reactivate the image recognition
sceneView.session.remove(anchor: anchor) 

让我解释一下。首先,我们需要添加一些变量。

// The scnNodeBarn variable will be the node to be added when the barn image is found. Add another scnNode when you have another image.    
var scnNodeBarn: SCNNode = SCNNode()
// This variable holds the currently added scnNode (in this case scnNodeBarn when the barn image is found)     
var currentNode: SCNNode? = nil
// This variable holds the UUID of the found Image Anchor that is used to add a scnNode    
var currentARImageAnchorIdentifier: UUID?
// This variable is used to call a function when there is no new anchor added for 0.6 seconds    
var timer: Timer!

以下评论的完整代码。

/// - Tag: ARImageAnchor-Visualizing
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let imageAnchor = anchor as? ARImageAnchor else { return }

    let referenceImage = imageAnchor.referenceImage

    // The following timer fires after 0.6 seconds, but everytime when there found an anchor the timer is stopped.
    // So when there is no ARImageAnchor found the timer will be completed and the current scene node will be deleted and the variable will set to nil
    DispatchQueue.main.async {
        if(self.timer != nil){
            self.timer.invalidate()
        }
        self.timer = Timer.scheduledTimer(timeInterval: 0.6 , target: self, selector: #selector(self.imageLost(_:)), userInfo: nil, repeats: false)
    }

    // Check if there is found a new image on the basis of the ARImageAnchorIdentifier, when found delete the current scene node and set the variable to nil
    if(self.currentARImageAnchorIdentifier != imageAnchor.identifier &&
        self.currentARImageAnchorIdentifier != nil
        && self.currentNode != nil){
            //found new image
            self.currentNode!.removeFromParentNode()
            self.currentNode = nil
    }

    updateQueue.async {

        //If currentNode is nil, there is currently no scene node
        if(self.currentNode == nil){

            switch referenceImage.name {
                case "barn":
                    self.scnNodeBarn.transform = node.transform
                    self.sceneView.scene.rootNode.addChildNode(self.scnNodeBarn)
                    self.currentNode = self.scnNodeBarn
                default: break
            }

        }

        self.currentARImageAnchorIdentifier = imageAnchor.identifier

        // Delete anchor from the session to reactivate the image recognition
        self.sceneView.session.remove(anchor: anchor)
    }

}

在计时器完成时删除节点,表明没有找到新的ARImageAnchor。

@objc
    func imageLost(_ sender:Timer){
        self.currentNode!.removeFromParentNode()
        self.currentNode = nil
    }

这样,当覆盖图像或找到新图像时,将删除当前添加的scnNode。

enter image description here

遗憾的是,由于以下原因,此解决方案无法解决图像的定位问题:

  

ARKit不会跟踪每个检测到的图像的位置或方向的变化。

答案 1 :(得分:7)

我认为目前不可能。

来自Recognizing Images in an AR Experience documentation

  

设计您的AR体验,将检测到的图像用作虚拟内容的起点。

     

ARKit不会跟踪每个检测到的图像的位置或方向的变化。如果您尝试将虚拟内容保留在检测到的图像上,则该内容可能无法正确保留。而是使用检测到的图像作为启动动态场景的参照系。

iOS 12.0的新答案

ARKit 2.0和iOS 12最终通过ARImageTrackingConfigurationARWorldTrackingConfiguration.detectionImages属性添加此功能,该属性现在也跟踪图像的位置。

ARImageTrackingConfiguration的Apple文档列出了两种方法的优点:

  

使用ARImageTrackingConfiguration,ARKit不是通过跟踪设备相对于世界的运动来建立3D空间,而是仅通过检测和跟踪相机视图中已知2D图像的运动来建立3D空间。 ARWorldTrackingConfiguration也可以检测图像,但每种配置都有自己的优势:

     
      
  • 世界跟踪的性能成本高于仅图像跟踪,因此您的会话可以使用ARImageTrackingConfiguration一次性可靠地跟踪更多图像。

  •   
  • 仅限图像跟踪可让您将虚拟内容锚定到已知图像,只有当这些图像在相机视图中时。通过图像检测进行世界跟踪,您可以使用已知图像将虚拟内容添加到3D世界,并在图像不再可见时继续跟踪该内容在世界空间中的位置。

  •   
  • 世界追踪在稳定,不动的环境中效果最佳。在更多情况下,您可以使用仅图像跟踪将虚拟内容添加到已知图像 - 例如,移动地铁车厢内的广告。

  •   

答案 2 :(得分:2)

检查ARKit当前是否跟踪的图像的正确方法是将didUpdate节点上ARImageAnchor中的“ isTracked”属性用于锚点功能。

为此,我使用下一个结构:

struct TrackedImage {
    var name : String
    var node : SCNNode?
}

然后是带有所有图像名称的该结构的数组。

var trackedImages : [TrackedImage] = [ TrackedImage(name: "image_1", node: nil) ]

然后在didAdd节点中进行定位,将新内容设置为场景,并将该节点添加到trackedImages数组中的相应元素上

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    // Check if the added anchor is a recognized ARImageAnchor
    if let imageAnchor = anchor as? ARImageAnchor{
        // Get the reference ar image
        let referenceImage = imageAnchor.referenceImage
        // Create a plane to match the detected image.
        let plane = SCNPlane(width: referenceImage.physicalSize.width, height: referenceImage.physicalSize.height)
        plane.firstMaterial?.diffuse.contents = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5)
        // Create SCNNode from the plane
        let planeNode = SCNNode(geometry: plane)
        planeNode.eulerAngles.x = -.pi / 2
        // Add the plane to the scene.
        node.addChildNode(planeNode)
        // Add the node to the tracked images
        for (index, trackedImage) in trackedImages.enumerated(){
            if(trackedImage.name == referenceImage.name){
                trackedImage[index].node = planeNode
            }
        }
    }
}

最后,在didUpdate节点中找到锚点功能,我们在数组中搜索锚点名称,并检查isTracked属性是否为false。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    var trackedImages : [TrackedImage] = [ TrackedImage(name: "image_1", node: nil) ]
    if let imageAnchor = anchor as? ARImageAnchor{
        // Search the corresponding node for the ar image anchor
        for (index, trackedImage) in trackedImages.enumerated(){
            if(trackedImage.name == referenceImage.name){
                // Check if track is lost on ar image
                if(imageAnchor.isTracked){
                    // The image is being tracked
                    trackedImage.node?.isHidden = false // Show or add content
                }else{
                    // The image is lost
                    trackedImage.node?.isHidden = true // Hide or delete content
                }
                break
            }
        }
    }
}

当您想同时跟踪多张图像并知道何时丢失任何图像时,此解决方案有效。

注意:要使此解决方案起作用,必须将AR配置中的maximumNumberOfTrackedImages设置为非零数字。

答案 3 :(得分:0)

我不完全确定我理解了你的要求(如此道歉),但如果我有,那么也许这可能会有所帮助...

似乎insideOfFrustum正常工作,它们必须与节点相关联SCNGeometry以使其工作(仅SCNNode就不够了)。

例如,如果我们在delegate回调中执行类似的操作,并将添加的SCNNode保存到数组中:

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

    //1. If Out Target Image Has Been Detected Than Get The Corresponding Anchor
    guard let currentImageAnchor = anchor as? ARImageAnchor else { return }

    //2. Print The Anchor ID & It's Associated Node
    print("""
        Anchor With ID Has Been Detected \(currentImageAnchor.identifier)
        Associated Node Details = \(node)
        """)


    //3. Store The Node
    imageTargets.append(node)
}

然后使用insideOfFrustum方法,99%的时间它会说节点在视野中,即使我们知道它不应该是。

但是,如果我们做这样的事情(我们创建一个透明的标记节点,例如具有某种几何体的节点):

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

    //1. If Out Target Image Has Been Detected Than Get The Corresponding Anchor
    guard let currentImageAnchor = anchor as? ARImageAnchor else { return }

    //2. Print The Anchor ID & It's Associated Node
    print("""
        Anchor With ID Has Been Detected \(currentImageAnchor.identifier)
        Associated Node Details = \(node)
        """)


    //3. Create A Transpanrent Geometry
    node.geometry = SCNSphere(radius: 0.1)
    node.geometry?.firstMaterial?.diffuse.contents = UIColor.clear

    //3. Store The Node
    imageTargets.append(node)
}

然后调用以下方法,它确实检测ARReferenceImage是否在inView:

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

    //1. Get The Current Point Of View
    guard let pointOfView = augmentedRealityView.pointOfView else { return }

    //2. Loop Through Our Image Target Markers
    for addedNode in imageTargets{

        if  augmentedRealityView.isNode(addedNode, insideFrustumOf: pointOfView){
            print("Node Is Visible")
        }else{
            print("Node Is Not Visible")
        }

    }

}

关于另一个关于SCNNode被另一个被遮挡的观点,Apple Docs

inViewOfFrostrum表示
  

不执行遮挡测试。也就是说,它返回   如果测试节点位于指定的视锥体内,则为true   无论该节点的内容是否被其他节点遮挡   几何形状。

再次,如果我没有正确理解你,请道歉,但希望它在某种程度上可能会有所帮助......

更新:

现在我完全理解你的问题,我同意@orangenkopf的说法,这是不可能的。由于文档说明:

  

ARKit不会跟踪每个位置或方向的变化   检测到的图像。

答案 4 :(得分:0)

来自Recognizing Images in an AR Experience documentation

  

ARKit只为每个会话添加一个图像锚点   会话配置的detectImages数组中的参考图像。   如果您的AR体验在图像时将虚拟内容添加到场景中   如果检测到该操作,默认情况下只会发生一次。允许   用户无需重新启动应用即可再次体验该内容,   调用session的remove(anchor :)方法删除相应的   ARImageAnchor。移除锚点后,ARKit将添加一个新锚点   下次检测到图像时锚定。

所以,也许你可以为你的案例找到一个解决方法:

我们假设我们的结构可以保存我们检测到的ARImageAnchor和相关的虚拟内容:

struct ARImage {
    var anchor: ARImageAnchor
    var node: SCNNode
}

然后,当调用renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)时,将检测到的图像保存到ARImage的临时列表中:

...

var tmpARImages: [ARImage] = []

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

        // If the ARImage does not exist
        if !tmpARImages.contains(where: {$0.anchor.referenceImage.name == referenceImage.name}) {
            let virtualContent = SCNNode(...)
            node.addChildNode(virtualContent)

            tmpARImages.append(ARImage(anchor: imageAnchor, node: virtualContent))
        }


        // Delete anchor from the session to reactivate the image recognition
        sceneView.session.remove(anchor: anchor)    
}

如果您了解,当您的相机的视图指向图像/标记之外时,委托功能将无休止地循环...(因为我们从会话中移除了锚点)。

这个想法是将图像识别循环,保存在tmp列表中的检测到的图像和sceneView.isNode(node, insideFrustumOf: pointOfView)函数结合起来,以确定检测到的图像/标记是否不再被查看。

我希望很清楚......

答案 5 :(得分:0)

仅当您严格水平或垂直握住设备时,此代码才有效。如果您握住iPhone倾斜或开始倾斜,则此代码不起作用:

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

    //1. Get The Current Point Of View
    guard let pointOfView = augmentedRealityView.pointOfView else { return }

    //2. Loop Through Our Image Target Markers
    for addedNode in imageTargets{

        if  augmentedRealityView.isNode(addedNode, insideFrustumOf: pointOfView){
            print("Node Is Visible")
        }else{
            print("Node Is Not Visible")
        }

    }

}

答案 6 :(得分:0)

关于它的价值,我花了几个小时试图弄清楚如何不断检查图像参考。 didUpdate函数就是答案。然后,您只需要使用.isTracked属性测试正在跟踪的参考图像即可。此时,您可以将.isHidden属性设置为true或false。这是我的例子:

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

        if let imageAnchor = anchor as? ARImageAnchor{

        if (imageAnchor.isTracked) {
            trackedNode.isHidden = false
            print("\(trackedNode.name)")

        }else {
            trackedNode.isHidden = true

            //print("\(trackedImageName)")
            print("No image in view")

        }
        }
    }