Domanda

Sto catturando l'immagine usando SurfaceView e ottenendo i dati di anteprima RAW YUV in public void onPreviewFrame4 (byte [] dati, fotocamera)

Devo eseguire una preelaborazione dell'immagine in OnPreviewFrame, quindi devo convertire i dati di anteprima YUV in dati RGB rispetto alla preelaborazione delle immagini e tornare ai dati YUV.

Ho usato sia la funzione per la codifica e la decodifica dei dati YUV in RGB come segue:

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

Il problema è che la codifica e la decodifica dei dati YUV potrebbero avere qualche errore perché se salto la fase di preelaborazione rispetto anche i dati YUV codificati sono differenti dai dati originali di anteprimaCallback.

Per favore aiutami a risolvere questo problema. Devo utilizzare questo codice nella scansione OCR, quindi devo implementare questo tipo di logica.

In caso di altro modo di fare la stessa cosa che, per favore, fornitemi.

Grazie in anticipo. :)

È stato utile?

Soluzione

Perché non specificare che l'anteprima della fotocamera dovrebbe fornire immagini RGB?

cioè Fotocamera.parameters.setPreviewFormat (ImageFormat.rgb_565);

Altri suggerimenti

Sebbene la documentazione suggerisca che è possibile impostare quale formattare i dati dell'immagine dovrebbero arrivare dalla fotocamera, in pratica hai spesso una scelta di uno: NV21, un formato YUV. Per molte informazioni su questo formato vedi http://www.fourcc.org/yuv.php#nv21 e per informazioni sulla teoria alla base della conversione in RGB see http://www.fourcc.org/fccyvrgb.php. C'è una spiegazione basata sull'immagine a Estrai l'immagine in bianco e nero dal formato NV21 della fotocamera Android.

Un altro formato, chiamato YUV420SP, è anche abbastanza diffuso.

Tuttavia, una volta impostata la routine OnPreviewFrame, la meccanica di passare dall'array di byte ti invia a dati utili è in qualche modo, ummmm, poco chiaro. Dall'API 8 in poi, è disponibile la seguente soluzione, per arrivare a un bytesteam che affligge un jpeg dell'immagine (CompressTojpeg è l'unica opzione di conversione offerta da 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);

Questo JPEG potrebbe quindi dover essere convertito nel formato desiderato. Se vuoi una bitmap:

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

Se, per qualsiasi motivo, non sei in grado di farlo, puoi fare la conversione manualmente. Alcuni problemi da superare nel fare questo:

  1. I dati arrivano in un array di byte. Per definizione, i byte sono numeri firmati, il che significa che passano da -128 a 127. Tuttavia, i dati sono effettivamente byte non firmati (da 0 a 255). Se questo non è trattato, il risultato è condannato ad avere alcuni strani effetti di ritaglio.

  2. I dati sono in un ordine molto specifico (secondo la pagina Web precedentemente menzionata) e ogni pixel deve essere estratto attentamente.

  3. Ogni pixel deve essere messo nel posto giusto su una bitmap, diciamo. Ciò richiede anche un approccio piuttosto disordinato (a mio avviso) di costruire un buffer dei dati e quindi riempire una bitmap da esso.

  4. Se hai effettivamente NV12 (o 420SP), dovrai scambiare le letture per U e V.

Presento una soluzione (che sembra funzionare), con richieste di correzioni, miglioramenti e modi per rendere il tutto meno costoso da eseguire. Crea una bitmap delle dimensioni dell'immagine di anteprima:

La variabile dati proviene dalla chiamata 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);

Come sottolinea @Timmmm di seguito, è possibile eseguire la conversione in int moltiplicando i fattori di ridimensionamento di 1000 (cioè 1.164 diventa 1164) e quindi dividersi i risultati finali di 1000.

Dopo alcuni test sul codice più veloce Samsung S4 Mini è (il 120% più veloce di Neil [galleggia!] E il 30% più veloce di quello originale):

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 velocità è paragonabile a YuvImage.compressTojpeg () Con ByteArrayOutputStream come output (30-50 ms per l'immagine 640x480).

Risultato: Samsung S4 Mini (2x1.7GHz) non può comprimere a JPEG/Converti Yuv in RGB in tempo reale (640x480@30fps)

L'implementazione di Java è 10 volte lenta della versione C, ti suggerisco di utilizzare la libreria GPUImage o semplicemente spostare questa parte del codice.

C'è una versione Android di GPUImage:https://github.com/cyberagent/android-gpuimage

È possibile includere questa libreria se si utilizza Gradle e chiamare il metodo: gpuimagenatiliverary.yuvtorbga (inputArray, larghezza, altezza, outputArray);

Confronto il tempo, per un'immagine NV21 che è 960x540, uso sopra il codice Java, costa 200ms+, con versione GPUImage, solo 10 ms ~ 20ms.

È possibile utilizzare Renderscript -> ScriptInsicyuvtorGB

Campione 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)

Correggi lo snippet di codice sopra

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

Prova Renderscript ScriptInsicyuvtorGB, che viene fornito con Jellybean 4.2 (API 17+).

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

Su Nexus 7 (2013, Jellybean 4.3) una conversione di immagini 1920x1080 (anteprima della fotocamera HD) richiede circa 7 ms.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top