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

Viro React图像跟踪异常:目标移出相机后AR图像未消失

React Native AR图像跟踪问题:目标移出视野后叠加图像未及时消失

我正在使用Viro React开发一款React Native AR应用,需求是实现图像跟踪功能,在识别到的目标图像上方显示另一张图像。目前遇到的问题是:有时当目标图像移出相机视野后,叠加的AR图像仍会停留在屏幕上,只有移动相机位置后,该AR图像才会消失。

package.json配置

{
  "name": "ToothARcademy",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "lint": "eslint .",
    "start": "react-native start",
    "test": "jest"
  },
  "dependencies": {
    "@expo/vector-icons": "^14.0.2",
    "@react-native-vector-icons/fontawesome": "^4.7.0-alpha.24",
    "@react-native-vector-icons/fontawesome6": "^6.6.0-alpha.27",
    "@react-navigation/bottom-tabs": "^6.6.1",
    "@react-navigation/native": "^6.1.18",
    "@react-navigation/native-stack": "^6.11.0",
    "@react-navigation/stack": "^6.4.1",
    "@reactvision/react-viro": "^2.41.4",
    "expo-font": "^12.0.9",
    "react": "18.2.0",
    "react-native": "0.73.3",
    "react-native-fs": "^2.20.0",
    "react-native-gesture-handler": "^2.18.1",
    "react-native-progress": "^5.0.1",
    "react-native-reanimated": "^3.15.0",
    "react-native-safe-area-context": "^4.10.8",
    "react-native-screens": "^3.34.0",
    "react-native-vector-icons": "^10.1.0",
    "react-native-zip-archive": "^7.0.1",
    "rn-fetch-blob": "^0.12.0"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@babel/preset-env": "^7.20.0",
    "@babel/runtime": "^7.20.0",
    "@react-native/babel-preset": "0.73.20",
    "@react-native/eslint-config": "0.73.2",
    "@react-native/metro-config": "0.73.4",
    "@react-native/typescript-config": "0.73.1",
    "@types/react": "^18.3.3",
    "@types/react-native": "^0.72.8",
    "@types/react-native-vector-icons": "^6.4.18",
    "@types/react-test-renderer": "^18.0.0",
    "babel-jest": "^29.6.3",
    "eslint": "^8.19.0",
    "jest": "^29.6.3",
    "prettier": "2.8.8",
    "react-test-renderer": "18.2.0",
    "typescript": "^5.5.4"
  },
  "engines": {
    "node": ">=18"
  }
}

ARExplorer.tsx代码

import React, { useEffect, useState } from "react";
import { ViroARScene, ViroARTrackingTargets, ViroARImageMarker, ViroImage, ViroAnimations, ViroTrackingStateConstants } from "@reactvision/react-viro";
import RNFS from 'react-native-fs';
import {DETAIL_PATHS,DIRECTORY_PATHS } from '../Statics/static-data'

// Dynamically initialize AR assets
const initARAssets = async () => {
  try {
    console.log("Creating AR tracking targets dynamically");

    const documentDir = RNFS.DocumentDirectoryPath;
    const learnDir = DETAIL_PATHS;
    const files = await RNFS.readDir(DIRECTORY_PATHS.LEARN_IMAGES);

    const targets: { [key: string]: any } = {};
    
    files.forEach(file => {
      console.log(file.path);
      // Identify target images by checking if the name ends with any image extension
      const targetMatch = file.name.match(/^Learn_Image(\d+)(\.[a-zA-Z]+)$/);
      if (targetMatch) {
        const key = `Learn_Image${targetMatch[1]}`;
        targets[key] = {
          source: { uri:'file://'+ file.path },
          orientation: "Up",
          physicalWidth: 0.165,
        };
      }
    });

    ViroARTrackingTargets.createTargets(targets);

    console.log("AR tracking targets created:", targets);

    // ViroAnimations.registerAnimations({
    //   scaleUp: {
    //     properties: { scaleX: 1, scaleY: 1, scaleZ: 1 },
    //     duration: 500,
    //     easing: "bounce",
    //   },
    //   scaleDown: {
    //     properties: { scaleX: 0, scaleY: 0, scaleZ: 0 },
    //     duration: 200,
    //   },
    //   scaleButton: {
    //     properties: { scaleX: 0.09, scaleY: 0.09, scaleZ: 0.09 },
    //     duration: 500,
    //     easing: "bounce",
    //   },
    //   scaleSphereUp: {
    //     properties: { scaleX: 0.8, scaleY: 0.8, scaleZ: 0.8 },
    //     duration: 50,
    //     easing: "easeineaseout",
    //   },
    //   scaleSphereDown: {
    //     properties: { scaleX: 1, scaleY: 1, scaleZ: 1 },
    //     duration: 50,
    //     easing: "easeineaseout",
    //   },
    // });
  } catch (error) {
    console.error('Error initializing AR assets:', error);
  }
};

const ARExplorer = () => {
  const [activeMarker, setActiveMarker] = useState<string | null>(null); // Store the active marker's key
  const [imageSources, setImageSources] = useState<{ [key: string]: string }>({});

  useEffect(() => {
    initARAssets();
    loadImagesFromDocumentDir();
  }, []);

  const loadImagesFromDocumentDir = async () => {
    try {
      const files = await RNFS.readDir(DIRECTORY_PATHS.LEARN_IMAGES);

      const sources: { [key: string]: string } = {};
      files.forEach(file => {
        const annotedMatch = file.name.match(/^Learn_Image(\d+)_Annotated(\.[a-zA-Z]+)$/);
        if (annotedMatch) {
          const key = `Learn_Image${annotedMatch[1]}`;
          sources[key] = file.path;
        }
      });

      setImageSources(sources);
    } catch (error) {
      console.error('Error loading images from document directory:', error);
    }
  };

  const onTrackingUpdated = (state: any) => {
    if (state === ViroTrackingStateConstants.TRACKING_UNAVAILABLE) {
      setActiveMarker(null); // Reset the active marker if tracking is unavailable
    }
  };

  const handleAnchorFound = (markerId: string) => {
    if (activeMarker !== markerId) {
      console.log("Anchor found for:", markerId);

      // Clear the previous marker
      setActiveMarker(null);

      // Allow time for the state to clear
      setTimeout(() => {
        setActiveMarker(markerId); // Set the new marker as active
      }, 100);
    }
    else{
      setActiveMarker(markerId);
    }
  };

  const handleAnchorRemoved = (markerId: string) => {
    if (activeMarker === markerId) {
      console.log("Anchor removed for:", markerId);
      setActiveMarker(null); // Reset the active marker when it is removed
    }
  };

  const handleAnchorNotViewed = (markerId: string) => {
    if (activeMarker === markerId) {
      console.log("Anchor no longer viewed:", markerId);
      setActiveMarker(null); // Reset the active marker when it is no longer viewed
    }
  };

  return (
    <ViroARScene onTrackingUpdated={onTrackingUpdated}>
      {Object.keys(imageSources).map(key => (
        <ViroARImageMarker
          key={key}
          target={key}
          onAnchorFound={() => handleAnchorFound(key)}
          onAnchorRemoved={() => handleAnchorRemoved(key)}
          onAnchorUpdated={(anchor) => {
            if (anchor.trackingMethod === "tracking") {
              handleAnchorFound(key);
            } else if (anchor.trackingMethod === "lastKnownPose") {
              handleAnchorNotViewed(key);
            }
          }}
        >
          {activeMarker === key && imageSources[key] && (
            <ViroImage
              source={{ uri: 'file://' + imageSources[key] }}
              scale={[0.18, 0.18, -0.7]}
              rotation={[-90, 0, 0]}
            />
          )}
        </ViroARImageMarker>
      ))}
    </ViroARScene>
  );
};

export default ARExplorer;

问题分析与解决方案

问题根源

  1. Viro的onAnchorRemoved和lastKnownPose回调触发存在延迟,当目标快速移出视野时,无法及时通知状态变更。
  2. 切换标记时的setTimeout延迟会加剧状态更新滞后,导致图像残留。
  3. 全局onTrackingUpdated仅处理整体跟踪失效,无法覆盖单个标记的跟踪丢失场景。

修复方案

1. 移除状态更新延迟

删除切换标记时的setTimeout,直接更新activeMarker,避免状态滞后:

const handleAnchorFound = (markerId: string) => {
  if (activeMarker !== markerId) {
    console.log("Anchor found for:", markerId);
    setActiveMarker(markerId);
  }
};

2. 优化跟踪状态判断

在onAnchorUpdated中直接通过anchor.isTracking判断跟踪状态,及时隐藏图像:

const handleAnchorLost = (markerId: string) => {
  if (activeMarker === markerId) {
    console.log("Anchor lost for:", markerId);
    setActiveMarker(null);
  }
};

// 组件内的ViroARImageMarker部分
<ViroARImageMarker
  key={key}
  target={key}
  onAnchorFound={() => handleAnchorFound(key)}
  onAnchorRemoved={() => handleAnchorLost(key)}
  onAnchorUpdated={(anchor) => {
    if (anchor.isTracking) {
      handleAnchorFound(key);
    } else {
      handleAnchorLost(key);
    }
  }}
>
  {/* 子组件内容 */}
</ViroARImageMarker>

3. 额外优化建议

  • 升级@reactvision/react-viro到最新稳定版,修复已知的跟踪回调延迟问题。
  • 确保目标图像的physicalWidth设置准确,不准确的物理尺寸会导致跟踪不稳定。
  • 可添加短时间防抖处理(如100ms),避免跟踪状态频繁切换导致图像闪烁,但不要过度延迟状态更新。

内容的提问来源于stack exchange,提问作者safiqul islam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 03:18:10