如何裁剪包含 Y'(亮度)的 byte[] 数组而不转换为位图

Mat*_*app 5 arrays android image-processing yuv android-camera2

编辑:解决了!见下文。我需要裁剪从 Camera2 的 onImageAvailable 侦听器获取的图像(YUV422888 颜色空间)。我不想或不需要将其转换为位图,因为它对性能影响很大,而且我实际上对亮度感兴趣,而不是对 RGB 信息(包含在图像的平面 0 中)感兴趣。我想出了以下解决方案:

  1. 获取侦听器中 Camera2 提供的 Image 对象的 Plane 0 中包含的 Y' 信息。
  2. 将 Y' 平面转换.
  3. 将 byte[] 数组转换为二维 byte[][] 数组以进行裁剪。
  4. 使用一些for循环在所需的左、右、上、下坐标处进行裁剪。
  5. 将 2d byte[][] 数组折叠回 1d byte[] 数组out,其中包含裁剪后的亮度 Y' 信息。

不幸的是,第 4 点产生了损坏的图像。我究竟做错了什么?

在Camera2的onImageAvailableListener中(请注意,虽然我正在计算位图,但这只是为了看看发生了什么,因为我对位图/RGB数据不感兴趣):

               Image.Plane[] planes = image.getPlanes();
               ByteBuffer buffer = planes[0].getBuffer(); // Grab just the Y' Plane.
               buffer.rewind();
               byte[] data = new byte[buffer.capacity()];
               buffer.get(data);

               Bitmap bitmap = cropByteArray(data, image.getWidth(), image.getHeight()); // Just for preview/sanity check purposes. The bitmap is **corrupt**.

               runOnUiThread(new bitmapRunnable(bitmap) {
                   @Override
                   public void run() {
                       image_view_preview.setImageBitmap(this.bitmap);
                   }
               });
Run Code Online (Sandbox Code Playgroud)

CropByteArray函数需要修复。它输出一个损坏的位图,并且应该输出一个类似于in 的out byte[] 数组,但仅包含裁剪区域:

    public Bitmap cropByteArray(byte[] in, int inw, int inh) {

    int l = 100; // left crop start
    int r = 400; // right crop end
    int t = 400; // top crop start
    int b = 700; // top crop end
    int outw = r-l;
    int outh = b-t;


    byte[][] in2d = new byte[inw][inh]; // input width and height are 1080 x 1920.
    byte[] out = new byte[outw*outh];
    int[] pixels = new int[outw*outh];

    i = 0;
    for(int col = 0; col < inw; col++) {
        for(int row = 0; row < inh; row++) {
            in2d[col][row] = in[i++];
        }
    }

    i = 0;
    for(int col = l; col < r; col++) {
        for(int row = t; row < b; row++) {
            //out[i++] = in2d[col][row]; // out is the desired output of the function, but for now we output a bitmap instead

            int grey = in2d[col][row] & 0xff;
            pixels[i++] = 0xFF000000 | (grey * 0x00010101);

        }
    }

    return Bitmap.createBitmap(pixels, inw, inh, Bitmap.Config.ARGB_8888);
}
Run Code Online (Sandbox Code Playgroud)

编辑解决了,感谢艾迪·塔瓦拉的建议。以下代码将产生裁剪为所需坐标的 Y'(来自 ImageReader 的亮度平面 0)。裁剪后的数据位于输出字节数组中。生成位图只是为了确认。我还在下面附上了方便的 YUVtoGrayscale() 函数。

        Image.Plane[] planes    = image.getPlanes();
        ByteBuffer buffer       = planes[0].getBuffer();
        int stride              = planes[0].getRowStride();
        buffer.rewind();
        byte[] Y = new byte[buffer.capacity()];
        buffer.get(Y);

        int t=200; int l=600;
        int out_h = 600; int out_w = 600;
        byte[] out = new byte[out_w*out_h];

        int firstRowOffset = stride * t + l;
        for (int row = 0; row < out_h; row++) {
            buffer.position(firstRowOffset + row * stride);
            buffer.get(out, row * out_w, out_w);
        }
        Bitmap bitmap = YUVtoGrayscale(out, out_w, out_h);
Run Code Online (Sandbox Code Playgroud)

这里是YUVtoGrayscale()

    public Bitmap YUVtoGrayscale(byte[] yuv, int width, int height) {

    int[] pixels = new int[yuv.length];

    for (int i = 0; i < yuv.length; i++) {
        int grey = yuv[i] & 0xff;
        pixels[i] = 0xFF000000 | (grey * 0x00010101);
    }

    return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
}
Run Code Online (Sandbox Code Playgroud)

还有一些遗留问题。我使用的是前置摄像头,虽然在TextureView中预览方向是正确的,但ImageViewer返回的图像是顺时针旋转并垂直翻转的(一个人在预览中右脸躺着,只有右脸是左脸,因为我的设备上的垂直翻转)的传感器方向为 270 度。是否有一个公认的解决方案可以使用 Camera2 将预览和保存的照片保持在相同、正确的方向?

干杯。

Edd*_*ala 2

如果您描述图像是如何损坏的,将会很有帮助 - 您是否看到了有效的图像,但它被扭曲了,或者它只是完全垃圾,或者只是完全黑色?

但我猜你没有注意 Y 平面的行步幅(https://developer.android.com/reference/android/media/Image.Plane.html#getRowStride()),这通常会导致图像倾斜(垂直线变成有角度的线)。

访问Y平面时,像素(x,y)的字节索引为:

y * rowStride + x
Run Code Online (Sandbox Code Playgroud)

不是

y * width + x
Run Code Online (Sandbox Code Playgroud)

因为行步幅可能大于宽度。

我也会避免复制太多;你确实不需要二维数组,而且图像的大 byte[] 也会浪费内存。

您可以改为使用eek() 到每个输出行的开头,然后仅使用ByteBuffer.get(byte[], offset, length) 读取需要直接复制到目标byte[] 中的字节。

那看起来像

int stride = planes[0].getRowStride();
ByteBuffer img = planes[0].getBuffer();
int firstRowOffset = stride * t + l;
for (int row = 0; row < outh; row++) {
    img.position(firstRowOffset + row * stride);
    img.get(out, row * outw, outw);
}
Run Code Online (Sandbox Code Playgroud)