web-dev-qa-db-fra.com

Conversion YUV-> RGB (traitement d'image) -> YUV pendant onPreviewFrame dans Android?

Je capture une image à l'aide de SurfaceView et j'obtiens des données d'aperçu Yuv Raw dans public void onPreviewFrame4 (byte [] data, Camera camera)

Je dois effectuer un prétraitement d'image dans onPreviewFrame, j'ai donc besoin de convertir les données d'aperçu Yuv en données RVB que le prétraitement d'image et de revenir en données Yuv.

J'ai utilisé les deux fonctions pour encoder et décoder les données Yuv en RVB 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 l'encodage et le décodage des données Yuv peuvent avoir une erreur car si je saute l'étape de prétraitement, les données Yuv encodées diffèrent également des données d'origine de PreviewCallback.

Veuillez m'aider à résoudre ce problème. Je dois utiliser ce code dans la numérisation OCR, j'ai donc besoin de mettre en œuvre ce type de logique.

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

Merci d'avance. :)

32
Hitesh Patel

Pourquoi ne pas spécifier que l'aperçu de la caméra doit fournir des images RVB?

c'est-à-dire Camera.Parameters.setPreviewFormat (ImageFormat.RGB_565) ;

17
Reuben Scratton

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, en pratique, vous avez souvent le choix entre un: NV21, un format YUV. Pour de nombreuses informations sur ce format, voir http://www.fourcc.org/yuv.php#NV21 et pour des informations sur la théorie derrière sa conversion en RVB, voir http: // www.fourcc.org/fccyvrgb.php . Il y a une explication basée sur l'image à Extraire l'image en noir et blanc du Android .

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

Cependant, une fois que vous avez configuré votre routine onPreviewFrame, la mécanique pour passer du tableau d'octets qu'il vous envoie à des données utiles est quelque peu, ummmm, peu claire. A partir de l'API 8, la solution suivante est disponible, pour accéder à un ByteStream en consolidant un JPEG de l'image (compressToJpeg est la seule option de conversion proposée 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 devra peut-être ensuite être converti au format souhaité. Si vous voulez un Bitmap:

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

Si, pour une raison quelconque, vous ne parvenez pas à le faire, vous pouvez effectuer la conversion manuellement. Quelques problèmes à surmonter pour ce faire:

  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 traité, le résultat est voué à avoir des effets d'écrêtage étranges.

  2. Les données sont dans un ordre très spécifique (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 une image bitmap, par exemple. Cela nécessite également une approche plutôt désordonnée (à mon avis) de la création d'un tampon des données, puis du remplissage d'un bitmap à partir de celui-ci.

  4. Si vous avez réellement NV12 (ou 420SP), vous devrez permuter les lectures pour U et V.

Je présente une solution (qui semble fonctionner), avec des demandes de corrections, des améliorations et des moyens de rendre le tout moins coûteux à exécuter. Il crée un bitmap de la taille de l'image d'aperçu:

La variable de données provient 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 le souligne @Timmmm ci-dessous, vous pouvez effectuer la conversion en entier en multipliant les facteurs d'échelle par 1000 (c'est-à-dire que 1,164 devient 1164), puis en divisant les résultats finaux par 1000.

39
Neil Townsend

Après quelques tests sur le mini code Samsung S4, le plus rapide est (120% plus rapide que celui de Neil [flotte!] Et 30% plus rapide que celui de Hitesh d'origine):

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 est comparable à YuvImage.compressToJpeg () avec ByteArrayOutputStream en sortie (30-50 ms pour une image 640x480).

Résultat: Samsung S4 mini (2x1.7GHz) ne peut pas compresser en JPEG/convertir YUV en RVB en temps réel (640x480 @ 30fps)

5
JerzySkalski

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

Il existe une Android de GPUImage: https://github.com/CyberAgent/Android-gpuimage

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

Je compare le temps, pour une image NV21 qui est 960x540, utilisez ci-dessus Java, cela coûte 200 ms +, avec la version GPUImage, seulement 10 ms ~ 20 ms.

4
promenade

Essayez RenderScript ScriptIntrinsicYuvToRGB, fourni avec JellyBean 4.2 (Api 17+).

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

Sur Nexus 7 (2013, JellyBean 4.3), une conversion d'image de 1920 x 1080 (aperçu de la caméra Full HD) prend environ 7 ms.

1
Matti81

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
HC_ZZ

Vous pouvez utiliser RenderScript -> ScriptIntrinsicYuvToRGB

Échantillon 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)
1
murgupluoglu