检查ARReferenceImage是否不再在相机视野内可见。

20
我想检查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)")
    }
}

所以我需要检查图片是否不再可见,而不是图片节点的可见性。但我无法确定这是否可能。第一个截图显示了在找到下面的图片时添加的三个框。当覆盖找到的图片时(参见第二个截图),我想要删除这些框。

Found image with boxes

Convered image with boxes

7个回答

16

我成功解决了问题!借鉴了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!

以下是带有注释的完整代码。

/// - 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不能跟踪每个检测到的图像的位置或方向的更改。


3
在ARKit 2.0中,我们有追踪图像的选项,只需使用“ARImageTrackingConfiguration”并将图像添加到“trackingImages”变量即可。 - Calebe Nunes Pastor
我知道,看到了ARKit 2.0的公告!无论如何还是谢谢。 - KNV
1
这是一个非常棒的答案,应该有更多的赞。我可以在我的项目中完美地使用它。谢谢! - giovannipds
只是一个快速的问题,updateQueue.async 对我显示为“未解决的标识符”,为什么?我可以使用 DispatchQueue.main.async 替代吗? - giovannipds
@KNV 你好,请帮我看一下这个问题,谢谢。https://dev59.com/96zla4cB1Zd3GeqPBb35 - PvDev
1
非常感谢!基于您的想法,我使用协程和Unity ARKit插件成功解决了这个问题。 - Vodenjak

10

我认为目前这是不可能的。

识别 AR 体验中的图像文档

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

ARKit 不跟踪每个检测到的图像的位置或方向的更改。如果您尝试放置始终附着在检测到的图像上的虚拟内容,则该内容可能无法正确地保持位置不变。相反,使用检测到的图像作为动态场景的起点。


iOS 12.0 的新答案

ARKit 2.0 和 iOS 12 最终添加了此功能,可以通过 ARImageTrackingConfiguration 或通过现在还跟踪图像位置的 ARWorldTrackingConfiguration.detectionImages 属性实现。

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

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

  • World tracking 的性能成本比仅图像跟踪更高,因此您可以使用 ARImageTrackingConfiguration 可靠地同时跟踪更多图像。

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

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


谢谢你的回答! - KNV
请注意,仅设置ARWorldTrackingConfiguration.detectionImages无法让您在世界跟踪会话中实时跟踪图像 - 您还需要将maximumNumberOfTrackedImages设置为非零值。 - rickster

6

正确的方法来检查您正在跟踪的图像当前是否未被ARKit跟踪是使用ARImageAnchor上的“isTracked”属性,在anchor函数的didUpdate节点上。

为此,我使用下面的结构:

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

然后是一个包含所有图像名称的该结构体的数组。

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

然后在anchor的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”必须设置为非零数字。

图像锚点的isTracked属性在哪里找到? - BlackMirrorz
@JoshRobbins 它可以在 ARTrackable 协议中找到,因为 ARImageAnchor 符合 ARTrackable 协议,所以你可以在任何 ARImageAnchor 实例上找到它。在这里,你可以了解更多信息:https://developer.apple.com/documentation/arkit/artrackable/2928210-istracked - Abraham Torres
我有一个稍微不同的设置,但成功地使用了didUpdate建议来跟踪图像何时被跟踪或失去其跟踪。基于此,我会点赞这个答案。 - Jamil

5

说实话,我花了好几个小时来研究如何持续检查图像引用。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")

        }
        }
    }

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是否在视图中:

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文档指出inViewOfFrostrum

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

如果我理解有误,请再次道歉,但希望这可以在一定程度上帮助您... 更新: 现在我完全理解了您的问题,我同意@orangenkopf的看法,这是不可能的。因为正如文档所述:

ARKit不跟踪每个检测到的图像的位置或方向的更改。


很遗憾,你没有正确理解我的问题。不用担心,我已经更新了我的问题以使其更加清晰明了。 - KNV
无论如何,还是谢谢你的解释! - KNV

0

来自 AR体验文档中识别图像:

对于会话配置的 detectionImages 数组中的每个参考图像,ARKit 会将一个图像锚点添加到会话中一次。如果您的 AR 经验在检测到图像时向场景中添加虚拟内容,则默认情况下该操作仅会发生一次。为了允许用户在不重启应用程序的情况下重新体验该内容,请调用会话的 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)    
}

如果您理解的话,当您的相机视角超出图像/标记时,委托函数将无限循环... (因为我们从会话中删除了锚点)。

想法是将图像识别循环、检测到的图像保存到临时列表以及sceneView.isNode(node, insideFrustumOf: pointOfView)函数结合起来,以确定检测到的图像/标记是否不再可见。

希望我表达清楚了...


当锚点被移除时,与其相关联的节点也会被移除。由于虚拟内容添加到该特定节点中,因此虚拟内容不再可见。因此,这种方法行不通,或者我没有理解你的意思。 - KNV

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

    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接