什么是ARAnchor?

27

我正在尝试理解和使用ARKit。但有一件事我无法完全理解。

苹果关于ARAnchor的说法:

一个真实世界的位置和方向,可用于在AR场景中放置物体。

但这并不足够。所以我的问题是:

  • ARAnchor到底是什么?
  • 锚点和特征点之间有什么区别?
  • ARAnchor只是特征点的一部分吗?
  • ARKit如何确定其锚点?
1个回答

86

更新日期:2023年10月15日

简而言之



ARAnchor(iOS)

ARAnchor是一个不可见的可跟踪对象,它在锚点位置上保存了一个3D模型。将ARAnchor视为您的模型的父变换节点,您可以像SceneKit或RealityKit中的任何其他节点一样对其进行平移、旋转和缩放。每个3D模型都有一个枢轴点,对吧?所以,这个枢轴点必须与AR应用中的ARAnchor的位置匹配。

如果您在ARKit或ARCore应用中不使用锚点(但在RealityKit iOS中,不使用锚点是不可能的,因为它们是场景的必要组成部分),您的3D模型可能会偏离其放置的位置,这将极大地影响应用的逼真度和用户体验。因此,锚点是任何AR场景中至关重要的元素。

enter image description here

根据ARKit 2017文档:

ARAnchor是一个真实世界的位置和方向,可用于在AR场景中放置物体。将锚点添加到会话中可以帮助ARKit优化围绕该锚点的区域的世界跟踪精度,使虚拟物体相对于真实世界保持固定位置。如果虚拟物体移动,应从旧位置移除相应的锚点,并在新位置添加一个锚点。

ARAnchor是ARKit中其他10种锚点类型的父类,因此所有这些子类都继承自ARAnchor。通常情况下,您不直接使用ARAnchor。我还必须说ARAnchorFeature Points没有任何共同之处。Feature Points是用于跟踪和调试的特殊视觉元素。

ARAnchor不会自动跟踪真实世界的目标。当您需要自动化时,您必须使用renderer()session()委托方法,这些方法可以在您遵循ARSCNViewDelegateARSessionDelegate协议的情况下实现。

这是一个带有平面锚点的视觉表示的图像。请记住,默认情况下,您既看不到检测到的平面,也看不到其对应的ARPlaneAnchor。因此,如果您想在场景中看到锚点,您必须使用三个细长的SCNCylinder原始体来“可视化”它。每个圆柱体的颜色代表一个特定的轴:RGB即XYZ。


enter image description here


在ARKit中,您可以使用不同的场景自动向您的场景中添加ARAnchors。
  • ARPlaneAnchor

    • 如果水平和/或垂直的planeDetection实例属性为ON,ARKit能够将ARPlaneAnchors添加到正在运行的会话的锚点集合中。有时激活planeDetection会显著增加场景理解阶段所需的时间。
  • ARImageAnchor(符合ARTrackable协议)

    • 这种类型的锚点包含有关检测到的图像的变换的信息 - 锚点放置在图像的中心 - 在世界跟踪或图像跟踪配置中。要激活图像跟踪,请使用detectionImages实例属性。在ARKit 2.0中,您可以完全跟踪多达25个图像,在ARKit 3.0 / 4.0中,分别可以跟踪多达100个图像。但是,在这两种情况下,同时只能跟踪不超过4个图像。然而,承诺在ARKit 5.0 / 6.0中可以同时检测和跟踪多达100个图像(但尚未实现)。
  • ARBodyAnchor(符合ARTrackable协议)

    • 您可以通过基于ARBodyTrackingConfig()运行会话来启用身体跟踪。您将在真实表演者的骨架的根关节或者说在被跟踪角色的骨盆位置处获得ARBodyAnchor。
  • ARFaceAnchor(符合ARTrackable协议)

    • Face Anchor存储有关头部拓扑、姿势和面部表情的信息。您可以使用前置TrueDepth摄像头跟踪ARFaceAnchor。当检测到面部时,Face Anchor将附在鼻子稍后的位置,位于面部的中心。在ARKit 2.0中,您只能跟踪一个面部,在ARKit 3.0及更高版本中,可以同时跟踪多达3个面部。然而,跟踪的面部数量取决于TrueDepth传感器和处理器版本的存在:配备TrueDepth摄像头的设备可以跟踪多达3个面部,配备A12+芯片组但没有TrueDepth摄像头的设备也可以跟踪多达3个面部。
  • ARObjectAnchor

    • 这种锚点类型保存了在世界跟踪会话中检测到的真实3D对象的6自由度(位置和方向)的信息。请记住,您需要为会话配置的detectionObjects属性指定ARReferenceObject实例。
  • AREnvironmentProbeAnchor

    • Probe Anchor为世界跟踪会话中的特定空间区域提供环境光照信息。ARKit的人工智能使用它为反射着色器提供环境反射。
  • ARParticipantAnchor

    • 这是多用户AR体验中不可或缺的锚点类型。如果要使用它,请在ARWorldTrackingConfig的isCollaborationEnabled属性中使用true值。然后导入MultipeerConnectivity框架。
  • ARMeshAnchor

    • ARKit和LiDAR将重建的用户周围的真实世界场景细分为具有相应多边形几何的网格锚点。随着ARKit对真实世界的理解不断完善,网格锚点会不断更新其数据。尽管ARKit会根据物理环境的变化更新网格以反映变化,但网格的后续变化并不打算实时反映。有时,您重建的场景可能有多达30-40个锚点甚至更多。这是因为每个分类对象(墙壁、椅子、门或桌子)都有自己的个人锚点。每个ARMeshAnchor存储有关相应顶点、八种分类情况之一、其面和顶点法线的数据。
  • ARGeoAnchor(符合ARTrackable协议)

    • 在ARKit 4.0+中,有一个地理锚点(也称为位置锚点),它使用GPS、Apple Maps和来自Apple服务器的其他环境数据来跟踪地理位置。这种类型的锚点标识应用程序可以参考的世界中的特定区域。当用户在场景中移动时,会话会根据地理锚点的坐标和设备的指南针方向更新位置锚点的变换。查看支持的城市列表
  • ARAppClipCodeAnchor(符合ARTrackable协议)

    • 在ARKit 4.0+中,这个锚点跟踪物理环境中的App Clip Code的位置和方向。您可以使用App Clip Code使用户在现实世界中发现您的App Clip。有NFC集成的App Clip Code和仅扫描的App Clip Code。

enter image description here


还有其他常规方法可以在AR会话中创建锚点:
  • 点击测试方法

    • 在屏幕上点击,将一个点投射到一个不可见的检测平面上,在这个平面上,通过想象中的射线与平面的交点来放置ARAnchor。顺便说一下,在iOS 16/17中,ARHitTestResult类及其对应的用于ARSCNView和ARSKView的点击测试方法将被完全弃用,所以你必须习惯使用射线投射。
  • 射线投射方法

    • 如果你正在使用射线投射,在屏幕上点击会在一个不可见的检测平面上得到一个投射的3D点。但你也可以在3D场景中的A和B位置之间执行射线投射。因此,射线投射可以是2D到3D和3D到3D的。当使用跟踪射线投射时,ARKit可以在学习更多关于检测到的表面的信息时不断优化射线投射。
  • 特征点

    • ARKit在真实世界对象的高对比度边缘上自动生成的特殊黄色点,可以为你提供放置ARAnchor的位置。
  • AR相机的变换

  • 任意的世界位置

    • 在场景中的任何位置放置一个自定义的ARWorldAnchor。你可以生成ARKit版本的world anchor,就像在RealityKit中找到的AnchorEntity(.world(transform: mtx))一样。


这段代码片段展示了如何在委托方法renderer(_:didAdd:for:)中使用ARPlaneAnchor。
func renderer(_ renderer: SCNSceneRenderer, 
             didAdd node: SCNNode, 
              for anchor: ARAnchor) {
    
    guard let planeAnchor = anchor as? ARPlaneAnchor 
    else { return }

    let grid = Grid(anchor: planeAnchor)
    node.addChildNode(grid)
}


锚点(visionOS)

ARKit 2023的API已经在visionOS上进行了重大修改。主要创新是创建了三个协议(组合优于继承,记住吗?):Anchor,它的子类TrackableAnchorDataProvider。现在有六种类型的visionOS ARKit锚点,它们是结构体而不是类。每种锚点类型都有一个对应的提供者对象,例如:ImageAnchor有一个ImageTrackingProviderHandAnchor有一个HandTrackingProvider,等等。

@available(visionOS 1.0, *)
public struct WorldAnchor : TrackableAnchor, @unchecked Sendable

@available(visionOS 1.0, *)
// The position and orientation of Apple Vision Pro headset.
public struct DeviceAnchor : TrackableAnchor, @unchecked Sendable

@available(visionOS 1.0, *)
public struct PlaneAnchor : Anchor, @unchecked Sendable

@available(visionOS 1.0, *)
public struct HandAnchor : TrackableAnchor, @unchecked Sendable

@available(visionOS 1.0, *)
public struct MeshAnchor : Anchor, @unchecked Sendable

@available(visionOS 1.0, *)
public struct ImageAnchor : TrackableAnchor, @unchecked Sendable

在visionOS下,ARKit中值得注意的创新包括ARKitSession类的出现,以及Pose结构(XYZ位置和XYZ旋转),类似的结构在ARCore开发者手中已经有很长时间了。


AnchorEntity(iOS和visionOS)

AnchorEntity是RealityKit中的关键。根据2019年的RealityKit文档:

AnchorEntity是将虚拟内容与AR会话中的真实对象连接的锚点。

在WWDC'19上宣布了RealityKit框架和Reality Composer应用程序。它们有一个名为AnchorEntity的新类。您可以将AnchorEntity用作任何实体层次结构的根点,并且必须将其添加到场景锚点集合中。AnchorEntity会自动跟踪真实世界目标。在RealityKit和Reality Composer中,AnchorEntity位于层次结构的顶部。这个锚点能够容纳数百个模型,在这种情况下比为每个模型使用100个个人锚点更稳定。

让我们看看代码中的示例:

func makeUIView(context: Context) -> ARView {
    
    let arView = ARView(frame: .zero)
    let modelAnchor = try! Experience.loadModel()
    arView.scene.anchors.append(modelAnchor)
    return arView
}

AnchorEntity有三个组件:

要了解ARAnchorAnchorEntity之间的区别,请查看此帖子

以下是iOS上RealityKit中可用的九个AnchorEntity案例:

// Fixed position in the AR scene
AnchorEntity(.world(transform: mtx)) 

// For body tracking (a.k.a. Motion Capture)
AnchorEntity(.body)

// Pinned to the tracking camera
AnchorEntity(.camera)

// For face tracking (Selfie Camera config)
AnchorEntity(.face)

// For image tracking config
AnchorEntity(.image(group: "GroupName", name: "forModel"))

// For object tracking config
AnchorEntity(.object(group: "GroupName", name: "forObject"))

// For plane detection with surface classification
AnchorEntity(.plane([.any], classification: [.seat], minimumBounds: [1, 1]))

// When you use ray-casting
AnchorEntity(raycastResult: myRaycastResult)

// When you use ARAnchor with a given identifier
AnchorEntity(.anchor(identifier: uuid))

// Creates anchor entity on a basis of ARAnchor
AnchorEntity(anchor: arAnchor) 

在RealityKit for macOS中,只有两种AnchorEntity的情况可用。
// Fixed world position in VR scene
AnchorEntity(.world(transform: mtx))

// Camera transform
AnchorEntity(.camera)

除了上述之外,visionOS还允许您使用另外两个锚点:
// User's hand anchor, taking into account chirality
AnchorEntity(.hand(.right, location: .thumbTip))

// Head anchor
AnchorEntity(.head)

通常,手性一词意味着相对于右侧和左侧缺乏对称性。在RealityKit中,手性是指使用三种情况:.either.left.right。此外,还有五种情况描述锚点的位置:.wrist.palm.thumbTip.indexFingerTip.aboveHand

你可以使用任何ARAnchor的子类来满足AnchorEntity的需求:
var anchor = AnchorEntity()

func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {

    guard let faceAnchor = anchors.first as? ARFaceAnchor 
    else { return }

    arView.session.add(anchor: faceAnchor)           // ARKit Session

    self.anchor = AnchorEntity(anchor: faceAnchor)
    anchor.addChild(model)        
    arView.scene.anchors.append(self.anchor)         // RealityKit Scene
}

RealityKit让你能够重新锚定你的模型。想象一下这样的情景:你开始使用图像或身体跟踪来创建场景,但需要切换到世界跟踪。


iOS Reality Composer的锚点:

目前(2023年6月),iOS Reality Composer只有4种类型的锚点实体:

enter image description here

// 1a
AnchorEntity(plane: .horizontal)

// 1b
AnchorEntity(plane: .vertical)

// 2
AnchorEntity(.image(group: "GroupName", name: "forModel"))

// 3
AnchorEntity(.face)

// 4
AnchorEntity(.object(group: "GroupName", name: "forObject"))


AR USD模式

当然,我应该说几句关于预备锚点的话。对于那些喜欢使用Python脚本创建USDZ模型的人来说,有3种预备锚点类型(2022年7月)- 分别是planeimageface预备锚点。看看这段代码片段,了解如何以Pythonic的方式实现模式。

def Cube "ImageAnchoredBox"(prepend apiSchemas = ["Preliminary_AnchoringAPI"])
{
    uniform token preliminary:anchoring:type = "image"
    rel preliminary: imageAnchoring:referenceImage = <ImageReference>

    def Preliminary_ReferenceImage "ImageReference"
    {
        uniform asset image = @somePicture.jpg@
        uniform double physicalWidth = 45
    }
}

如果你想了解更多关于AR USD模式的信息,请阅读Meduim上的this story


可视化锚点实体

这是一个在RealityKit中可视化锚点的示例(Mac版本)。

import AppKit
import RealityKit

class ViewController: NSViewController {
    
    @IBOutlet var arView: ARView!
    var model = Entity()
    let anchor = AnchorEntity()

    fileprivate func visualAnchor() -> Entity {

        let colors: [SimpleMaterial.Color] = [.red, .green, .blue]

        for index in 0...2 {
            
            let box: MeshResource = .generateBox(size: [0.20, 0.005, 0.005])
            let material = UnlitMaterial(color: colors[index])              
            let entity = ModelEntity(mesh: box, materials: [material])

            if index == 0 {
                entity.position.x += 0.1

            } else if index == 1 {
                entity.transform = Transform(pitch: 0, yaw: 0, roll: .pi/2)
                entity.position.y += 0.1

            } else if index == 2 {
                entity.transform = Transform(pitch: 0, yaw: -.pi/2, roll: 0)
                entity.position.z += 0.1
            }
            model.scale *= 1.5
            self.model.addChild(entity)
        }
        return self.model
    }

    override func awakeFromNib() {
        anchor.addChild(self.visualAnchor())
        arView.scene.addAnchor(anchor)
    }
}

enter image description here


关于ARCore中的ArAnchors

在我的帖子末尾,我想谈谈在ARCore 1.40+中使用的五种锚点类型。谷歌的官方文档对锚点的定义如下:“ArAnchor描述了现实世界中的固定位置和方向”。ARCore的锚点在许多方面与ARKit的锚点类似。

让我们来看看ArAnchors的类型:

  • 本地锚点

    • 存储在应用程序本地,并且仅对该应用程序实例有效。用户必须身处放置锚点的位置。锚点可以附加到可跟踪对象或ARCore会话
  • 云锚点

    • 存储在Google Cloud中,可以在应用程序实例之间共享。用户必须身处放置锚点的位置。云锚点是托管在云端的锚点(借助于“持久云锚点API”),您可以创建一个在创建后可解析1到365天的云锚点。它们可以被多个用户解析,以建立用户和设备之间的共同参考框架。
  • 地理空间锚点

    • 基于大地测量学纬度、经度和海拔,以及Google的“视觉定位系统”数据,几乎可以在世界上任何地方提供精确的位置。这些锚点可以在应用程序实例之间共享。只要应用程序连接到互联网并能够使用VPS,用户可以从远程位置放置锚点。
  • 地形锚点

    • 是地理空间锚点的一个子类型,允许您仅使用纬度和经度放置AR对象,利用Google地图的信息自动找到地面上的精确高度。
  • 屋顶锚点

    • 是Streetscape Geometry API和场景语义API的一部分,提供场景中建筑物和地形的几何形状。这些几何形状可以用于遮挡、渲染或通过命中测试放置AR内容(最多100米)。Streetscape Geometry数据是通过Google街景图像获取的。顾名思义,屋顶锚点放置在屋顶上。

这些Kotlin代码片段展示了如何使用地理空间锚点和屋顶锚点。
地理空间锚点
fun configureSession(session: Session) {
    session.configure(
        session.config.apply {
            geospatialMode = Config.GeospatialMode.ENABLED
        }
    )
}

val earth = session?.earth ?: return

if (earth.trackingState != TrackingState.TRACKING) { return }

earthAnchor?.detach()

val altitude = earth.cameraGeospatialPose.altitude - 1
val qx = 0f; val qy = 0f; val qz = 0f; val qw = 1f

earthAnchor = earth.createAnchor(latLng.latitude, 
                                 latLng.longitude, 
                                 altitude, 
                                 qx, qy, qz, qw)

屋顶锚点
streetscapeGeometryMode = Config.StreetscapeGeometryMode.ENABLED
val streetscapeGeo = session.getAllTrackables(StreetscapeGeometry::class.java)
streetscapeGeometryRenderer.render(render, streetscapeGeo)

val centerHits = frame.hitTest(centerCoords[0], centerCoords[1])

val hit = centerHits.firstOrNull {
    val trackable = it.trackable
    trackable is StreetscapeGeometry && 
                 trackable.type == StreetscapeGeometry.Type.BUILDING
} ?: return

val transformedPose = ObjectPlacementHelper.createStarPose(hit.hitPose)
val anchor = hit.trackable.createAnchor(transformedPose)
starAnchors.add(anchor)

val earth = session?.earth ?: return
val geospatialPose = earth.getGeospatialPose(transformedPose)

earth.resolveAnchorOnRooftopAsync(geospatialPose.latitude, 
                                  geospatialPose.longitude,
                                  0.0,
                                  transformedPose.qx(), 
                                  transformedPose.qy(), 
                                  transformedPose.qz(), 
                                  transformedPose.qw() ) { anchor, state ->

    if (!state.isError) {
        balloonAnchors.add(anchor)
    }
}

1
我们能否覆盖ArPlaneAnchor的extent属性?是否可以为检测到的平面锚点创建无限平面? - Roshan Bade
1
安迪:太棒了!你在哪里找到这些东西的?有没有教程页面可以学习这些东西。我有一个快速问题,如何自动检测现实世界中的物体,比如手提箱? - raja pateriya
2
嗨@rajapateriya,请将其发布为SO问题。附言:我把所有这些东西都记在脑海中)). 目前没有太多优秀的学习资源。这是因为ARKit还太年轻了。顺便说一下,尝试阅读raywenderlich's的ARKit书籍。 - Andy Jazz
1
嘿@AndyFedoroff,非常感谢您详细的回答!我和我的团队陷入了一个问题几天啊 :/ 我发布了一个有趣的问题:https://stackoverflow.com/questions/63662318/arkit-apply-filter-cifilter-to-a-specific-part-vertex-of-an-arfaceanchor 希望能得到您的建议! - Roi Mulia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接