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

如何在ARCore中使用SurfaceView获取相机Pose

解决ARCore Pose类的OpenGL上下文错误及SurfaceView使用指南

核心问题原因

ARCore的Pose类依赖OpenGL上下文运行,而Android Studio默认相机教程用的PreviewView是基于View体系的,没有绑定ARCore所需的GL上下文。改用SurfaceView后,需配合ARCore的Session来完成相机预览、GL上下文绑定及Pose数据获取。

步骤1:替换布局为SurfaceView

修改你的布局文件(比如activity_main.xml),用SurfaceView替代原来的PreviewView,同时添加拍照按钮:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/capture_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_marginBottom="32dp"
        android:text="拍照" />
</FrameLayout>

步骤2:初始化ARCore Session与SurfaceView回调

在Activity中完成ARCore会话初始化、SurfaceView绑定,确保GL上下文正确创建:

public class CameraPoseActivity extends AppCompatActivity {
    private Session arSession;
    private SurfaceView surfaceView;
    private Button captureBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        surfaceView = findViewById(R.id.surface_view);
        captureBtn = findViewById(R.id.capture_btn);

        // 初始化ARCore会话
        try {
            arSession = new Session(this);
            Config config = new Config(arSession);
            config.setCameraFacing(Config.CameraFacing.BACK);
            arSession.configure(config);
        } catch (UnavailableException e) {
            e.printStackTrace();
            Toast.makeText(this, "设备不支持ARCore", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        // 绑定SurfaceView回调,处理GL上下文生命周期
        surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                try {
                    arSession.setDisplayGeometry(getDisplay().getRotation(), surfaceView.getWidth(), surfaceView.getHeight());
                    arSession.setSurface(holder.getSurface());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                arSession.setDisplayGeometry(getDisplay().getRotation(), width, height);
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                arSession.pause();
            }
        });

        // 拍照按钮点击事件
        captureBtn.setOnClickListener(v -> capturePhotoWithPose());
    }

    // 处理ARCore会话生命周期
    @Override
    protected void onResume() {
        super.onResume();
        if (arSession != null) {
            try {
                arSession.resume();
            } catch (CameraNotAvailableException e) {
                e.printStackTrace();
                arSession = null;
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (arSession != null) arSession.pause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (arSession != null) arSession.close();
    }
}

步骤3:拍照并同步保存Pose数据

实现拍照逻辑,从ARCore的Frame中获取Pose,并与照片关联保存:

private void capturePhotoWithPose() {
    if (arSession == null) return;

    // 获取当前帧与设备Pose
    Frame frame = arSession.update();
    Pose devicePose = frame.getCamera().getPose();

    // 获取相机图像并保存为照片
    try (Image image = frame.acquireCameraImage()) {
        String photoPath = savePhotoToStorage(image);
        if (photoPath != null) {
            savePoseToExif(devicePose, photoPath);
            Toast.makeText(this, "照片与Pose已保存", Toast.LENGTH_SHORT).show();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 将Image转为JPEG保存到本地
private String savePhotoToStorage(Image image) {
    Image.Plane[] planes = image.getPlanes();
    ByteBuffer yBuffer = planes[0].getBuffer();
    ByteBuffer uBuffer = planes[1].getBuffer();
    ByteBuffer vBuffer = planes[2].getBuffer();

    byte[] nv21 = new byte[yBuffer.remaining() + uBuffer.remaining() + vBuffer.remaining()];
    yBuffer.get(nv21, 0, yBuffer.remaining());
    vBuffer.get(nv21, yBuffer.remaining(), vBuffer.remaining());
    uBuffer.get(nv21, yBuffer.remaining() + vBuffer.remaining(), uBuffer.remaining());

    YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);
    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
    yuvImage.compressToJpeg(new Rect(0, 0, image.getWidth(), image.getHeight()), 90, outStream);

    String photoDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath();
    String photoName = "pose_photo_" + System.currentTimeMillis() + ".jpg";
    String photoPath = photoDir + File.separator + photoName;

    try (FileOutputStream fos = new FileOutputStream(photoPath)) {
        fos.write(outStream.toByteArray());
        return photoPath;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

// 将Pose数据写入照片Exif元数据
private void savePoseToExif(Pose pose, String photoPath) {
    float[] translation = pose.getTranslation();
    float[] rotation = pose.getRotationQuaternion();

    try {
        ExifInterface exif = new ExifInterface(photoPath);
        // 保存平移向量(自定义字段,可替换为合适的Exif标签)
        exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, String.valueOf(translation[0]));
        exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, String.valueOf(translation[1]));
        exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, String.valueOf(translation[2]));
        // 保存旋转四元数到用户注释字段
        String rotationStr = String.format("%f,%f,%f,%f", rotation[0], rotation[1], rotation[2], rotation[3]);
        exif.setAttribute(ExifInterface.TAG_USER_COMMENT, rotationStr);
        exif.saveAttributes();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

步骤4:配置权限与清单

在AndroidManifest.xml中添加必要权限与ARCore声明:

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.ar" android:required="true" />

<application ...>
    <meta-data
        android:name="com.google.ar.core"
        android:value="required" />
</application>

关键注意事项

  • 动态申请相机权限:Android 6.0+需在代码中请求Manifest.permission.CAMERA权限,否则无法启动相机。
  • ARCore兼容性:部分旧设备不支持ARCore,需在初始化时捕获UnavailableException并提示用户。
  • 图像格式处理:ARCore返回的Image为YUV格式,需转换为JPEG才能保存为常规照片。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 12:55:26