ОБНОВЛЕНИЕ. Мое предположение о том, что непрерывное отслеживание изображений невозможно из коробки с 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
}
}