Подтвердить что ты не робот

Преобразование YUV-> RGB (Обработка изображений) → YUV во время onPreviewFrame в android?

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

Мне нужно выполнить некоторую предварительную обработку изображения в 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.

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

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

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

4b9b3361

Ответ 2

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

Другой формат, называемый 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);

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

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

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

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

  • Если у вас есть 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.

Ответ 3

После нескольких тестов на самый быстрый код Samsung S4 mini (на 120% быстрее, чем Neil [плавает!] и на 30% быстрее, чем оригинальные Hitesh's):

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 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.7GHz) не может сжимать JPEG/конвертировать YUV в RGB в реальном времени (640x480 @30 кадров в секунду)

Ответ 4

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

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

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

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

Ответ 5

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

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

Ответ 7

Вы можете использовать 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)