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

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

找到带框的图像

带框的转换图像

4

7 回答 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 不会跟踪每个检测到的图像的位置或方向的变化。

于 2018-05-03T09:12:45.223 回答
10

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

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

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

ARKit 不会跟踪每个检测到的图像的位置或方向的变化。如果您尝试放置始终附加到检测到的图像的虚拟内容,则该内容可能无法正确保留在原位。相反,使用检测到的图像作为开始动态场景的参考框架。


iOS 12.0 的新答案

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

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

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

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

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

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

于 2018-04-24T10:14:38.557 回答
6

检查您正在跟踪的图像是否当前未被 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
            }
        }
    }
}

当您想要同时跟踪多个图像并知道其中任何一个丢失时,此解决方案就可以工作。

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

于 2018-08-07T04:07:03.697 回答
2

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

        }
        }
    }
于 2019-07-20T21:45:00.197 回答
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")
        }

    }

}
于 2018-12-10T03:39:54.610 回答
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 被另一个 SCNNode 遮挡的另一点,以下Apple Docs状态inViewOfFrostrum

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

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

更新:

现在我完全理解你的问题,我同意@orangenkopf 的观点,这是不可能的。由于文档状态:

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

于 2018-04-24T10:57:48.527 回答
0

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

ARKit 为会话配置的 detectionImages 数组中的每个参考图像添加一个图像锚点到会话中。如果您的 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)    
}

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

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

我希望它很清楚...

于 2018-04-25T16:25:40.627 回答