Преобразование YUV-> RGB (Обработка изображений) -> YUV во время onPreviewFrame в Android? - PullRequest
32 голосов
/ 17 февраля 2012

Я снимаю изображение с помощью SurfaceView и получаю данные предварительного просмотра Yuv Raw в public void onPreviewFrame4 (byte [] data, Camera camera)

Мне нужно выполнить некоторую предварительную обработку изображения в onPreviewFrame, чтобымне нужно преобразовать данные предварительного просмотра Yuv в данные RGB, а не предварительную обработку изображений и обратно в данные Yuv.

Я использовал обе функции для кодирования и декодирования данных Yuv в RGB следующим образом:

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

Проблема заключается в том, что при кодировании и декодировании данных Yuv может возникнуть некоторая ошибка, потому что если я пропущу этап предварительной обработки, то и кодированные данные Yuv отличаются от исходных данных PreviewCallback.

Пожалуйста, помогите мне решить эту проблему.Я должен использовать этот код при сканировании оптического распознавания текста, поэтому мне нужно реализовать логику такого типа.

Если есть какой-либо другой способ сделать то же самое, чем, пожалуйста, предоставьте мне.

Заранее спасибо.:)

Ответы [ 7 ]

39 голосов
/ 12 апреля 2012

Хотя в документации предлагается указать, в каком формате данные изображения должны поступать с камеры, на практике у вас часто есть один из них: NV21, формат YUV. Для получения дополнительной информации об этом формате см. http://www.fourcc.org/yuv.php#NV21, а информацию о теории преобразования его в RGB см. http://www.fourcc.org/fccyvrgb.php.. Объяснение на основе рисунка см. На Извлечение черно-белого изображения с камеры Android NV21 формат .

Другой формат, называемый YUV420SP, также широко распространен.

Однако, после того, как вы настроили подпрограмму onPreviewFrame, механизм перехода от байтового массива, который она отправляет вам к полезным данным, несколько неясен. Начиная с API 8, доступно следующее решение для доступа к ByteStream, содержащему JPEG изображения (compressToJpeg - единственный вариант преобразования, предлагаемый 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);

Этот JPEG может потребоваться преобразовать в нужный вам формат. Если вы хотите растровое изображение:

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

Если по какой-либо причине вы не можете это сделать, вы можете выполнить преобразование вручную. Некоторые проблемы, которые необходимо преодолеть при этом:

  1. Данные поступают в байтовом массиве. По определению, байты являются числами со знаком, что означает, что они идут от -128 до 127. Однако данные на самом деле являются байтами без знака (от 0 до 255). Если с этим не справиться, результат обречен на некоторые странные эффекты отсечения.

  2. Данные располагаются в очень конкретном порядке (согласно ранее упомянутой веб-странице), и каждый пиксель необходимо тщательно извлекать.

  3. Каждый пиксель должен быть помещен в правильное место на растровом изображении, скажем. Это также требует довольно грязного (на мой взгляд) подхода построения буфера данных и последующего заполнения из него растрового изображения.

  4. Если вы на самом деле получили NV12 (или 420SP), вам нужно поменять показания для U и V.

Я представляю решение (которое, кажется, работает) с запросами на исправления, улучшения и способы сделать все это менее затратным для выполнения. Создается растровое изображение размера предварительного изображения:

Переменная данных поступает из вызова 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);

Как указывает @Timmmm ниже, вы можете выполнить преобразование в int, умножив коэффициенты масштабирования на 1000 (т. Е. 1.164 становится 1164), а затем разделив конечные результаты на 1000.

17 голосов
/ 17 февраля 2012

Почему бы не указать, что предварительный просмотр камеры должен обеспечивать изображения RGB?

т.е. Camera.Parameters.setPreviewFormat (ImageFormat.RGB_565) ;

5 голосов
/ 31 января 2015

После некоторых тестов на Samsung S4 mini самый быстрый код (на 120% быстрее, чем Нил [плавает!] И на 30% быстрее, чем оригинальный 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);
            }
        }
    }

Скорость сравнима с YuvImage.compressToJpeg () с ByteArrayOutputStream в качестве вывода (30-50 мс для изображения 640x480).

Результат: Samsung S4 mini (2x1,7 ГГц) не может сжимать в JPEG / конвертировать YUV в RGB в режиме реального времени (640x480 при 30 кадр / с)

4 голосов
/ 12 апреля 2016

Реализация Java в 10 раз медленнее, чем версия c, я предлагаю вам использовать библиотеку GPUImage или просто переместить эту часть кода.

Существует версия GPUImage для Android: https://github.com/CyberAgent/android-gpuimage

Вы можете включить эту библиотеку, если используете gradle, и вызвать метод: GPUImageNativeLibrary.YUVtoRBGA (inputArray, WIDTH, HEIGHT, outputArray);

Я сравниваю время для изображения NV21, которое составляет 960x540, используйте вышеJava-код, он стоит 200 мс +, с версией GPUImage, всего 10 мс ~ 20 мс.

1 голос
/ 05 октября 2018

Вы можете использовать RenderScript -> ScriptIntrinsicYuvToRGB

Образец Котлина

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 голос
/ 21 апреля 2017

Попробуйте RenderScript ScriptIntrinsicYuvToRGB, который поставляется с JellyBean 4.2 (API 17 +).

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

На Nexus 7 (2013, JellyBean 4.3) преобразование изображения 1920x1080 (предварительный просмотр камеры в формате HD) занимает около 7 мс.

1 голос
/ 20 августа 2015

Исправьте приведенный выше фрагмент кода

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