Conversion YUV - >RGB (traitement D'Image) - > YUV pendant onPreviewFrame dans android?

je capture des images en utilisant SurfaceView et je reçois des données brutes de prévisualisation de Yuv dans public void onPreviewFrame4 (byte[] data, Camera camera)

je dois effectuer un prétraitement d'image dans onPreviewFrame donc je dois convertir les données de prévisualisation Yuv en données RGB que prétraitement d'image et retour aux données Yuv.

j'ai utilisé les deux fonctions pour encoder et décoder les données Yuv à RGB comme suit :

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;
        }
    }

le problème est que le codage et le décodage des données Yuv pourrait avoir une certaine erreur parce que si je passe l'étape de prétraitement que les données Yuv codées aussi sont différentes des données originales de PreviewCallback.

Aidez-moi à résoudre ce problème. Je dois utiliser ce code dans le scan OCR donc j'ai besoin d'implémenter ce type de logique.

s'il y a une autre façon de faire la même chose que s'il vous plaît me fournir.

Merci d'avance. :)

29
demandé sur Hitesh Patel 2012-02-17 13:36:17

7 réponses

pourquoi ne pas spécifier que la prévisualisation de la caméra devrait fournir des images RGB?

c'est à dire de la Caméra.Paramètre.setPreviewFormat (ImageFormat.RGB_565) ;

17
répondu Reuben Scratton 2012-02-17 15:01:19

bien que la documentation suggère que vous pouvez définir le format dans lequel les données d'image doivent arriver de la caméra, dans la pratique, vous avez souvent le choix d'un: NV21, un format YUV. Pour beaucoup d'information sur ce format voir http://www.fourcc.org/yuv.php#NV21 et pour des informations sur la théorie de sa conversion en RVB, voir http://www.fourcc.org/fccyvrgb.php . Il y a une explication basée sur l'image à extrait noir et image en blanc du format nv21 de la caméra android .

un autre format, appelé YUV420SP, est également très répandu.

cependant, une fois que vous avez mis en place votre routine onPreviewFrame, la mécanique de passer du tableau octet qu'il vous envoie aux données utiles est quelque peu, ummmm, peu claire. A partir de L'API 8, la solution suivante est disponible, pour obtenir un ByteStream avec un JPEG de l'image (compressToJpeg est la seule option de conversion offerte par 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);

ce JPEG peut alors avoir besoin d'être converti dans le format que vous voulez. Si vous voulez un Bitmap:

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

si, pour une raison quelconque, vous n'êtes pas en mesure de le faire, vous pouvez effectuer la conversion manuellement. Certains problèmes à surmonter en faisant ceci:

  1. Les données arrivent dans un tableau d'octets. Par définition, les octets sont des nombres signés, ce qui signifie qu'ils vont de -128 à 127. Cependant, les données sont en fait des octets non signés (0 à 255). Si cela n'est pas réglé, le résultat est condamné à avoir des effets de coupure étranges.

  2. les données sont dans un ordre très précis (selon la page Web mentionnée précédemment) et chaque pixel doit être extrait avec soin.

  3. chaque pixel doit être placé au bon endroit sur un bitmap, disons. Cela nécessite également une approche assez désordonnée (à mon avis) de construire un tampon des données et ensuite remplir un bitmap à partir de celui-ci.

  4. si vous avez réellement NV12( ou 420SP), alors vous aurez besoin d'échanger les lectures pour U et V.

je présente une solution (qui semble fonctionner), avec des demandes de corrections, des améliorations et des façons de rendre l'ensemble moins coûteux à exécuter. Il crée un bitmap de la taille de l'image de prévisualisation:

les données variable vient de l'appel à 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);

comme @Timmmm le souligne ci-dessous, vous pouvez faire la conversion en int en multipliant les facteurs d'échelle par 1000 (C.-à-d. 1.164 devient 1164) et puis en divisant les résultats finaux par 1000.

37
répondu Neil Townsend 2017-05-23 12:26:05

après quelques essais sur Samsung S4 mini code le plus rapide est (120% plus rapide que Neil's [flotte!] et 30% plus rapide que d'origine Hitesh):

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 vitesse de

est comparable à celle de YuvImage.compressToJpeg () avec ByteArrayOutputStream comme Sortie (30-50 ms pour l'image 640x480).

Résultat: Samsung S4 mini (2x1.7GHz) ne peut compresser des fichiers au format JPEG/conversion YUV vers RGB en temps réel (640x480@30fps)

5
répondu JerzySkalski 2015-01-30 21:31:02

l'implémentation Java est 10 fois plus lente que la version c, je vous suggère D'utiliser la bibliothèque GPUImage ou tout simplement déplacer cette partie du code.

il y a une version android de GPUImage: https://github.com/CyberAgent/android-gpuimage

vous pouvez inclure cette bibliothèque Si vous utilisez gradle, et appelez la méthode: Gpuimagenativelibraire.YUVtoRBGA (inputArray, WIDTH, HEIGHT, outputArray);

je compare le temps, pour une image NV21 qui est 960x540, utiliser au-dessus du code java, il a coûté 200ms+, avec la version GPUImage, seulement 10ms~20ms.

4
répondu promenade 2016-04-12 06:30:40

Correction de l'extrait de code ci-dessus

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);
        }
    }
}
1
répondu HC_ZZ 2016-12-21 06:17:08

Essayer RenderScript ScriptIntrinsicYuvToRGB, qui est livré avec JellyBean 4.2 Api (17+).

https://developer.android.com/reference/android/renderscript/ScriptIntrinsicYuvToRGB.html

Sur la Nexus 7 (2013, JellyBean 4.3) 1920x1080, la conversion d'image (full HD de prévisualisation de la caméra) prend environ 7 ms.

0
répondu Matti81 2017-04-21 20:55:13

vous pouvez utiliser RenderScript - > ScriptIntrinsicYuvToRGB

Échantillon De Kotlin

val rs = RenderScript.create(CONTEXT_HERE)
val yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))

val yuvType = Type.Builder(rs, Element.U8(rs)).setX(byteArray.size)
val inData = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT)

val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height)
val outData = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT)

inData.copyFrom(byteArray)

yuvToRgbIntrinsic.setInput(inData)
yuvToRgbIntrinsic.forEach(outData)

val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
outData.copyTo(bitmap)
0
répondu murgupluoglu 2018-10-05 13:57:54