android yuv android-camera2

android - Interpretación YUV_420_888 en Samsung Galaxy S7(Camera2)



android-camera2 (6)

Además para cualquier otra persona

android.support.v8.renderscript.RSIllegalArgumentException: matriz demasiado pequeña para el tipo de asignación

Lo arreglé cambiando yAlloc.copyFrom(y); a yAlloc.copy1DRangeFrom(0, y.length, y);

Escribí una conversión de YUV_420_888 a Bitmap, considerando la siguiente lógica (según tengo entendido):

Para resumir el enfoque: las coordenadas x e y del núcleo son congruentes tanto con la x e y de la parte no acolchada del plano Y (asignación 2d) como con las x e y del mapa de bits de salida. Sin embargo, los planos U y V tienen una estructura diferente que el plano Y, ya que usan 1 byte para una cobertura de 4 píxeles y, además, pueden tener un PixelStride que es más de uno, además podrían También tiene un relleno que puede ser diferente del del plano Y. Por lo tanto, para acceder a las U y V de manera eficiente por el núcleo, las puse en asignaciones 1-d y creé un índice "uvIndex" que da la posición de las U y V correspondientes dentro de esa asignación 1-d, por ejemplo ( x, y) coordina en el plano Y (sin relleno) (y, por lo tanto, el mapa de bits de salida).

Para mantener la inclinación de rs-Kernel, excluí el área de relleno en el yPlane al limitar el rango x a través de LaunchOptions (esto refleja el RowStride del plano y que, por lo tanto, puede ignorarse DENTRO del núcleo). Por lo tanto, solo tenemos que considerar uvPixelStride y uvRowStride dentro del uvIndex, es decir, el índice utilizado para acceder a los valores u y v.

Este es mi código:

Renderscript Kernel, llamado yuv420888.rs

#pragma version(1) #pragma rs java_package_name(com.xxxyyy.testcamera2); #pragma rs_fp_relaxed int32_t width; int32_t height; uint picWidth, uvPixelStride, uvRowStride ; rs_allocation ypsIn,uIn,vIn; // The LaunchOptions ensure that the Kernel does not enter the padding zone of Y, so yRowStride can be ignored WITHIN the Kernel. uchar4 __attribute__((kernel)) doConvert(uint32_t x, uint32_t y) { // index for accessing the uIn''s and vIn''s uint uvIndex= uvPixelStride * (x/2) + uvRowStride*(y/2); // get the y,u,v values uchar yps= rsGetElementAt_uchar(ypsIn, x, y); uchar u= rsGetElementAt_uchar(uIn, uvIndex); uchar v= rsGetElementAt_uchar(vIn, uvIndex); // calc argb int4 argb; argb.r = yps + v * 1436 / 1024 - 179; argb.g = yps -u * 46549 / 131072 + 44 -v * 93604 / 131072 + 91; argb.b = yps +u * 1814 / 1024 - 227; argb.a = 255; uchar4 out = convert_uchar4(clamp(argb, 0, 255)); return out; }

Lado de Java:

private Bitmap YUV_420_888_toRGB(Image image, int width, int height){ // Get the three image planes Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[0].getBuffer(); byte[] y = new byte[buffer.remaining()]; buffer.get(y); buffer = planes[1].getBuffer(); byte[] u = new byte[buffer.remaining()]; buffer.get(u); buffer = planes[2].getBuffer(); byte[] v = new byte[buffer.remaining()]; buffer.get(v); // get the relevant RowStrides and PixelStrides // (we know from documentation that PixelStride is 1 for y) int yRowStride= planes[0].getRowStride(); int uvRowStride= planes[1].getRowStride(); // we know from documentation that RowStride is the same for u and v. int uvPixelStride= planes[1].getPixelStride(); // we know from documentation that PixelStride is the same for u and v. // rs creation just for demo. Create rs just once in onCreate and use it again. RenderScript rs = RenderScript.create(this); //RenderScript rs = MainActivity.rs; ScriptC_yuv420888 mYuv420=new ScriptC_yuv420888 (rs); // Y,U,V are defined as global allocations, the out-Allocation is the Bitmap. // Note also that uAlloc and vAlloc are 1-dimensional while yAlloc is 2-dimensional. Type.Builder typeUcharY = new Type.Builder(rs, Element.U8(rs)); typeUcharY.setX(yRowStride).setY(height); Allocation yAlloc = Allocation.createTyped(rs, typeUcharY.create()); yAlloc.copyFrom(y); mYuv420.set_ypsIn(yAlloc); Type.Builder typeUcharUV = new Type.Builder(rs, Element.U8(rs)); // note that the size of the u''s and v''s are as follows: // ( (width/2)*PixelStride + padding ) * (height/2) // = (RowStride ) * (height/2) // but I noted that on the S7 it is 1 less... typeUcharUV.setX(u.length); Allocation uAlloc = Allocation.createTyped(rs, typeUcharUV.create()); uAlloc.copyFrom(u); mYuv420.set_uIn(uAlloc); Allocation vAlloc = Allocation.createTyped(rs, typeUcharUV.create()); vAlloc.copyFrom(v); mYuv420.set_vIn(vAlloc); // handover parameters mYuv420.set_picWidth(width); mYuv420.set_uvRowStride (uvRowStride); mYuv420.set_uvPixelStride (uvPixelStride); Bitmap outBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Allocation outAlloc = Allocation.createFromBitmap(rs, outBitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); Script.LaunchOptions lo = new Script.LaunchOptions(); lo.setX(0, width); // by this we ignore the y’s padding zone, i.e. the right side of x between width and yRowStride lo.setY(0, height); mYuv420.forEach_doConvert(outAlloc,lo); outAlloc.copyTo(outBitmap); return outBitmap; }

Al probar Nexus 7 (API 22), esto devuelve bonitos mapas de bits de color. Sin embargo, este dispositivo tiene diapositivas triviales (= 1) y no tiene relleno (es decir, filas de fila = ancho). Pruebas en el nuevo Samsung S7 (API 23) obtengo imágenes cuyos colores no son correctos, excepto los verdes. Pero la imagen no muestra un sesgo general hacia el verde, solo parece que los colores no verdes no se reproducen correctamente. Tenga en cuenta que el S7 aplica una diapositiva de píxeles u / v de 2 y no rellena.

Dado que la línea de código más crucial está dentro del código rs, el acceso de los planos u / v uint uvIndex = (...) Creo que podría haber un problema, probablemente con una consideración incorrecta de las diapositivas de píxeles aquí. ¿Alguien ve la solución? Gracias.

ACTUALIZACIÓN: Revisé todo, y estoy bastante seguro de que el código con respecto al acceso de y, u, v es correcto. Por lo tanto, el problema debe estar en los valores u y v. Los colores no verdes tienen una inclinación púrpura, y mirando los valores u, v parecen estar en un rango bastante estrecho de aproximadamente 110-150. ¿Es realmente posible que necesitemos hacer frente a conversiones específicas de dispositivos YUV -> RBG ...? ¿Yo me perdí algo?

ACTUALIZACIÓN 2: han corregido el código, funciona ahora, gracias a los comentarios de Eddy.


En un Samsung Galaxy Tab 5 (Tablet), versión de Android 5.1.1 (22), con el supuesto formato YUV_420_888, las siguientes matemáticas de renderscript funcionan bien y producen los colores correctos:

uchar yValue = rsGetElementAt_uchar(gCurrentFrame, x + y * yRowStride); uchar vValue = rsGetElementAt_uchar(gCurrentFrame, ( (x/2) + (y/4) * yRowStride ) + (xSize * ySize) ); uchar uValue = rsGetElementAt_uchar(gCurrentFrame, ( (x/2) + (y/4) * yRowStride ) + (xSize * ySize) + (xSize * ySize) / 4);

No entiendo por qué el valor horizontal (es decir, y) se escala en un factor de cuatro en lugar de dos, pero funciona bien. También necesitaba evitar el uso de rsGetElementAtYuv_uchar_Y | U | V. Creo que el valor de paso de asignación asociado se establece en cero en lugar de algo apropiado. El uso de rsGetElementAt_uchar () es una solución razonable.

En un Samsung Galaxy S5 (teléfono inteligente), versión de Android 5.0 (21), con el supuesto formato YUV_420_888, no puedo recuperar los valores de U y V, aparecen como todos ceros. Esto da como resultado una imagen de aspecto verde. Luminoso está bien, pero la imagen se voltea verticalmente.


Este código requiere el uso de la biblioteca de compatibilidad RenderScript (android.support.v8.renderscript. *).

Para que la biblioteca de compatibilidad funcione con Android API 23, actualicé a gradle-plugin 2.1.0 y Build-Tools 23.0.3 según la respuesta de Miao Wang en Cómo crear scripts de Renderscript en Android Studio, y hacer que se ejecuten.

Si sigue su respuesta y aparece el mensaje de error "Se requiere la versión 2.10 de Gradle", NO cambie

classpath ''com.android.tools.build:gradle:2.1.0''

En su lugar, actualice el campo distributionUrl del archivo Project / gradle / wrapper / gradle-wrapper.properties a

distributionUrl=https/://services.gradle.org/distributions/gradle-2.10-all.zip

y cambie Archivo> Configuración> Compilaciones, Ejecución, Implementación> Herramientas de compilación> Gradle> Gradle para usar el envoltorio de gradle predeterminado según "Se requiere la versión 2.10 de Gradle". Error


Mirar

floor((float) uvPixelStride*(x)/2)

que calcula su desplazamiento de fila U, V (uv_row_offset) a partir de la coordenada Y x.

si uvPixelStride = 2, entonces a medida que x aumenta:

x = 0, uv_row_offset = 0 x = 1, uv_row_offset = 1 x = 2, uv_row_offset = 2 x = 3, uv_row_offset = 3

Y esto es incorrecto. No hay un valor de píxel U / V válido en uv_row_offset = 1 o 3, ya que uvPixelStride = 2.

Usted quiere

uvPixelStride * floor(x/2)

(suponiendo que no confía en sí mismo para recordar el comportamiento crítico de redondeo hacia abajo de la división entera, si lo hace entonces):

uvPixelStride * (x/2)

debería ser suficiente

Con eso, su mapeo se convierte en:

x = 0, uv_row_offset = 0 x = 1, uv_row_offset = 0 x = 2, uv_row_offset = 2 x = 3, uv_row_offset = 2

Vea si eso corrige los errores de color. En la práctica, el direccionamiento incorrecto aquí significaría que cualquier otra muestra de color sería del plano de color incorrecto, ya que es probable que los datos YUV subyacentes sean semiplanarios (por lo que el plano U comienza en el plano V + 1 byte, con los dos planos intercalados)


Para personas que encuentran errores

android.support.v8.renderscript.RSIllegalArgumentException: Array too small for allocation type

use buffer.capacity() lugar de buffer.remaining()

y si ya realizó algunas operaciones en la imagen, deberá llamar al método rewind() en el búfer.


Re: RSIllegalArgumentException

En mi caso, este fue el caso de que buffer.remaining () no era múltiplo de zancada: la longitud de la última línea era menor que zancada (es decir, solo hasta donde estaban los datos reales).