You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ArCore图像跟踪与锚点稳定性优化及全局锚点实现咨询

增强图像锚点稳定性优化(固定图像场景)

一、全局锚点(Global Anchor)实现步骤

针对固定不动的目标图像(如墙上海报),Global Anchor可将锚点持久化到ARCore空间地图中,规避增强图像跟踪波动导致的锚点偏移,具体实现如下:

  1. 开启Cloud Anchor支持
    在Session配置阶段,先确认设备支持并启用Cloud Anchor功能:
// 在onSessionConfiguration方法中添加
if (session!!.isSupported(Config.Feature.CLOUD_ANCHOR)) {
    config!!.enableFeature(Config.Feature.CLOUD_ANCHOR)
}
  1. 创建并托管Global Anchor
    当增强图像进入FULL_TRACKING状态后,基于图像中心Pose创建Cloud Anchor,托管成功后替换原锚点:
// 修改attachVideo方法中创建AnchorNode的逻辑
anchorNode = AnchorNode(augmentedImage.createAnchor(augmentedImage.centerPose))
// 托管Cloud Anchor
val cloudAnchorFuture = session!!.hostCloudAnchor(anchorNode!!.anchor)
cloudAnchorFuture.addOnSuccessListener { cloudAnchor ->
    // 替换原锚点为全局锚点
    anchorNode!!.anchor = cloudAnchor
    Log.d("ttt", "全局锚点托管成功")
}.addOnFailureListener { e ->
    Log.e("ttt", "全局锚点托管失败: ${e.message}")
}
  1. 复用已托管的全局锚点
    若同一图像再次被识别,可直接加载已保存的锚点ID,避免重复托管:
// 示例:加载已存储的全局锚点ID
val savedAnchorId = "xxx-xxx-xxx"
val resolveFuture = session!!.resolveCloudAnchor(savedAnchorId)
resolveFuture.addOnSuccessListener { resolvedAnchor ->
    anchorNode = AnchorNode(resolvedAnchor)
    arFragment!!.arSceneView.scene.addChild(anchorNode)
    // 后续绑定视频渲染逻辑
}

二、额外跟踪优化技巧

除全局锚点外,还可通过以下手段进一步提升稳定性:

  • 优化增强图像数据库

    • 确保入库图像分辨率≥1024x1024,且包含丰富纹理细节(避免纯色、低对比度图像)
    • 调用addImage时,精准设置图像实际物理尺寸(你的代码中已设置0.25f,需确保与真实尺寸一致)
  • 精细化跟踪状态处理

    • 仅在TrackingState.TRACKING且TrackingMethod.FULL_TRACKING状态下创建锚点,排除LAST_KNOWN_POSE状态的不稳定锚点
    • 增加防抖逻辑:连续3帧检测到FULL_TRACKING再创建锚点,避免短暂识别导致的错位
  • 调整AnchorNode平滑参数
    在开启isSmoothed = true基础上,细化平滑配置:

    anchorNode!!.smoothPosition = true
    anchorNode!!.smoothRotation = true
    anchorNode!!.smoothScale = true
    anchorNode!!.smoothingFactor = 0.1f // 系数越小越平滑,延迟越高,按需调整
    
  • 优化渲染逻辑

    • 提前预加载视频渲染资源,避免在onFrameAvailableListener中执行复杂创建逻辑,减少主线程阻塞
    • 确保视频Cube的extentX/extentZ与增强图像尺寸完全匹配,消除视觉错位

用户原始代码

override fun onSessionConfiguration(session: Session?, config: Config?) {

    Log.d("ttt","onSessionConfiguration")
    this.session = session
    // Disable plane detection
    config!!.planeFindingMode = Config.PlaneFindingMode.DISABLED

    Thread().run {

        database = AugmentedImageDatabase(session).also {
            for(fileName in requireContext().fileList().filter { it.contains("image.jpg") }) {
                val file = File(requireContext().filesDir,fileName)
                if(file.exists()){
                    it.addImage(fileName.toString().split("-")[0],BitmapFactory.decodeFile(file.absolutePath),0.25f)
                }
            }
        }

        config.setAugmentedImageDatabase(database)
    }

    arFragment?.setOnAugmentedImageUpdateListener { it->
        run {
            onAugmentedImageTrackingUpdate(it)
        }
    }
}

private fun onAugmentedImageTrackingUpdate(augmentedImage: AugmentedImage) {
    attachVideo(augmentedImage)
}

private fun attachVideo(augmentedImage: AugmentedImage){
    if (augmentedImage.trackingState == TrackingState.TRACKING
        && augmentedImage.trackingMethod == AugmentedImage.TrackingMethod.FULL_TRACKING){

        if (!matrixDetected) {
            matrixDetected = true
            lastTracketedImageName = augmentedImage.name

            anchorNode = AnchorNode(augmentedImage.createAnchor(augmentedImage.centerPose))
            anchorNode!!.isSmoothed = true

            texture = ExternalTexture().also {
                it.surfaceTexture.setOnFrameAvailableListener {
                    texture!!.surfaceTexture.setOnFrameAvailableListener(null)

                    val model = ShapeFactory.makeCube(
                        Vector3(augmentedImage.extentX,0f,augmentedImage.extentZ),
                        Vector3(0f,0f,0f),
                        plainVideoMaterial
                    )

                    model.isShadowReceiver = false
                    model.isShadowCaster = false

                    val renderable  = anchorNode!!.setRenderable(model)
                    renderable.material!!.setExternalTexture("videoTexture",texture)

                    fadeInVideo(renderable.material)
                }
            }

            val videoFile = File(requireContext().filesDir,"${augmentedImage.name}-video.mp4")

            mediaPlayer = MediaPlayer.create(requireContext(),Uri.fromFile(videoFile)).also {
                it.isLooping = true
                it.setSurface(texture!!.surface)
                it.start()
            }

            arFragment!!.arSceneView.scene.addChild(anchorNode)
        }
    }else{
        if(matrixDetected){
            if(anchorNode != null){
                arFragment!!.arSceneView.scene.removeChild(anchorNode!!)
                anchorNode = null
            }

            viewLifecycleOwner.lifecycleScope.launch {
                val result = viewModel.endArVideo(deviceID,augmentedImage.name)
                if(result.isSuccessful){
                    Log.d("ttt","stop ar request success")
                }else{
                    Log.d("ttt","stop ar request failed ${result.errorBody()}")
                }
            }

            matrixDetected = false
        }

        mediaPlayer?.let {
            it.setSurface(null)
            it.stop()
            it.release()
            mediaPlayer = null
        }
    }
}

private fun fadeInVideo(material: Material) {
    ValueAnimator.ofFloat(0f, 1f).apply {
        duration = 500L
        interpolator = LinearInterpolator()
        addUpdateListener { v ->
            material.setFloat("videoAlpha", v.animatedValue as Float)
        }
    }.start()
}

内容的提问来源于stack exchange,提问作者Yousef Aiman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 22:18:21