如何转换和在onImageAvailable(android Camera2)中从前凸轮肖像模式旋转原始NV21阵列图像(android.media.Image)? [英] How to convert & rotate raw NV21 array image (android.media.Image) from front cam portrait mode in onImageAvailable (android Camera2)?

查看:802
本文介绍了如何转换和在onImageAvailable(android Camera2)中从前凸轮肖像模式旋转原始NV21阵列图像(android.media.Image)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:我的帖子中的所有信息仅适用于Samsung Galaxy S7设备.我不知道模拟器和其他设备的行为.

在onImageAvailable中,我将每个图像连续转换为NV21字节数组,然后将其转发到期望原始NV21格式的API.

In onImageAvailable I convert continuously each image to a NV21 byte array and forward it to an API expecting raw NV21 format.

这是我初始化图像读取器并接收图像的方式:

This is how I initialize the image reader and receive the images:

private void openCamera() {
    ...
    mImageReader = ImageReader.newInstance(WIDTH, HEIGHT,
            ImageFormat.YUV_420_888, 1); // only 1 for best performance
    mImageReader.setOnImageAvailableListener(
    mOnImageAvailableListener, mBackgroundHandler);
    ...
}

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
        = new ImageReader.OnImageAvailableListener() {

    @Override
    public void onImageAvailable(ImageReader reader) {
        Image image = reader.acquireLatestImage();
        if (image != null) {
            byte[] data = convertYUV420ToNV21_ALL_PLANES(image); // this image is turned 90 deg using front cam in portrait mode
            byte[] data_rotated = rotateNV21_working(data, WIDTH, HEIGHT, 270);
            ForwardToAPI(data_rotated); // image data is being forwarded to api and received later on
            image.close();
        }
    }
};

将图像转换为原始NV21的函数(从此处),可以正常工作,图像是(由于android ?)在纵向模式下使用前凸轮时旋转90度: (根据Alex Cohn的评论,我对其进行了修改)

The function converting the image to raw NV21 (from here), working fine, the image is (due to android?) turned by 90 degrees when using front cam in portrait mode: (I modified it, slightly according to comments of Alex Cohn)

private byte[] convertYUV420ToNV21_ALL_PLANES(Image imgYUV420) {

    byte[] rez;

    ByteBuffer buffer0 = imgYUV420.getPlanes()[0].getBuffer();
    ByteBuffer buffer1 = imgYUV420.getPlanes()[1].getBuffer();
    ByteBuffer buffer2 = imgYUV420.getPlanes()[2].getBuffer();

    // actually here should be something like each second byte
    // however I simply get the last byte of buffer 2 and the entire buffer 1
    int buffer0_size = buffer0.remaining();
    int buffer1_size = buffer1.remaining(); // / 2 + 1;
    int buffer2_size = 1;//buffer2.remaining(); // / 2 + 1;

    byte[] buffer0_byte = new byte[buffer0_size];
    byte[] buffer1_byte = new byte[buffer1_size];
    byte[] buffer2_byte = new byte[buffer2_size];

    buffer0.get(buffer0_byte, 0, buffer0_size);
    buffer1.get(buffer1_byte, 0, buffer1_size);
    buffer2.get(buffer2_byte, buffer2_size-1, buffer2_size);


    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try {
        // swap 1 and 2 as blue and red colors are swapped
        outputStream.write(buffer0_byte);
        outputStream.write(buffer2_byte);
        outputStream.write(buffer1_byte);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    rez = outputStream.toByteArray();

    return rez;
}

因此,数据"需要旋转.使用此功能(从这里),我收到了奇怪的 3次隔行扫描图片错误:

Hence "data" needs to be rotated. Using this function (from here), I get a weird 3-times interlaced picture error:

public static byte[] rotateNV21(byte[] input, int width, int height, int rotation) {
    byte[] output = new byte[input.length];
    boolean swap = (rotation == 90 || rotation == 270);
    // **EDIT:** in portrait mode & front cam this needs to be set to true:
    boolean yflip = true;// (rotation == 90 || rotation == 180);
    boolean xflip = (rotation == 270 || rotation == 180);
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            int xo = x, yo = y;
            int w = width, h = height;
            int xi = xo, yi = yo;
            if (swap) {
                xi = w * yo / h;
                yi = h * xo / w;
            }
            if (yflip) {
                yi = h - yi - 1;
            }
            if (xflip) {
                xi = w - xi - 1;
            }
            output[w * yo + xo] = input[w * yi + xi];
            int fs = w * h;
            int qs = (fs >> 2);
            xi = (xi >> 1);
            yi = (yi >> 1);
            xo = (xo >> 1);
            yo = (yo >> 1);
            w = (w >> 1);
            h = (h >> 1);
            // adjust for interleave here
            int ui = fs + (w * yi + xi) * 2;
            int uo = fs + (w * yo + xo) * 2;
            // and here
            int vi = ui + 1;
            int vo = uo + 1;
            output[uo] = input[ui];
            output[vo] = input[vi];
        }
    }
    return output;
}

产生此图片:

注意:它仍然是同一杯,但是您会看到3-4次.

从此处使用另一个建议的旋转函数 可以得到正确的结果:

Using another suggested rotate function from here gives the proper result:

public static byte[] rotateNV21_working(final byte[] yuv,
                                final int width,
                                final int height,
                                final int rotation)
{
  if (rotation == 0) return yuv;
  if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
    throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");
  }

  final byte[]  output    = new byte[yuv.length];
  final int     frameSize = width * height;
  final boolean swap      = rotation % 180 != 0;
  final boolean xflip     = rotation % 270 != 0;
  final boolean yflip     = rotation >= 180;

  for (int j = 0; j < height; j++) {
    for (int i = 0; i < width; i++) {
      final int yIn = j * width + i;
      final int uIn = frameSize + (j >> 1) * width + (i & ~1);
      final int vIn = uIn       + 1;

      final int wOut     = swap  ? height              : width;
      final int hOut     = swap  ? width               : height;
      final int iSwapped = swap  ? j                   : i;
      final int jSwapped = swap  ? i                   : j;
      final int iOut     = xflip ? wOut - iSwapped - 1 : iSwapped;
      final int jOut     = yflip ? hOut - jSwapped - 1 : jSwapped;

      final int yOut = jOut * wOut + iOut;
      final int uOut = frameSize + (jOut >> 1) * wOut + (iOut & ~1);
      final int vOut = uOut + 1;

      output[yOut] = (byte)(0xff & yuv[yIn]);
      output[uOut] = (byte)(0xff & yuv[uIn]);
      output[vOut] = (byte)(0xff & yuv[vIn]);
    }
  }
  return output;
}

结果现在很好:

最上面的图像显示了使用纹理视图的表面并将其添加到captureRequestBuilder的直接流.底部图像显示了旋转后的原始图像数据.

The top image shows the direct stream using a texture view's surface and adding it to the captureRequestBuilder. The bottom image shows the raw image data after rotating.

问题是:

  • "convertYUV420ToNV21_ALL_PLANES"中的此hack是否对任何 设备/仿真器?
  • 为什么rotateNV21不能正常工作,而rotateNV21_working可以正常工作.
  • Does this hack in "convertYUV420ToNV21_ALL_PLANES" work on any device/emulator?
  • Why does rotateNV21 not work, while rotateNV21_working works fine.

镜像问题已修复,请参见代码注释.压缩问题已修复,这是由转发的API引起的. 实际的未解决问题是适当的,不太昂贵的功能,可以将图像转换并旋转为可在任何设备上运行的原始NV21.

The mirror issue is fixed, see code comment. The squeeze issue is fixed, it was caused by the API it gets forwarded. The actual open issue is a proper not too expensive function, converting and rotating an image into raw NV21 working on any device.

推荐答案

下面是将图片转换为 NV21 字节[]的代码.当 imgYUV420 的U和V平面的pixelStride = 1(在模拟器上)或pixelStride = 2(在Nexus上)时,此功能将起作用:

Here is the code to convert the Image to NV21 byte[]. This will work when the imgYUV420 U and V planes have pixelStride=1 (as on emulator) or pixelStride=2 (as on Nexus):

private byte[] convertYUV420ToNV21_ALL_PLANES(Image imgYUV420) {

    assert(imgYUV420.getFormat() == ImageFormat.YUV_420_888);
    Log.d(TAG, "image: " + imgYUV420.getWidth() + "x" + imgYUV420.getHeight() + " " + imgYUV420.getFormat());
    Log.d(TAG, "planes: " + imgYUV420.getPlanes().length);
    for (int nplane = 0; nplane < imgYUV420.getPlanes().length; nplane++) {
        Log.d(TAG, "plane[" + nplane + "]: length " + imgYUV420.getPlanes()[nplane].getBuffer().remaining() + ", strides: " + imgYUV420.getPlanes()[nplane].getPixelStride() + " " + imgYUV420.getPlanes()[nplane].getRowStride());
    }

    byte[] rez = new byte[imgYUV420.getWidth() * imgYUV420.getHeight() * 3 / 2];
    ByteBuffer buffer0 = imgYUV420.getPlanes()[0].getBuffer();
    ByteBuffer buffer1 = imgYUV420.getPlanes()[1].getBuffer();
    ByteBuffer buffer2 = imgYUV420.getPlanes()[2].getBuffer();

    int n = 0;
    assert(imgYUV420.getPlanes()[0].getPixelStride() == 1);
    for (int row = 0; row < imgYUV420.getHeight(); row++) {
        for (int col = 0; col < imgYUV420.getWidth(); col++) {
            rez[n++] = buffer0.get();
        }
    }
    assert(imgYUV420.getPlanes()[2].getPixelStride() == imgYUV420.getPlanes()[1].getPixelStride());
    int stride = imgYUV420.getPlanes()[1].getPixelStride();
    for (int row = 0; row < imgYUV420.getHeight(); row += 2) {
        for (int col = 0; col < imgYUV420.getWidth(); col += 2) {
            rez[n++] = buffer1.get();
            rez[n++] = buffer2.get();
            for (int skip = 1; skip < stride; skip++) {
                if (buffer1.remaining() > 0) {
                    buffer1.get();
                }
                if (buffer2.remaining() > 0) {
                    buffer2.get();
                }
            }
        }
    }

    Log.w(TAG, "total: " + rez.length);
    return rez;
}

优化的Java代码可在此处 获得.

如您所见,很容易更改此代码以一步一步生成旋转图像 :

As you can see, it is very easy to change this code to produce a rotated image in a single step:

private byte[] rotateYUV420ToNV21(Image imgYUV420) {

    Log.d(TAG, "image: " + imgYUV420.getWidth() + "x" + imgYUV420.getHeight() + " " + imgYUV420.getFormat());
    Log.d(TAG, "planes: " + imgYUV420.getPlanes().length);
    for (int nplane = 0; nplane < imgYUV420.getPlanes().length; nplane++) {
        Log.d(TAG, "plane[" + nplane + "]: length " + imgYUV420.getPlanes()[nplane].getBuffer().remaining() + ", strides: " + imgYUV420.getPlanes()[nplane].getPixelStride() + " " + imgYUV420.getPlanes()[nplane].getRowStride());
    }

    byte[] rez = new byte[imgYUV420.getWidth() * imgYUV420.getHeight() * 3 / 2];
    ByteBuffer buffer0 = imgYUV420.getPlanes()[0].getBuffer();
    ByteBuffer buffer1 = imgYUV420.getPlanes()[1].getBuffer();
    ByteBuffer buffer2 = imgYUV420.getPlanes()[2].getBuffer();

    int width = imgYUV420.getHeight();
    assert(imgYUV420.getPlanes()[0].getPixelStride() == 1);
    for (int row = imgYUV420.getHeight()-1; row >=0; row--) {
        for (int col = 0; col < imgYUV420.getWidth(); col++) {
            rez[col*width+row] = buffer0.get();
        }
    }
    int uv_offset = imgYUV420.getWidth()*imgYUV420.getHeight();
    assert(imgYUV420.getPlanes()[2].getPixelStride() == imgYUV420.getPlanes()[1].getPixelStride());
    int stride = imgYUV420.getPlanes()[1].getPixelStride();
    for (int row = imgYUV420.getHeight() - 2; row >= 0; row -= 2) {
        for (int col = 0; col < imgYUV420.getWidth(); col += 2) {
            rez[uv_offset+col/2*width+row] = buffer1.get();
            rez[uv_offset+col/2*width+row+1] = buffer2.get();
            for (int skip = 1; skip < stride; skip++) {
                if (buffer1.remaining() > 0) {
                    buffer1.get();
                }
                if (buffer2.remaining() > 0) {
                    buffer2.get();
                }
            }
        }
    }

    Log.w(TAG, "total rotated: " + rez.length);
    return rez;
}

我真诚地建议该网站 http://rawpixels.net/来查看原始图像的实际结构.

I sincerely recommend the site http://rawpixels.net/ to see the actual structure of your raw images.

这篇关于如何转换和在onImageAvailable(android Camera2)中从前凸轮肖像模式旋转原始NV21阵列图像(android.media.Image)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆