{"id":858,"date":"2010-12-13T23:32:46","date_gmt":"2010-12-13T21:32:46","guid":{"rendered":"http:\/\/192.168.1.2:8080\/?p=858"},"modified":"2010-12-13T23:59:03","modified_gmt":"2010-12-13T21:59:03","slug":"bitmap-desde-nv21-en-android","status":"publish","type":"post","link":"https:\/\/nauj27.com\/blog\/?p=858","title":{"rendered":"Bitmap desde NV21 en Android"},"content":{"rendered":"<p>En el nuevo proyecto que estoy escribiendo para Android, me he visto en la necesidad de analizar cada una de las im\u00e1genes que recoge la c\u00e1mara durante la vista previa de la misma. Seg\u00fan la documentaci\u00f3n el formato que deben de implementar los dispositivos con Android es <a href=\"http:\/\/developer.android.com\/reference\/android\/hardware\/Camera.PreviewCallback.html#onPreviewFrame(byte[],%20android.hardware.Camera)\">YCbCr_420_SP codificado como NV21<\/a>. Aunque en principio se podr\u00edan especificar otros formatos es extra\u00f1o que alg\u00fan terminal tenga otro formato implementado as\u00ed que deberemos de usar ese si queremos que funcione en el mayor n\u00famero de plataformas y dispositivos posibles.<\/p>\n<div id=\"attachment_859\" style=\"width: 510px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-859\" loading=\"lazy\" class=\"size-full wp-image-859\" title=\"Cylon detector\" src=\"http:\/\/192.168.1.2:8080\/wp-content\/uploads\/2010\/12\/cylondetector.jpg\" alt=\"Cylon detector pre-early release\" width=\"500\" height=\"312\" \/><p id=\"caption-attachment-859\" class=\"wp-caption-text\">Desarrollo de Cylon Detector para Android<\/p><\/div>\n<p>En cada frame recibido se desea analizar si existe la cara de una persona, por lo que hay que convertir la imagen a <a href=\"http:\/\/developer.android.com\/reference\/android\/graphics\/Bitmap.html\">Bitmap<\/a> y pasarla al <a href=\"http:\/\/developer.android.com\/reference\/android\/media\/FaceDetector.html\">detector de caras<\/a> que provee el API. Pues bien, existe un problema y es que <a href=\"http:\/\/code.google.com\/p\/android\/issues\/detail?id=823\">la factor\u00eda para crear Bitmaps no acepta ese formato<\/a>. Hay <a href=\"http:\/\/code.google.com\/p\/zxing\/source\/browse\/trunk\/android\/src\/com\/google\/zxing\/client\/android\/PlanarYUVLuminanceSource.java?r=1677\">varias soluciones<\/a> por la red, pero unas son para Android 2.2 en adelante, otras son implementaciones en C para hacerlas usando el NDK como librer\u00eda independiente, y otras s\u00edmplemente son demasiado complejas para lo que aqu\u00ed se necesita.<\/p>\n<p>Al final lo que he hecho es analizar el formato de los datos en base a su <a href=\"http:\/\/www.fourcc.org\/yuv.php#NV21\">definici\u00f3n<\/a> y crear el Bitmap a partir de estos datos para la imagenen blanco y negro. Podr\u00eda hacerse para la imagen en color s\u00edmplemente leyendo todos los datos y haciendo uso de las funciones de <a href=\"http:\/\/www.fourcc.org\/fccyvrgb.php\">conversi\u00f3n de YCrCb a RGB<\/a> pero ello requerir\u00eda mayor tiempo de proceso incluyendo varias multiplicaciones en coma flotante. Aqu\u00ed 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\u00fan el formato NV21 est\u00e1n 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\u00f3n que estoy realizando no es del todo correcta pero es cuesti\u00f3n de convertir y acotar correctamente los valores.<\/p>\n<p>Por si a alguien le es de utilidad, esto es un recorte de la parte m\u00e1s importante que he programado para la conversi\u00f3n de NV21 a Bitmap:<\/p>\n<pre lang=\"java\" line=\"1\">\r\n\/**\r\n * @author nauj27\r\n * The Utils class contains utilities for CylonDetector.\r\n *\/\r\npublic class Utils {\r\n\t\r\n\t\/**\r\n\t * See http:\/\/www.fourcc.org\/yuv.php#NV21 for more information.\r\n\t * We only read luminance for speed up the whole process. \r\n\t * All colors of the image are set to the luminance value and\r\n\t * this way we obtains a black and white image for processing.\r\n\t * \r\n\t * @param data The data array in NV21 (YCbCr_420_SP) format.\r\n\t * @return Black and white bitmap decoded from NV21 input data.\r\n\t *\/\r\n\tpublic static Bitmap getBitmapFromNV21(byte[] data, int width, int height) {\r\n\t\t\r\n\t\tint pixelsNumber = width * height;\r\n\t\tint[] colors = new int[pixelsNumber];\r\n\t\t\r\n\t\tfor (int pixel = 0; pixel < pixelsNumber; pixel++) {\r\n\t\t\tcolors[pixel] = Color.rgb(data[pixel], data[pixel], data[pixel]);\r\n\t\t}\r\n\t\t\r\n\t\treturn Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);\r\n\t}\r\n\r\n}\r\n<\/pre>\n<p><b>Editada:<\/b> 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\u00f3n:<\/p>\n<pre lang=\"java\" line=\"1\">\r\ngrey = data[pixel] & 0xff;\r\ncolors[pixel] = 0xff000000 | (grey * 0x00010101);\r\n<\/pre>\n<p>Esto es lo que ir\u00eda dentro del bucle <i>for<\/i> para que sea interpretado correctamente por el <b>Bitmap.createBitmap<\/b> como mapa de color <b>ARGB_8888<\/b>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>En el nuevo proyecto que estoy escribiendo para Android, me he visto en la necesidad de analizar cada una de las im\u00e1genes que recoge la c\u00e1mara durante la vista previa de la misma. Seg\u00fan la documentaci\u00f3n el formato que deben de implementar los dispositivos con Android es YCbCr_420_SP codificado como NV21. Aunque en principio se [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[151,4],"tags":[85,182,154,181,153,183,184,185],"_links":{"self":[{"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/858"}],"collection":[{"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=858"}],"version-history":[{"count":10,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/858\/revisions"}],"predecessor-version":[{"id":868,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/858\/revisions\/868"}],"wp:attachment":[{"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=858"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=858"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=858"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}