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

Android Studio ARCore多图跟踪锚点未移除致卡顿崩溃问题求解

ARCore多图像跟踪功能异常修复方案

问题说明

现有基于ARCore实现的多图像跟踪功能可完成基础识别渲染,但存在两类异常:

  • 摄像头移出标记识别范围后,对应Anchor未被自动移除,场景内残留无效渲染内容
  • 扫描识别超过2个标记时出现明显性能卡顿,最终触发应用崩溃
    此前尝试在AugmentedImage的TrackingState为LAST_KNOWN_POSE状态时执行锚点分离、场景节点移除操作,未达到预期效果。

原有参考代码

MainActivity基础结构

public class MainActivity extends AppCompatActivity implements Scene.OnUpdateListener {
    private CustomArFragment arFragment;
    public DataBaseHelper mydb;
    public TextView mytext ;
    private Button button;
    AnchorNode anchorNode;
    Anchor anchor;
    
     protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            arFragment=(CustomArFragment)(Object)getSupportFragmentManager().findFragmentById(R.id.scene_frag);
            assert arFragment != null;
            arFragment.getArSceneView ().getScene ().addOnUpdateListener (this);
    }
}

AugmentedImageDatabase配置逻辑

public void setupdataBase(Config config, Session session){
    Bitmap mark1= BitmapFactory.decodeResource(getResources(),R.drawable.marks1);
    AugmentedImageDatabase aid=new AugmentedImageDatabase(session);
    Bitmap mark2= BitmapFactory.decodeResource(getResources(),R.drawable.marks2);
    Bitmap mark3= BitmapFactory.decodeResource(getResources(),R.drawable.marks3);
    Bitmap mark4= BitmapFactory.decodeResource(getResources(),R.drawable.marks4);
    Bitmap mark5= BitmapFactory.decodeResource(getResources(),R.drawable.markss5);
    //添加识别标记
    aid.addImage("mark1",mark1);
    aid.addImage("mark2",mark2);
    aid.addImage("mark3",mark3);
    aid.addImage("mark4",mark4);
    aid.addImage("mark5",mark5);

    config.setAugmentedImageDatabase(aid);
}

帧更新回调核心逻辑

@Override
public void onUpdate(FrameTime frameTime) {
    Frame frame=arFragment.getArSceneView ().getArFrame ();
    assert frame != null;
    Collection<AugmentedImage> images=frame.getUpdatedTrackables (AugmentedImage.class);
    for (AugmentedImage image:images) {
        String marker=image.getName ();
        String[] tempname={"mark1","mark2","mark3","mark4","mark5"};

        for (String s : tempname) {
            if (s.equals (marker)) {
                createModel (image, marker);
            }
        }
    }
}

@SuppressLint("SetTextI18n")
private void createModel(AugmentedImage image, String marker) {
    ViewRenderable
            .builder ()
            .setView (this,R.layout.text_main)
            .build ()
            .thenAccept (viewRenderable -> setupModel(viewRenderable,image,marker));
}

渲染逻辑

@SuppressLint("SetTextI18n")
private void setupModel(ViewRenderable viewRenderable, AugmentedImage image, String marker) {
    mytext=(TextView)viewRenderable.getView ().findViewById (R.id.planetInfoCard);
    anchor=image.createAnchor (image.getCenterPose ());
    anchorNode=new AnchorNode (anchor);
    anchorNode.setRenderable (viewRenderable);
    anchorNode.setParent (arFragment.getArSceneView ().getScene ());
    arFragment.getArSceneView ().getScene ().addChild (anchorNode);
}

问题根因

  1. 锚点残留问题:错误选择了清理触发时机,LAST_KNOWN_POSE属于追踪临时中断的PAUSED状态,此时ARCore仍在尝试恢复追踪,并非识别完全丢失;同时未给每个标记建立独立的锚点、节点映射,清理时无法定位到对应标记的关联资源。
  2. 卡顿崩溃问题:
    • 帧回调每触发一次、只要识别到已入库标记就重复创建ViewRenderable、Anchor、AnchorNode实例,同一张标记会生成大量重复对象占用内存
    • 仅用单个全局变量存储最新的anchor和anchorNode,多图识别时旧实例的引用被覆盖,无法被回收造成内存泄漏,累积后触发OOM崩溃
    • 重复调用节点添加逻辑,setParent已经完成节点挂接,额外调用addChild会触发渲染层异常
    • 配置图像数据库时未传入标记物理尺寸,ARCore需要额外计算位姿,额外消耗性能

修复实现

第一步:替换成员变量

将原有单个anchor、anchorNode变量替换为映射表,同时增加可复用的渲染实例缓存,避免重复创建对象:

// 存储每个标记对应的锚点、渲染节点,key为标记名称
private final HashMap<String, Anchor> markerAnchorMap = new HashMap<>();
private final HashMap<String, AnchorNode> markerNodeMap = new HashMap<>();
// 提前加载可复用的文本渲染实例,避免帧回调中重复加载
private ViewRenderable sharedTextRenderable;

第二步:提前加载渲染资源

在onCreate中提前完成渲染资源加载,不要在识别触发时动态加载:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    arFragment=(CustomArFragment)(Object)getSupportFragmentManager().findFragmentById(R.id.scene_frag);
    assert arFragment != null;
    // 预加载渲染资源
    ViewRenderable.builder()
            .setView(this, R.layout.text_main)
            .build()
            .thenAccept(renderable -> sharedTextRenderable = renderable);
    arFragment.getArSceneView().getScene().addOnUpdateListener(this);
}

第三步:重写帧回调逻辑

区分三种追踪状态,识别到有效追踪时仅在未创建节点的情况下新建实例,追踪完全停止时彻底清理对应资源:

@Override
public void onUpdate(FrameTime frameTime) {
    Frame frame = arFragment.getArSceneView().getArFrame();
    // 帧为空或渲染资源未加载完成时直接跳过
    if (frame == null || sharedTextRenderable == null) return;
    Collection<AugmentedImage> updatedImages = frame.getUpdatedTrackables(AugmentedImage.class);
    List<String> validMarkers = Arrays.asList("mark1","mark2","mark3","mark4","mark5");

    for (AugmentedImage image : updatedImages) {
        String markerName = image.getName();
        // 跳过无效识别结果
        if (!validMarkers.contains(markerName)) continue;
        TrackingState trackState = image.getTrackingState();
        
        switch (trackState) {
            case TRACKING:
                // 仅当该标记未创建过渲染节点时新建,避免重复生成
                if (!markerNodeMap.containsKey(markerName)) {
                    createMarkerRender(image, markerName);
                }
                break;
            case STOPPED:
                // 追踪完全终止,彻底清理关联资源
                if (markerNodeMap.containsKey(markerName)) {
                    AnchorNode targetNode = markerNodeMap.get(markerName);
                    Anchor targetAnchor = markerAnchorMap.get(markerName);
                    // 从场景移除节点
                    arFragment.getArSceneView().getScene().removeChild(targetNode);
                    // 分离锚点、释放引用
                    targetNode.setRenderable(null);
                    targetNode.setParent(null);
                    targetAnchor.detach();
                    // 从映射表移除记录
                    markerNodeMap.remove(markerName);
                    markerAnchorMap.remove(markerName);
                }
                break;
            case PAUSED:
                // 即原LAST_KNOWN_POSE状态,临时丢帧不做处理,等待追踪恢复或终止
                break;
        }
    }
}

第四步:简化渲染创建逻辑

直接使用预加载的渲染资源,将实例存入映射表,避免重复添加节点:

@SuppressLint("SetTextI18n")
private void createMarkerRender(AugmentedImage image, String markerName) {
    // 创建锚点和节点
    Anchor newAnchor = image.createAnchor(image.getCenterPose());
    AnchorNode newNode = new AnchorNode(newAnchor);
    newNode.setRenderable(sharedTextRenderable);
    newNode.setParent(arFragment.getArSceneView().getScene());
    // 存入映射表
    markerAnchorMap.put(markerName, newAnchor);
    markerNodeMap.put(markerName, newNode);
    // 给当前标记的文本赋值,不要使用全局TextView引用避免串位
    TextView cardText = (TextView) sharedTextRenderable.getView().findViewById(R.id.planetInfoCard);
    cardText.setText(markerName); // 替换为对应标记要显示的实际内容
}

第五步:优化图像数据库配置

添加标记时传入物理实际宽度(单位为米),可大幅提升追踪稳定性、降低性能消耗:

// 示例:标记实际宽度为10厘米时传0.1f,根据实际标记尺寸修改
aid.addImage("mark1", mark1, 0.1f);
aid.addImage("mark2", mark2, 0.1f);
aid.addImage("mark3", mark3, 0.1f);
aid.addImage("mark4", mark4, 0.1f);
aid.addImage("mark5", mark5, 0.1f);

额外优化建议

  • 若不同标记需要显示完全不同的视图内容,不要复用单个ViewRenderable,可提前将各标记对应的渲染实例加载完成后存入缓存,不要在帧回调中动态加载
  • 若同时识别的标记数量较多,可设置识别距离上限,过远的标记不创建渲染节点,降低渲染压力
  • 不要在onUpdate帧回调中做任何耗时操作(如数据库读写、网络请求、大图解码),避免阻塞渲染线程

内容的提问来源于stack exchange,提问作者Harshal Patil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 12:12:22