android image-processing camera renderscript android-camera2

¿Convertir Android ImageReader Image a una matriz de bytes YCbCr_420_SP(NV21) usando un script de render?



image-processing camera (1)

Actualmente estoy trabajando con Javacv que hace uso de la función de cámara public void onPreviewFrame(byte[] data, Camera camera) .

Dado que la cámara está obsoleta, he estado buscando en camera2 y MediaProjection . Ambas bibliotecas hacen uso de la clase ImageReader .

Actualmente, he creado una instancia de este ImageReader con el siguiente código:

ImageReader.newInstance(DISPLAY_WIDTH, DISPLAY_HEIGHT, PixelFormat.RGBA_8888, 2);

Y adjunte un OnImageAvailableListener como este:

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { mBackgroundHandler.post(new processImage(reader.acquireNextImage())); }

He intentado usar el formato RGBA_8888 con Javacv según este hilo: https://github.com/bytedeco/javacv/issues/298 pero eso no funciona para mí.

Así que en lugar de eso, estaba pensando en usar el script Render para convertir estas Image a NV21(YUV_420_SP) (que es la salida predeterminada de la cámara en la función onPreviewFrame ) ya que funcionó para mí con la biblioteca de camera .

También he leído publicaciones como esta y este sitio web para hacer la conversión, pero no me funcionaron y me temo que serán demasiado lentas. Además, mi conocimiento de C es muy limitado. Básicamente, parece que quiero la operación inversa de https://developer.android.com/reference/android/renderscript/ScriptIntrinsicYuvToRGB.html

Entonces, ¿cómo se puede pasar de una Image a una matriz de bytes que coincida con la salida de la función onPreviewFrame , es decir, el NV21(YUV_420_SP) ? Preferiblemente usar Renderscript ya que es más rápido.

Edición 1:

He intentado usar ImageFormat.YUV_420_888 pero fue en vano. Seguí recibiendo errores como The producer output buffer format 0x1 doesn''t match the ImageReader''s configured buffer format . PixelFormat.RGBA_8888 nuevo a PixelFormat.RGBA_8888 y descubrí que solo hay un plano en el ImageObject . El búfer de bytes de este plano es de tamaño ancho * altura * 4 (un byte para R, G, B, A respectivamente). Así que traté de convertir esto al formato NV21 .

He modificado el código de esta respuesta para producir la siguiente función:

void RGBtoNV21(byte[] yuv420sp, byte[] argb, int width, int height) { final int frameSize = width * height; int yIndex = 0; int uvIndex = frameSize; int A, R, G, B, Y, U, V; int index = 0; int rgbIndex = 0; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { R = argb[rgbIndex++]; G = argb[rgbIndex++]; B = argb[rgbIndex++]; A = argb[rgbIndex++]; // RGB to YUV conversion according to // https://en.wikipedia.org/wiki/YUV#Y.E2.80.B2UV444_to_RGB888_conversion Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16; U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128; V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128; // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor // of 2 meaning for every 4 Y pixels there are 1 V and 1 U. // Note the sampling is every other pixel AND every other scanline. yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y)); if (i % 2 == 0 && index % 2 == 0) { yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V)); yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U)); } index++; } } }

e invocarlo utilizando:

int mWidth = mImage.getWidth(); int mHeight = mImage.getHeight(); byte[] rgbaBytes = new byte[mWidth * mHeight * 4]; mImage.getPlanes()[0].getBuffer().get(rgbaBytes); mImage.close(); byte[] yuv = new byte[mWidth * mHeight * 3 / 2]; RGBtoNV21(yuv, rgbaBytes, mWidth, mHeight);

Aquí mImage es un objeto Image producido por mi ImageReader .

Sin embargo, esto produce un resultado similar a esta imagen:

que está claramente mal formado Parece que mi conversión está desactivada pero no puedo entender qué es exactamente.


@TargetApi(19) public static byte[] yuvImageToByteArray(Image image) { assert(image.getFormat() == ImageFormat.YUV_420_888); int width = image.getWidth(); int height = image.getHeight(); Image.Plane[] planes = image.getPlanes(); byte[] result = new byte[width * height * 3 / 2]; int stride = planes[0].getRowStride(); assert (1 == planes[0].getPixelStride()); if (stride == width) { planes[0].getBuffer().get(result, 0, width*height); } else { for (int row = 0; row < height; row++) { planes[0].getBuffer().position(row*stride); planes[0].getBuffer().get(result, row*width, width); } } stride = planes[1].getRowStride(); assert (stride == planes[2].getRowStride()); int pixelStride = planes[1].getPixelStride(); assert (pixelStride == planes[2].getPixelStride()); byte[] rowBytesCb = new byte[stride]; byte[] rowBytesCr = new byte[stride]; for (int row = 0; row < height/2; row++) { int rowOffset = width*height + width/2 * row; planes[1].getBuffer().position(row*stride); planes[1].getBuffer().get(rowBytesCb); planes[2].getBuffer().position(row*stride); planes[2].getBuffer().get(rowBytesCr); for (int col = 0; col < width/2; col++) { result[rowOffset + col*2] = rowBytesCr[col*pixelStride]; result[rowOffset + col*2 + 1] = rowBytesCb[col*pixelStride]; } } return result; }

He publicado otra función con requisitos similares. Esa nueva implementación intenta aprovechar el hecho de que, con bastante frecuencia, YUV_420_888 solo es NV21 disfrazado.