检查 ARReferenceImage 在相机视图中是不是不再可见

Posted

技术标签:

【中文标题】检查 ARReferenceImage 在相机视图中是不是不再可见【英文标题】:Check whether the ARReferenceImage is no longer visible in the camera's view检查 ARReferenceImage 在相机视图中是否不再可见 【发布时间】:2018-10-04 10:33:55 【问题描述】:

我想检查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),我想删除这些框。

【问题讨论】:

【参考方案1】:

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

// 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!

下面是带有cmets的完整代码。

/// - 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 会在图片被覆盖或发现新图片时被删除。

不幸的是,这个解决方案并没有解决图像的定位问题,原因如下:

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

【讨论】:

在 ARKit 2.0 中,我们可以选择跟踪图像,您只需使用 ARImageTrackingConfiguration 并将图像添加到 trackingImages 变量中。 我知道,看到了 ARKit 2.0 的公告!还是谢谢 这么棒的答案,应该有更多的赞成票。我可以在我的项目中完美地使用它。谢谢! 只是一个简单的问题,updateQueue.async 正在向我显示use of unresolved identifier,为什么?我可以改用DispatchQueue.main,async 吗? 非常感谢!根据您的想法,我设法通过使用协程的 Unity ARKit 插件解决了这个问题。【参考方案2】:

我认为目前不可能。

来自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 图像的运动。 ARWorldTrackingConfiguration 也可以检测图像,但每种配置都有自己的优势:

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

仅图像跟踪允许您将虚拟内容锚定到已知图像,仅当这些图像在相机视野中时。带有图像检测的世界跟踪功能让您可以使用已知图像将虚拟内容添加到 3D 世界中,并且即使在图像不再可见后也可以继续跟踪该内容在世界空间中的位置。

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

【讨论】:

感谢您的回答! 请注意,不仅仅是ARWorldTrackingConfiguration.detectionImages 可以让您实时跟踪图像(在世界跟踪会话中)——您还需要将maximumNumberOfTrackedImages 设置为非零值。【参考方案3】:

检查您正在跟踪的图像是否当前未被 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 必须设置为非零数字。

【讨论】:

imageAnchor.isTracked的属性在哪里找到? @JoshRobbins 可以在 ARTrackable 协议上找到,因为 ARImageAnchor 符合 ARTrackable 协议,您可以在任何 ARImageAnchor 实例上找到它。在这里您可以找到更多相关信息:developer.apple.com/documentation/arkit/artrackable/… 有趣 :) 谢谢你的信息 :) 会玩的 :) 我的设置略有不同,但设法使用 didUpdate 建议来跟踪图像何时被跟踪或丢失跟踪。在此基础上支持答案。【参考方案4】:

为了它的价值,我花了几个小时试图弄清楚如何不断检查图像参考。 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")

        
        
    

【讨论】:

【参考方案5】:

我不完全确定我是否理解您的要求(非常抱歉),但如果我理解了,那么也许这可能会有所帮助...

看来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 被另一个 SCNNode 遮挡的另一点,Apple Docs 声明inViewOfFrostrum

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

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

更新:

现在我完全理解您的问题,我同意@orangenkopf 的观点,即这是不可能的。正如文档所述:

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

【讨论】:

很遗憾,您理解我的问题不正确。不用担心,我已经更新了我的问题,以便更清楚。 感谢您的解释!【参考方案6】:

来自Recognizing Images in an AR Experience documentation:

ARKit 为每个会话仅添加一次图像锚点 会话配置的 detectionImages 数组中的参考图像。 如果您的 AR 体验在图像中添加虚拟内容到场景中 被检测到,默认情况下该动作只会发生一次。允许 用户无需重新启动您的应用即可再次体验该内容, 调用会话的 remove(anchor:) 方法来移除对应的 ARImageAnchor。移除anchor后,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) 函数来确定检测到的图像/标记是否不再可见。

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

【讨论】:

当锚点被移除时,与之关联的节点也被移除。由于 virtualContent 已添加到该特定节点,因此 virtualContent 不再可见。所以这种方法行不通或者我不理解你。【参考方案7】:

仅当您严格水平或垂直握住设备时,此代码才有效。如果您将 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")
        

    


【讨论】:

以上是关于检查 ARReferenceImage 在相机视图中是不是不再可见的主要内容,如果未能解决你的问题,请参考以下文章

在 iOS 11.3 beta 2 中将 ARReferenceImage 与 ARKit 一起使用会导致“错误:运行 actool 时出现异常:”

检查保存到相机胶卷是否成功

相机视图 SurfaceView 仅以横向存储

Unity3d检查用户是不是拥有授权设备相机

从相机单击后在 tableView 单元中添加图像

iOS 应用程序不会返回到同一个视图控制器