Могу ли я выполнять непрерывное отслеживание изображений ARKit в конфигурации отслеживания мира с помощью RealityKit?

ОБНОВЛЕНИЕ. Мое предположение о том, что непрерывное отслеживание изображений невозможно из коробки с RealityKit ARViews, было неверным. Все, что мне нужно было сделать, это правильно создать AnchorEntity для постоянно отслеживаемого эталонного изображения.

Якорная сущность должна быть создана с использованием инициализатора init(anchor: ARAnchor). (Инициализатор init(world: SIMD3<Float>) подходит для привязок, привязанных к реальному миру, но не для тех, которые должны отслеживать эталонное изображение.)

Используя ARKit и RealityKit с ARWorldTrackingConfiguration, я пытаюсь выполнять непрерывное отслеживание изображений (где эталонное изображение отслеживается для каждого кадра, и к нему могут быть привязаны виртуальные объекты, которые кажутся прикрепленными к эталонному изображению и перемещаются вместе с ним). Поскольку эталонные изображения распознаются только один раз при мировом отслеживании (в отличие от ARImageTrackingConfiguration, где эталонные изображения отслеживаются постоянно, пока они находятся в кадре), это невозможно из коробки.

Чтобы получить те же результаты в конфигурации отслеживания мира, я привязываю виртуальные объекты к эталонному изображению в методе делегата session(_:didAdd:) и использую метод делегата session(_:didUpdate:) как возможность удалять ARImageAnchor после каждого его определения. Это вызывает повторное распознавание эталонного изображения снова и снова, позволяя виртуальным объектам быть привязанными к изображению и, по-видимому, отслеживать его от кадра к кадру.

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

Это работает. Виртуальный контент отслеживает эталонное изображение в ARWorldTrackingConfiguration так же, как и в конфигурации отслеживания изображений. Но в то время как анимация в ARImageTrackingConfiguration очень плавная, анимация в отслеживании мира гораздо менее плавная, более резкая, как если бы она работала со скоростью 10 или 15 кадров в секунду. (Фактический FPS, по данным .showStatistics, остается около 60 FPS в обеих конфигурациях.)

Я предполагаю, что разница в плавности возникает из-за того, что ARKit выполняет работу по многократному повторному распознаванию и удалению привязки эталонного изображения в каждом цикле didAdd / didUpdate.

Я хотел бы знать, есть ли лучший способ получить непрерывное отслеживание изображений в ARWorldTrackingConfiguration и / или есть ли способ улучшить код в методах делегата для достижения этого эффекта.

import ARKit
import RealityKit

class ViewController: UIViewController, ARSessionDelegate {

    @IBOutlet var arView: ARView!
    
    // originalImageAnchor is used to visualize the first-detected location of reference image
    // currentImageAnchor should be continuously updated to match current position of ref image
    var originalImageAnchor: AnchorEntity!
    var currentImageAnchor: AnchorEntity!
    
    let ballRadius: Float = 0.02

    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources",
             bundle: nil) else { fatalError("Missing expected asset catalog resources.") }
        
        arView.session.delegate = self
        arView.automaticallyConfigureSession = false
        arView.debugOptions = [.showStatistics]
        arView.renderOptions = [.disableCameraGrain, .disableHDR, .disableMotionBlur,
            .disableDepthOfField, .disableFaceOcclusions, .disablePersonOcclusion,
            .disableGroundingShadows, .disableAREnvironmentLighting]

        let configuration = ARWorldTrackingConfiguration()
        configuration.detectionImages = referenceImages
        configuration.maximumNumberOfTrackedImages = 1  // there is one ref image named "coaster_rb"

        arView.session.run(configuration)
    }

    func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        guard let imageAnchor = anchors[0] as? ARImageAnchor else { return }

        // Reference image detected. This will happen multiple times because
        // we delete ARImageAnchor in session(_:didUpdate:)
        if let imageName = imageAnchor.name, imageName  == "coaster_rb" {

            // If originalImageAnchor is nil, create an anchor and
            // add a marker at initial position of reference image.
            if originalImageAnchor == nil {
                originalImageAnchor = AnchorEntity(world: imageAnchor.transform)
                let originalImageMarker = generateBallMarker(radius: ballRadius, color: .systemPink)
                originalImageMarker.position.y = ballRadius + (ballRadius * 2)
                originalImageAnchor.addChild(originalImageMarker)
                arView.scene.addAnchor(originalImageAnchor)
            }
            
            // If currentImageAnchor is nil, add an anchor and marker at reference image position
            // If currentImageAnchor has already been added, adjust it's position to match ref image
            if currentImageAnchor == nil {
                currentImageAnchor = AnchorEntity(world: imageAnchor.transform)
                let currentImageMarker = generateBallMarker(radius: ballRadius, color: .systemTeal)
                currentImageMarker.position.y = ballRadius
                currentImageAnchor.addChild(currentImageMarker)
                arView.scene.addAnchor(currentImageAnchor)
            } else {
                currentImageAnchor.setTransformMatrix(imageAnchor.transform, relativeTo: nil)
            }
        }
    }
    
    func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
        guard let imageAnchor = anchors[0] as? ARImageAnchor else { return }

        // Delete reference image anchor to allow for ongoing tracking as it moves
        if let imageName = imageAnchor.name, imageName  == "coaster_rb" {
            arView.session.remove(anchor: anchors[0])
        }
    }
    
    func generateBallMarker(radius: Float, color: UIColor) -> ModelEntity {
        let ball = ModelEntity(mesh: .generateSphere(radius: radius),
            materials: [SimpleMaterial(color: color, isMetallic: false)])
        return ball
    }
}

person Rick Free    schedule 24.07.2020    source источник


Ответы (1)


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

Неправильная инициализация объекта привязки (для того, что я пытался выполнить):

currentImageAnchor = AnchorEntity(world: imageAnchor.transform)

Поскольку я хотел отслеживать ARImageAnchor, назначенный совпадающему эталонному изображению, я должен был сделать это следующим образом:

currentImageAnchor = AnchorEntity(anchor: imageAnchor)

В приведенном ниже исправленном примере один виртуальный маркер фиксируется в исходной позиции эталонного изображения, а другой, который плавно отслеживает эталонное изображение в конфигурации отслеживания мира:

import ARKit
import RealityKit

class ViewController: UIViewController, ARSessionDelegate {

    @IBOutlet var arView: ARView!
    
    let ballRadius: Float = 0.02

    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let referenceImages = ARReferenceImage.referenceImages(
            inGroupNamed: "AR Resources", bundle: nil) else {
            fatalError("Missing expected asset catalog resources.")
        }
        
        arView.session.delegate = self
        arView.automaticallyConfigureSession = false
        arView.debugOptions = [.showStatistics]
        arView.renderOptions = [.disableCameraGrain, .disableHDR,
            .disableMotionBlur, .disableDepthOfField,
            .disableFaceOcclusions, .disablePersonOcclusion,
            .disableGroundingShadows, .disableAREnvironmentLighting]

        let configuration = ARWorldTrackingConfiguration()
        configuration.detectionImages = referenceImages
        configuration.maximumNumberOfTrackedImages = 1

        arView.session.run(configuration)
    }

    func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        guard let imageAnchor = anchors[0] as? ARImageAnchor else { return }

        if let imageName = imageAnchor.name, imageName  == "target_image" {
            
            // AnchorEntity(world: imageAnchor.transform) results in anchoring
            // virtual content to the real world.  Content anchored like this
            // will remain in position even if the reference image moves.
            let originalImageAnchor = AnchorEntity(world: imageAnchor.transform)
            let originalImageMarker = makeBall(radius: ballRadius, color: .systemPink)
            originalImageMarker.position.y = ballRadius + (ballRadius * 2)
            originalImageAnchor.addChild(originalImageMarker)
            arView.scene.addAnchor(originalImageAnchor)

            // AnchorEntity(anchor: imageAnchor) results in anchoring
            // virtual content to the ARImageAnchor that is attached to the
            // reference image.  Content anchored like this will appear
            // stuck to the reference image.
            let currentImageAnchor = AnchorEntity(anchor: imageAnchor)
            let currentImageMarker = makeBall(radius: ballRadius, color: .systemTeal)
            currentImageMarker.position.y = ballRadius
            currentImageAnchor.addChild(currentImageMarker)
            arView.scene.addAnchor(currentImageAnchor)
        }
    }
    
    func makeBall(radius: Float, color: UIColor) -> ModelEntity {
        let ball = ModelEntity(mesh: .generateSphere(radius: radius),
            materials: [SimpleMaterial(color: color, isMetallic: false)])
        return ball
    }
}
person Rick Free    schedule 31.07.2020