Убедитесь, что ARReferenceImage больше не отображается в режиме просмотра камеры

Я хотел бы проверить, не видна ли 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

Ответы

Ответ 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!

Полный код с комментариями ниже.

/// - 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 не отслеживает изменения положения или ориентации каждого обнаруженного изображения.

Ответ 2

Я не думаю, что в настоящее время это возможно.

Из документа " Распознавание изображений в AR Experience":

Создайте свой опыт AR для использования обнаруженных изображений в качестве отправной точки для виртуального контента.

ARKit не отслеживает изменения положения или ориентации каждого обнаруженного изображения. Если вы попытаетесь разместить виртуальный контент, который остается прикрепленным к обнаруженному изображению, этот контент может не отображаться правильно. Вместо этого, использование обнаружено изображения в качестве системы отсчета для начала динамической сцены.


Новый ответ для iOS 12.0

ARKit 2.0 и iOS 12, наконец, добавляют эту функцию либо через ARImageTrackingConfiguration либо через свойство ARWorldTrackingConfiguration.detectionImages которое теперь также отслеживает положение изображений.

Документация Apple к ARImageTrackingConfiguration перечисляет преимущества обоих методов:

С ARImageTrackingConfiguration ARKit устанавливает 3D-пространство не путем отслеживания движения устройства по отношению к миру, а исключительно путем обнаружения и отслеживания движения известных 2D-изображений с учетом камеры. ARWorldTrackingConfiguration также может обнаруживать изображения, но каждая конфигурация имеет свои сильные стороны:

  • Мировое отслеживание имеет более высокую производительность, чем отслеживание только изображений, поэтому ваш сеанс может надежно отслеживать больше изображений одновременно с ARImageTrackingConfiguration.

  • Отслеживание только изображений позволяет привязывать виртуальный контент к известным изображениям только тогда, когда эти изображения видны с камеры. Мировое отслеживание с обнаружением изображений позволяет использовать известные изображения для добавления виртуального контента в мир 3D и продолжает отслеживать положение этого контента в мировом пространстве даже после того, как изображение больше не отображается.

  • Мировое отслеживание работает лучше всего в стабильной, неподвижной среде. Вы можете использовать отслеживание только изображений для добавления виртуального контента к известным изображениям в других ситуациях - например, рекламу внутри движущегося автомобиля метро.

Ответ 3

Правильный способ проверить, отслеживается ли отслеживаемое вами изображение в данный момент ARKit, - использовать свойство isTracked в ARImageAnchor на узле didUpdate для функции привязки.

Для этого я использую следующую структуру:

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 должно быть установлено ненулевое число.

Ответ 4

Я не совсем уверен, что понял, что вы просите (извините), но если у меня есть, возможно, это может помочь...

Кажется, что для insideOfFrustum работать правильно, что их должна быть какая-то SCNGeometry связанная с узлом для ее работы (одного SCNNode недостаточно).

Например, если мы сделаем что-то подобное в 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 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 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 не отслеживает изменения положения или ориентации каждого обнаруженного изображения.

Ответ 5

Из документа " Распознавание изображений в AR Experience":

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

Если вы поняли, что в то время, как точка вашего камеры указывает на изображение/маркер, функция делегата будет бесконечно зацикливаться... (потому что мы удалили якорь из сеанса).

Идея состоит в объединении цикла распознавания изображений, обнаруженного изображения, сохраненного в списке tmp, и функции sceneView.isNode(node, insideFrustumOf: pointOfView) чтобы определить, не обнаружено ли обнаруженное изображение/маркер.

Надеюсь, это было ясно...

Ответ 6

Этот код работает, только если вы держите устройство строго горизонтально или вертикально. Если вы держите 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")
        }

    }

}

Ответ 7

Для чего это стоило, я потратил часы, пытаясь понять, как постоянно проверять ссылки на изображения. Функция 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")

        }
        }
    }