跟踪丢失漂移和恢复策略
问题表现
用户打开 AR 应用后,虚拟物体:
- 突然"飞"到别处或消失
- 随着手机移动一起漂移,不钉在地面上
- 慢慢从正确位置飘走(累积误差)
- 闪一下正确位置,然后继续漂移
这些统称为跟踪质量问题。
跟踪丢失的三种阶段
ARCore 用 TrackingState 表示跟踪质量:
| 状态 | 含义 | 用户能看到的现象 |
|---|---|---|
| TRACKING | 正常 | 虚拟物体稳定不动 |
| PAUSED | 暂时丢失,正在恢复 | 虚拟物体可能轻微漂移或短暂闪烁 |
| STOPPED | 彻底丢失,需要重新初始化 | 虚拟物体消失(或一直飘着),AR 功能不可用 |
各阶段处理策略
// 在每一帧检查所有 Trackable 的状态
fun updateTrackingState() {
session.getAllTrackables<Any>().forEach { trackable ->
when (trackable.trackingState) {
TrackingState.TRACKING -> {
// 正常,继续渲染虚拟物体
}
TrackingState.PAUSED -> {
// 暂时丢失:淡化虚拟物体 + 给用户提示
virtualObject.alpha = 0.5f
showTrackingTip("请缓慢移动手机让我重新识别环境")
}
TrackingState.STOPPED -> {
// 彻底丢失:隐藏虚拟物体 + 引导用户重新开始
virtualObject.alpha = 0f
showRestartTip("跟踪丢失,请重新扫描环境开始")
}
}
}
}
为什么会丢失跟踪
原因一:环境没有足够特征点
纯白墙面 ← 没有特征点 → SLAM 无法构建地图 → 丢失
光滑桌面 ← 特征点少 → 跟踪不稳定
漆黑房间 ← 摄像头无有效数据 → 立刻丢失
解决:引导用户走到特征点丰富的地方("请把手机对准有图案的地面、有纹理的墙壁、或带家具的区域")。
原因二:快速运动
用户快速挥舞手机 → 画面模糊 → 特征点无法跨帧匹配 → 丢失
解决:引导用户缓慢移动——"请慢慢移动手机来扫描周围环境"。
原因三:走出了扫描区域
用户一直把手机对准同一个角落 → SLAM 地图只构建了这一小块 →
用户走出这个区域时 SLAM 对新区域完全未知 → 丢失
解决:引导用户先扫描整个房间——"请先缓慢移动手机扫过整个房间"。这是 AR 应用最经典的引导文案。
原因四:光照条件突变
用户从明亮房间走到暗走廊 → 摄像头突然曝光不足 → SLAM 失准
闪光灯突然打开 → 摄像头过曝 → SLAM 失准
解决:检测到 TrackingState 变化时给用户提示,让他们适应新光照条件。
原因五:动态物体太多
画面中充满走动的人、跑动的狗、行驶的车辆 →
ARCore 的静态世界假设被破坏 → 特征点被动态物体干扰 → 失准
解决:提示用户"AR 效果在静态环境中最好",或让用户避开人群。
累积漂移:怎么办
累积漂移(Accumulated Drift)是指长时间使用后虚拟物体慢慢"飘"了——这不是跟踪丢失,而是 SLAM 在纯本地模式下无法修正全局误差。
普通 SLAM 的累积漂移
纯本地 SLAM 存在经典问题:没有回环(Loop Closure)时,累积误差只会增大。如果用户只在一个有限区域内走动,SLAM 会不断迭代求精;但如果用户走了一圈回到起点,没有回环检测就会产生几米的误差。
解决累积漂移的手段
| 手段 | 原理 | 效果 |
|---|---|---|
| 回环检测 | 当 SLAM 检测到回到已知位置时修正全局地图 | 自动,对用户透明 |
| Cloud Anchors | 解析 Cloud Anchor 时强制对齐到云端地图 | 需网络,精度最高 |
| Geospatial API + VPS | VPS 定期重新定位纠正 SLAM 误差 | 需网络,持续对齐 |
| 定期重置 | 引导用户每隔几分钟重新扫描 | 打扰用户,但有效 |
| 选择锚点策略 | 用 Trackable Anchor(跟随平面)而不是世界坐标 Anchor | 基础缓解 |
Geospatial 定期重定位
// 每 30 秒检查一下 Geospatial 定位精度
private var lastVPSCheck = 0L
private val vpsCheckInterval = 30_000L // 30 秒
fun checkVPSRelocalization() {
val now = System.currentTimeMillis()
if (now - lastVPSCheck < vpsCheckInterval) return
lastVPSCheck = now
if (!session.isGeospatialModeSupported(GeospatialMode.ENABLED)) return
val earthPose = earth.cameraGeospatialPose
if (earthPose.altitudeAccuracy > 5f || earthPose.headingAccuracy > Math.toRadians(20.0)) {
// VPS 精度不够,提示用户
showVPSWarning("位置精度下降,建议到视野开阔处使用")
} else if (earthPose.altitudeAccuracy < 1f) {
// VPS 精度很好,可以考虑用它重新对齐本地坐标
// 这需要重新解析 Geospatial Anchor
}
}
恢复流程设计
一个完整的恢复流程应该是自动检测 → 友好提示 → 引导恢复:
// 1. 监听 TrackingState 变化
// 2. PAUSED 时:淡化虚拟物体 + 显示"请缓慢移动"
// 3. STOPPED 时:隐藏所有 AR 内容 + 显示完整引导界面
// 4. 恢复后:渐变显示虚拟物体 + 不打扰用户
fun onTrackingStateChanged(newState: TrackingState) {
when (newState) {
TrackingState.TRACKING -> {
fadeInVirtualContent() // 恢复显示
if (wasPausedOrStopped) {
showRecoveryToast("跟踪已恢复") // 可选的成功提示
}
}
TrackingState.PAUSED -> {
fadeOutVirtualContent(0.5f)
showMinimalGuide("缓慢移动手机以恢复")
}
TrackingState.STOPPED -> {
hideAllVirtualContent()
showFullRecoveryUI(
title = "跟踪丢失",
tip = "请缓慢移动手机,扫过周围的环境(地面、墙壁、家具)以重新识别"
)
}
}
}
Unity 中的跟踪恢复
using UnityEngine.XR.ARFoundation;
public class TrackingRecovery : MonoBehaviour
{
private ARSession session;
void Start()
{
session = GetComponent<ARSession>();
session.trackingStateChanged += OnTrackingStateChanged;
}
void OnTrackingStateChanged(ARTrackingState state)
{
switch (state)
{
case ARTrackingState.Tracking:
// 正常
break;
case ARTrackingState.NotTracking:
// 丢失
StartCoroutine(RecoverTracking());
break;
}
}
IEnumerator RecoverTracking()
{
// 自动重置 Session 让 SLAM 重新初始化
session.Reset();
// 给用户看引导 UI
recoveryUI.Show();
// 等待用户移动 + SLAM 重新跟踪
yield return new WaitUntil(() => session.trackingState == ARTrackingState.Tracking);
recoveryUI.Hide();
}
}
防丢失的主动策略
除了被动检测和恢复,还可以主动降低丢失概率:
| 策略 | 说明 |
|---|---|
| 首次打开自动扫描引导 | 不要让用户直接进 AR 场景,先引导"扫一下房间" |
| 实时反馈跟踪质量 | 在 AR 画面角落显示一个小指示器:绿=好、黄=警告、红=丢 |
| 光照变化检测 | 亮度突然降低很多时主动提示"光线变暗了,AR 效果可能受影响" |
| 移动速度检测 | IMU 检测到设备快速移动时提示"请放慢一些" |