Bitmap desde NV21 en Android

En el nuevo proyecto que estoy escribiendo para Android, me he visto en la necesidad de analizar cada una de las imágenes que recoge la cámara durante la vista previa de la misma. Según la documentación el formato que deben de implementar los dispositivos con Android es YCbCr_420_SP codificado como NV21. Aunque en principio se podrían especificar otros formatos es extraño que algún terminal tenga otro formato implementado así que deberemos de usar ese si queremos que funcione en el mayor número de plataformas y dispositivos posibles.

Cylon detector pre-early release

Desarrollo de Cylon Detector para Android

En cada frame recibido se desea analizar si existe la cara de una persona, por lo que hay que convertir la imagen a Bitmap y pasarla al detector de caras que provee el API. Pues bien, existe un problema y es que la factoría para crear Bitmaps no acepta ese formato. Hay varias soluciones por la red, pero unas son para Android 2.2 en adelante, otras son implementaciones en C para hacerlas usando el NDK como librería independiente, y otras símplemente son demasiado complejas para lo que aquí se necesita.

Al final lo que he hecho es analizar el formato de los datos en base a su definición y crear el Bitmap a partir de estos datos para la imagenen blanco y negro. Podría hacerse para la imagen en color símplemente leyendo todos los datos y haciendo uso de las funciones de conversión de YCrCb a RGB pero ello requeriría mayor tiempo de proceso incluyendo varias multiplicaciones en coma flotante. Aquí solo necesitamos en principio detectar si hay una cara en la imagen o no, y para ello es suficiente con que la imagen sea en blanco y negro. Para obtener la imagen en blanco y negro lo que he hecho ha sido leer los valores de luminancia (luminosidad), que según el formato NV21 están en la primera parte del array y convertir cada valor a un punto RGB con el mismo valor. El resultado, aunque no se aprecia demasiado bien en la imagen anterior, no es completamente una imagen en blanco y negro, sino que aparecen zonas saturadas de un color amarillo. La conversión que estoy realizando no es del todo correcta pero es cuestión de convertir y acotar correctamente los valores.

Por si a alguien le es de utilidad, esto es un recorte de la parte más importante que he programado para la conversión de NV21 a Bitmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * @author nauj27
 * The Utils class contains utilities for CylonDetector.
 */
public class Utils {
 
	/**
	 * See http://www.fourcc.org/yuv.php#NV21 for more information.
	 * We only read luminance for speed up the whole process. 
	 * All colors of the image are set to the luminance value and
	 * this way we obtains a black and white image for processing.
	 * 
	 * @param data The data array in NV21 (YCbCr_420_SP) format.
	 * @return Black and white bitmap decoded from NV21 input data.
	 */
	public static Bitmap getBitmapFromNV21(byte[] data, int width, int height) {
 
		int pixelsNumber = width * height;
		int[] colors = new int[pixelsNumber];
 
		for (int pixel = 0; pixel < pixelsNumber; pixel++) {
			colors[pixel] = Color.rgb(data[pixel], data[pixel], data[pixel]);
		}
 
		return Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
	}
 
}

Editada: El modo correcto de convertir el valor de luminancia a escala de grises para crear la imagen lo he sacado finalmente del proyecto zxing, y es el que dejo a continuación:

1
2
grey = data[pixel] & 0xff;
colors[pixel] = 0xff000000 | (grey * 0x00010101);

Esto es lo que iría dentro del bucle for para que sea interpretado correctamente por el Bitmap.createBitmap como mapa de color ARGB_8888.

Etiquetas: , , , , , , ,

Archivado en:Android, Programación

10 comentarios en “Bitmap desde NV21 en Android”

  • Arkangel dijo:

    Jojojo, como mola leer eso de «* The Utils class contains utilities for CylonDetector.» :D

  • nauj27 dijo:

    Jeje, pues ahora resulta que el FaceDetector solo sabe interpretar Bitmaps en RGB_565 así que otra conversión toca. Bueno poco a poco :)

  • Monik dijo:

    mu? no entiendo nada….que cosas tan raras teneis los informáticos de hoy en día…qué cosas!!

  • José Manuel dijo:

    Hola, he estado leyendo el blog todo el día, me ha parecido bastante interesante lo que haces en Android.

    Actualmente estoy trabajando en un proyecto sobre Android, que puedes leer en mi web, y me gustaría preguntarte una cosa..

    Verás, tengo un problema con la conversión de YCrCb a RGB, he probado lo que aquí expones y me he dado una vuelta por zxing pero no consigo arreglarlo.

    Me encantaría contactar contigo, solamente te robaría 10 minutos :)

    Te lo agradecería de veras.

  • Rodrigo dijo:

    Y cómo sería la conversión de nv21 a grayscale, pero dejándolo en un byte[], i.e., no quiero pasarlo a bitmap.

    Gracias!

  • nauj27 dijo:

    En el array ‘colors’ tienes los valores para la escala de grises. Si te quieres quedar con un solo byte por pixel, en lugar de guardarlo para RGB guárdalo únicamente una vez.

  • Rodrigo dijo:

    Muchas gracias por tu respuesta nauj27, pero creo que no entiendo lo que me dices… :S

    Podrías intentar explicarlo de otro modo?

    Muchas gracias de nuevo.

  • mei dijo:

    Hola, has probado a utilizar las OpenCV para Android, quizá te podría ser útil para la detección de caras.

  • nauj27 dijo:

    Solo he probado el detector de caras del API de Android, acabo de echar un vistazo al OpenCV y se ve bastante potente, gracias!

  • nauj27 dijo:

    @Rodrigo, en la última línea, la del ‘return’, se hace uso del array de int[] llamado ‘colors’.

    Si entiendo tu pregunta, esa variable contiene lo que necesitas.