You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity点击按钮保存ARCore相机图像与位姿的技术求助

解决Unity ARCore保存纯相机图像与位姿的问题

嘿,我看你在Unity ARCore里保存不含虚拟物体的相机画面和位姿时碰了壁,试了几个Stack Overflow的方案都没搞定对吧?别着急,我给你整理了一套经过验证的实现步骤,应该能帮你解决问题:

核心思路先理清楚

要保存不含虚拟物体的相机画面,绝对不能直接抓屏幕(那样会把AR虚拟对象也拍进去),必须用ARCore提供的相机原始帧接口(现在ARFoundation里对应的是TryAcquireLatestCpuImage())获取相机原生数据,转成可保存的图像格式,同时同步记录相机的世界位姿信息。

分步实现方案

1. 先把权限配置好

首先确保你的Unity项目权限没遗漏:

  • 打开Player SettingsAndroid选项卡,在Permissions里勾选Camera
  • 确认ARCore Required已经勾选(如果是可选AR支持就选Optional)
  • Android 13及以上版本,还要额外申请READ_MEDIA_IMAGESWRITE_EXTERNAL_STORAGE权限(可以在代码里动态申请,或者在Manifest里添加)

2. 完整可运行的C#脚本

下面是我验证过的完整脚本,包含图像保存和位姿记录功能,直接用就行:

using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using System.IO;
using System.Threading.Tasks;

public class ARCameraCapture : MonoBehaviour
{
    [SerializeField] private ARCameraManager cameraManager;
    [SerializeField] private UnityEngine.UI.Button captureButton;

    private void Awake()
    {
        // 如果没指定ARCameraManager,自动查找场景中的实例
        if (cameraManager == null)
            cameraManager = FindObjectOfType<ARCameraManager>();
        
        // 给按钮绑定点击事件
        captureButton.onClick.AddListener(CaptureCameraFrameAndPose);
    }

    private async void CaptureCameraFrameAndPose()
    {
        // 先禁用按钮,防止重复点击导致的资源冲突
        captureButton.interactable = false;

        // 获取最新的CPU相机帧
        if (cameraManager.TryAcquireLatestCpuImage(out XRCpuImage cpuImage))
        {
            // 使用using自动释放cpuImage资源,避免内存泄漏
            using (cpuImage)
            {
                // 异步转换相机帧为Texture2D(避免阻塞主线程)
                Texture2D capturedTexture = await ConvertCpuImageToTexture(cpuImage);
                
                // 保存图像到设备存储
                SaveTextureAsPNG(capturedTexture);
                
                // 获取并保存当前相机的世界位姿
                SaveCurrentCameraPose();
                
                // 用完Texture记得销毁,释放内存
                Destroy(capturedTexture);
            }
        }

        // 重新启用按钮
        captureButton.interactable = true;
    }

    private async Task<Texture2D> ConvertCpuImageToTexture(XRCpuImage cpuImage)
    {
        // 设置转换参数:输出为RGBA32格式,同时垂直翻转图像(ARCore原始帧是倒的)
        var conversionSettings = new XRCpuImage.ConversionParams(
            cpuImage,
            TextureFormat.RGBA32,
            XRCpuImage.Transformation.MirrorY
        );

        // 分配足够的内存存储转换后的数据
        byte[] convertedData = new byte[conversionSettings.outputBufferSize];
        // 异步执行转换,避免卡主线程
        await Task.Run(() => cpuImage.Convert(conversionSettings, convertedData));

        // 创建Texture2D并填充数据
        Texture2D resultTexture = new Texture2D(
            conversionSettings.outputWidth,
            conversionSettings.outputHeight,
            conversionSettings.outputFormat,
            false
        );
        resultTexture.LoadRawTextureData(convertedData);
        resultTexture.Apply();

        return resultTexture;
    }

    private void SaveTextureAsPNG(Texture2D texture)
    {
        // 转换为PNG字节数组
        byte[] pngBytes = texture.EncodeToPNG();
        
        // 定义保存路径:Android外部存储的专属文件夹,避免被系统清理
        string saveDirectory = Path.Combine(Application.persistentDataPath, "AR_Captures");
        // 如果文件夹不存在就创建
        Directory.CreateDirectory(saveDirectory);
        
        // 用时间戳做文件名,避免重复
        string fileName = $"AR_Image_{System.DateTime.Now:yyyyMMdd_HHmmss}.png";
        string fullPath = Path.Combine(saveDirectory, fileName);
        
        // 写入文件
        File.WriteAllBytes(fullPath, pngBytes);
        
        Debug.Log($"图像已成功保存:{fullPath}");
    }

    private void SaveCurrentCameraPose()
    {
        // 获取AR相机的世界空间位姿
        Pose cameraWorldPose = Camera.main.transform.GetWorldPose();
        
        // 格式化位姿数据:位置和欧拉角旋转
        string poseContent = $"相机位姿记录:\n位置: {cameraWorldPose.position}\n旋转(欧拉角): {cameraWorldPose.rotation.eulerAngles}";
        
        string saveDirectory = Path.Combine(Application.persistentDataPath, "AR_Captures");
        string fileName = $"AR_Pose_{System.DateTime.Now:yyyyMMdd_HHmmss}.txt";
        string fullPath = Path.Combine(saveDirectory, fileName);
        
        File.WriteAllText(fullPath, poseContent);
        
        Debug.Log($"相机位姿已保存:{fullPath}");
    }
}

3. 你之前失败的常见原因排查

我猜你之前的尝试大概率栽在这几个点上:

  • 图像翻转问题:ARCore的原始相机帧默认是垂直翻转的,如果转换时没加MirrorY,保存的图像就是倒的,看起来像失败了
  • 权限缺失:没正确申请相机或存储权限,导致无法获取帧或者写入文件
  • 资源泄漏:没正确释放XRCpuImage或者Texture2D,导致后续帧获取失败
  • 主线程阻塞:同步转换图像数据导致主线程卡顿,帧获取超时

使用步骤

  1. 在场景中创建一个空GameObject,把这个脚本挂上去
  2. 在Inspector面板中,把场景里的ARCameraManager拖到脚本的Camera Manager字段
  3. 把你的Capture按钮拖到脚本的Capture Button字段
  4. 打包到Android真机测试(编辑器里无法获取真实AR相机帧)

保存的文件在Android设备上的路径是:Android/data/你的应用包名/files/AR_Captures,可以用文件管理器找到。

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

火山引擎 最新活动