如何从 ARCore [Android Studio 中的 session.update() 获取位图]

How to get Bitmap from session.update() in ARCore [Android Studio]

我正在尝试使用 ARCore 从我的 ARSession 的当前帧中获取位图。但它始终等于 null。我已经在网上搜索了很长时间,但无法弄清楚我做错了什么。

try {
    capturedImage = mFrame.acquireCameraImage();

    ByteBuffer buffer = capturedImage.getPlanes()[0].getBuffer();

    byte[] bytes = new byte[buffer.capacity()];

    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length,null);

    if (bitmap == null) 
        Log.e(TAG,"Bitmap was NOT initialized!");

} catch(Exception e){

}

我从 GLSurfaceViewonDrawFrame 中获取 mFrame,我用它来显示相机图像。一切正常,除了我的位图等于 null。

我用的是Button,所以只用了一个Frame,如下:

scanButton = (Button) findViewById(R.id.scanButton);
scanButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        checkbox = false;
        if (capturedImage!=null) capturedImage.close();
            BitmapMethod();
    }
});

capturedImagebufferbytes 都不等于 null。

mFrame.acquireCameraImage()可能有问题吗?

非常感谢

Is there probably something wrong with mFrame.acquireCameraImage()?

不,mFrame.acquireCameraImage() 按预期工作。

But it always equals null

Bitmap 将始终等于 null,因为位图工厂不理解传递给它的图像数据。

方法 mFrame.acquireCameraImage() 以 YUV 格式或 YCbCr 类型的 Image 对象响应。这些类型的图像有 3 个平面,here 解释得非常好。这些位面包含的ByteArray可能直接被native代码中的CPU/GPU读取。 BitmapFactory 无法读取此类数据。因此,您需要将此 YUV 图像转换为其他图像。

为此,您需要使用 YuvImage class 创建 YUV 实例,然后使用 compressToJpeg 方法将其转换为 JPEG。从中获得 byteArray 后,您可以简单地执行上面的操作。使用 BitmapFactory 将其转换为位图并将其添加到您的 ImageView.

注:YUV有3个平面。从所有平面创建一个字节数组,然后将其传递给 YUV 构造函数。虽然没有详细说明,但它看起来应该与此类似:

//The camera image received is in YUV YCbCr Format. Get buffers for each of the planes and use them to create a new bytearray defined by the size of all three buffers combined
val cameraPlaneY = cameraImage.planes[0].buffer
val cameraPlaneU = cameraImage.planes[1].buffer
val cameraPlaneV = cameraImage.planes[2].buffer

//Use the buffers to create a new byteArray that 
val compositeByteArray = ByteArray(cameraPlaneY.capacity() + cameraPlaneU.capacity() + cameraPlaneV.capacity())

cameraPlaneY.get(compositeByteArray, 0, cameraPlaneY.capacity())
cameraPlaneU.get(compositeByteArray, cameraPlaneY.capacity(), cameraPlaneU.capacity())
cameraPlaneV.get(compositeByteArray, cameraPlaneY.capacity() + cameraPlaneU.capacity(), cameraPlaneV.capacity())

val baOutputStream = ByteArrayOutputStream()
val yuvImage: YuvImage = YuvImage(compositeByteArray, ImageFormat.NV21, cameraImage.width, cameraImage.height, null)
yuvImage.compressToJpeg(Rect(0, 0, cameraImage.width, cameraImage.height), 75, baOutputStream)
val byteForBitmap = baOutputStream.toByteArray()
val bitmap = BitmapFactory.decodeByteArray(byteForBitmap, 0, byteForBitmap.size)
imageView.setImageBitmap(bitmap)

这只是一个粗略的代码。它也许有改进的余地。另请参阅 here.

我不知道是否还有人在寻找答案,但这是我的代码。

        Image image = mFrame.acquireCameraImage();
        byte[] nv21;
        // Get the three planes.
        ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
        ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
        ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();

        int ySize = yBuffer.remaining();
        int uSize = uBuffer.remaining();
        int vSize = vBuffer.remaining();


        nv21 = new byte[ySize + uSize + vSize];

        //U and V are swapped
        yBuffer.get(nv21, 0, ySize);
        vBuffer.get(nv21, ySize, vSize);
        uBuffer.get(nv21, ySize + vSize, uSize);

        int width = image.getWidth();
        int height = image.getHeight();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out);
        byte[] byteArray = out.toByteArray();
        Bitmap bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);

我也最终重现了与您面临的相同情况。我将 Image 对象设置为 Null。

经过一番研究,我发现问题出在逻辑流程上。

然后我写了下面的代码,它解决了我的问题:

我定义了以下布尔值以设置为捕获按钮单击时的当前帧:

private static boolean captureCurrentFrame = false;

这段代码是我在onClick()函数中写的,用来获取当前帧的RGB和深度图:

public void captureFrame(View view){
    Toast.makeText(getApplicationContext(),"Capturing depth and rgb photo",Toast.LENGTH_SHORT).show();
    captureCurrentFrame = true;
  }

我在 onDrawFrame() 方法中写的这一部分是在从 session.update() 获取帧之后写的:

if(captureCurrentFrame) {
        RGBImage = frame.acquireCameraImage();
        DepthImage = frame.acquireDepthImage();

        Log.d("Image","Format of the RGB Image: " + RGBImage.getFormat());
        Log.d("Image","Format of the Depth Image: " + DepthImage.getFormat());

        RGBImage.close();
        DepthImage.close();

        captureCurrentFrame = false;
      }

在我的例子中得到 Null 的原因是 onClick 侦听器中的代码在通过 onDraw() 方法之前被触发,因此图像没有存储在变量中。

因此,我将逻辑转移到 onDraw() 并通过侦听器设置的布尔变量触发该流程。