Проверьте, не виден ли 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), я бы хотел снять флажки.

Найдено изображение с коробками

Конвертированное изображение с коробками

5 ответов

Решение

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

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

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

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

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


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

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

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

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

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

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

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

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

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

        }
        }
    }

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

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

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

    }

}

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

Аркыт добавляет якорь изображения на сессию только один раз для каждого опорного изображения в 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) Функция для определения, если обнаруженное изображение / маркер больше не отображается.

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

Другие вопросы по тегам