android image-processing ocr android-camera yuv

Convirtiendo YUV-> RGB(procesamiento de imágenes)-> YUV durante onPreviewFrame en android?



image-processing ocr (6)

¿Por qué no especificar que la vista previa de la cámara debería proporcionar imágenes RGB?

es decir, Camera.Parameters.setPreviewFormat(ImageFormat.RGB_565) ;

Estoy capturando imágenes usando SurfaceView y obteniendo datos de vista previa de Yuv Raw en public void onPreviewFrame4 (datos de byte [], cámara de la cámara)

Tengo que realizar un preprocesamiento de imagen en onPreviewFrame, así que necesito convertir datos de vista previa de Yuv a datos RGB que el preprocesamiento de imagen y volver a datos Yuv.

He usado ambas funciones para codificar y decodificar datos Yuv a RGB de la siguiente manera:

public void onPreviewFrame(byte[] data, Camera camera) { Point cameraResolution = configManager.getCameraResolution(); if (data != null) { Log.i("DEBUG", "data Not Null"); // Preprocessing Log.i("DEBUG", "Try For Image Processing"); Camera.Parameters mParameters = camera.getParameters(); Size mSize = mParameters.getPreviewSize(); int mWidth = mSize.width; int mHeight = mSize.height; int[] mIntArray = new int[mWidth * mHeight]; // Decode Yuv data to integer array decodeYUV420SP(mIntArray, data, mWidth, mHeight); // Converting int mIntArray to Bitmap and // than image preprocessing // and back to mIntArray. // Encode intArray to Yuv data encodeYUV420SP(data, mIntArray, mWidth, mHeight); } } static public void decodeYUV420SP(int[] rgba, byte[] yuv420sp, int width, int height) { final int frameSize = width * height; for (int j = 0, yp = 0; j < height; j++) { int uvp = frameSize + (j >> 1) * width, u = 0, v = 0; for (int i = 0; i < width; i++, yp++) { int y = (0xff & ((int) yuv420sp[yp])) - 16; if (y < 0) y = 0; if ((i & 1) == 0) { v = (0xff & yuv420sp[uvp++]) - 128; u = (0xff & yuv420sp[uvp++]) - 128; } int y1192 = 1192 * y; int r = (y1192 + 1634 * v); int g = (y1192 - 833 * v - 400 * u); int b = (y1192 + 2066 * u); if (r < 0) r = 0; else if (r > 262143) r = 262143; if (g < 0) g = 0; else if (g > 262143) g = 262143; if (b < 0) b = 0; else if (b > 262143) b = 262143; // rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & // 0xff00) | ((b >> 10) & 0xff); // rgba, divide 2^10 ( >> 10) rgba[yp] = ((r << 14) & 0xff000000) | ((g << 6) & 0xff0000) | ((b >> 2) | 0xff00); } } } static public void encodeYUV420SP_original(byte[] yuv420sp, int[] rgba, int width, int height) { final int frameSize = width * height; int[] U, V; U = new int[frameSize]; V = new int[frameSize]; final int uvwidth = width / 2; int r, g, b, y, u, v; for (int j = 0; j < height; j++) { int index = width * j; for (int i = 0; i < width; i++) { r = (rgba[index] & 0xff000000) >> 24; g = (rgba[index] & 0xff0000) >> 16; b = (rgba[index] & 0xff00) >> 8; // rgb to yuv 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; // clip y yuv420sp[index++] = (byte) ((y < 0) ? 0 : ((y > 255) ? 255 : y)); U[index] = u; V[index++] = v; } }

El problema es que la codificación y decodificación de datos Yuv puede tener algún error porque si omito el paso de preprocesamiento que también codifica los datos Yuv son diferentes de los datos originales de PreviewCallback.

Por favor, ayúdame a resolver este problema. Tengo que usar este código en el escaneo de OCR, así que necesito implementar este tipo de lógica.

Si hay alguna otra forma de hacer lo mismo que por favor provéenme.

Gracias por adelantado. :)


Aunque la documentación sugiere que puede establecer a qué formato deben llegar los datos de imagen de la cámara, en la práctica a menudo puede elegir uno: NV21, un formato YUV. Para obtener mucha información sobre este formato, consulte http://www.fourcc.org/yuv.php#NV21 y para obtener información sobre la teoría detrás de la conversión a RGB, consulte http://www.fourcc.org/fccyvrgb.php . Hay una explicación basada en imágenes en Extraer imagen en blanco y negro del formato NV21 de la cámara Android .

Otro formato, llamado YUV420SP, también es bastante frecuente.

Sin embargo, una vez que haya configurado su rutina onPreviewFrame, la mecánica de pasar de la matriz de bytes a la que le envía datos útiles es algo, ummmm, poco claro. Desde API 8 en adelante, la siguiente solución está disponible, para llegar a un ByteStream que contiene un JPEG de la imagen (compressToJpeg es la única opción de conversión ofrecida por YuvImage):

// pWidth and pHeight define the size of the preview Frame ByteArrayOutputStream out = new ByteArrayOutputStream(); // Alter the second parameter of this to the actual format you are receiving YuvImage yuv = new YuvImage(data, ImageFormat.NV21, pWidth, pHeight, null); // bWidth and bHeight define the size of the bitmap you wish the fill with the preview image yuv.compressToJpeg(new Rect(0, 0, bWidth, bHeight), 50, out);

Es posible que este JPEG deba convertirse al formato que desee. Si quieres un mapa de bits:

byte[] bytes = out.toByteArray(); Bitmap bitmap= BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

Si, por algún motivo, no puede hacer esto, puede hacer la conversión manualmente. Algunos problemas que deben superarse al hacer esto:

  1. Los datos llegan en una matriz de bytes. Por definición, los bytes son números con signo, lo que significa que van de -128 a 127. Sin embargo, los datos son en realidad bytes sin firmar (0 a 255). Si esto no se soluciona, el resultado está condenado a tener algunos efectos de recorte extraños.

  2. Los datos están en un orden muy específico (según la página web mencionada anteriormente) y cada píxel debe extraerse con cuidado.

  3. Cada píxel debe colocarse en el lugar correcto en un mapa de bits, por ejemplo. Esto también requiere un enfoque bastante desordenado (en mi opinión) de construir un búfer de los datos y luego llenar un mapa de bits de la misma.

  4. Si en realidad tiene NV12 (o 420SP), necesitará cambiar las lecturas para U y V.

Presento una solución (que parece funcionar), con solicitudes de correcciones, mejoras y formas de hacer que todo sea menos costoso de ejecutar. Crea un mapa de bits del tamaño de la imagen de vista previa:

La variable de datos proviene de la llamada a onPreviewFrame

// the bitmap we want to fill with the image Bitmap bitmap = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888); int numPixels = imageWidth*imageHeight; // the buffer we fill up which we then fill the bitmap with IntBuffer intBuffer = IntBuffer.allocate(imageWidth*imageHeight); // If you''re reusing a buffer, next line imperative to refill from the start, // if not good practice intBuffer.position(0); // Set the alpha for the image: 0 is transparent, 255 fully opaque final byte alpha = (byte) 255; // Get each pixel, one at a time for (int y = 0; y < imageHeight; y++) { for (int x = 0; x < imageWidth; x++) { // Get the Y value, stored in the first block of data // The logical "AND 0xff" is needed to deal with the signed issue int Y = data[y*imageWidth + x] & 0xff; // Get U and V values, stored after Y values, one per 2x2 block // of pixels, interleaved. Prepare them as floats with correct range // ready for calculation later. int xby2 = x/2; int yby2 = y/2; // make this V for NV12/420SP float U = (float)(data[numPixels + 2*xby2 + yby2*imageWidth] & 0xff) - 128.0f; // make this U for NV12/420SP float V = (float)(data[numPixels + 2*xby2 + 1 + yby2*imageWidth] & 0xff) - 128.0f; // Do the YUV -> RGB conversion float Yf = 1.164f*((float)Y) - 16.0f; int R = (int)(Yf + 1.596f*V); int G = (int)(Yf - 0.813f*V - 0.391f*U); int B = (int)(Yf + 2.018f*U); // Clip rgb values to 0-255 R = R < 0 ? 0 : R > 255 ? 255 : R; G = G < 0 ? 0 : G > 255 ? 255 : G; B = B < 0 ? 0 : B > 255 ? 255 : B; // Put that pixel in the buffer intBuffer.put(alpha*16777216 + R*65536 + G*256 + B); } } // Get buffer ready to be read intBuffer.flip(); // Push the pixel information from the buffer onto the bitmap. bitmap.copyPixelsFromBuffer(intBuffer);

Como @Timmmm señala a continuación, puede hacer la conversión en int multiplicando los factores de escala por 1000 (es decir, 1.164 se convierte en 1164) y luego dividir los resultados finales por 1000.


Después de algunas pruebas en el mini código más rápido de Samsung S4 es (120% más rápido que Neil''s [flotadores!] Y 30% más rápido que Hitesh original):

static public void decodeYUV420SP(int[] rgba, byte[] yuv420sp, int width, int height) { final int frameSize = width * height; // define variables before loops (+ 20-30% faster algorithm o0`) int r, g, b, y1192, y, i, uvp, u, v; for (int j = 0, yp = 0; j < height; j++) { uvp = frameSize + (j >> 1) * width; u = 0; v = 0; for (i = 0; i < width; i++, yp++) { y = (0xff & ((int) yuv420sp[yp])) - 16; if (y < 0) y = 0; if ((i & 1) == 0) { v = (0xff & yuv420sp[uvp++]) - 128; u = (0xff & yuv420sp[uvp++]) - 128; } y1192 = 1192 * y; r = (y1192 + 1634 * v); g = (y1192 - 833 * v - 400 * u); b = (y1192 + 2066 * u); // Java''s functions are faster then ''IFs'' r = Math.max(0, Math.min(r, 262143)); g = Math.max(0, Math.min(g, 262143)); b = Math.max(0, Math.min(b, 262143)); // rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & // 0xff00) | ((b >> 10) & 0xff); // rgba, divide 2^10 ( >> 10) rgba[yp] = ((r << 14) & 0xff000000) | ((g << 6) & 0xff0000) | ((b >> 2) | 0xff00); } } }

La velocidad es comparable a YuvImage.compressToJpeg () con ByteArrayOutputStream como salida (30-50 ms para imágenes de 640x480).

Resultado: Samsung S4 mini (2x1.7GHz) no puede comprimir a JPEG / convertir YUV a RGB en tiempo real (640x480 @ 30 fps)


La implementación de Java es 10 veces más lenta que la versión c, le sugiero que use la biblioteca GPUImage o simplemente mueva esta parte del código.

Hay una versión de Android de GPUImage: https://github.com/CyberAgent/android-gpuimage

Puede incluir esta biblioteca si usa gradle y llamar al método: GPUImageNativeLibrary.YUVtoRBGA (inputArray, WIDTH, HEIGHT, outputArray);

Comparo el tiempo, para una imagen NV21 que es 960x540, use el código java anterior, cuesta 200ms +, con la versión GPUImage, solo 10ms ~ 20ms.



Reparar el fragmento de código anterior

static public void decodeYUV420SP(int[] rgba, byte[] yuv420sp, int width, int height) { final int frameSize = width * height; int r, g, b, y1192, y, i, uvp, u, v; for (int j = 0, yp = 0; j < height; j++) { uvp = frameSize + (j >> 1) * width; u = 0; v = 0; for (i = 0; i < width; i++, yp++) { y = (0xff & ((int) yuv420sp[yp])) - 16; if (y < 0) y = 0; if ((i & 1) == 0) { // above answer is wrong at the following lines. just swap ***u*** and ***v*** u = (0xff & yuv420sp[uvp++]) - 128; v = (0xff & yuv420sp[uvp++]) - 128; } y1192 = 1192 * y; r = (y1192 + 1634 * v); g = (y1192 - 833 * v - 400 * u); b = (y1192 + 2066 * u); r = Math.max(0, Math.min(r, 262143)); g = Math.max(0, Math.min(g, 262143)); b = Math.max(0, Math.min(b, 262143)); // combine ARGB rgba[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) | 0xff); } } }