Преобразование 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 > 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  262143) r = 262143; if (g  262143) g = 262143; if (b  262143) b = 262143; // rgb[yp] = 0xff000000 | ((r <> 2) & // 0xff00) | ((b >> 10) & 0xff); // rgba, divide 2^10 ( >> 10) rgba[yp] = ((r << 14) & 0xff000000) | ((g <> 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 > 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  255) ? 255 : y)); U[index] = u; V[index++] = v; } } 

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

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

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

Заранее спасибо. 🙂

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

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

Несмотря на то, что в документации указано, что вы можете установить формат, с которого должны поступать данные изображения с камеры, на практике у вас часто есть выбор: NV21, формат YUV. Для получения большей информации об этом формате см. http://www.fourcc.org/yuv.php#NV21, а также информацию о теории преобразования ее в RGB см. http://www.fourcc.org/fccyvrgb.php . Существует иллюстрация, основанная на изображении, на черно-белом изображении Extract из формата 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 можно преобразовать в желаемый формат. Если вы хотите bitmap:

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

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

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

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

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

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

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

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

После нескольких тестов на самый быстрый код Samsung S4 mini (на 120% быстрее, чем Neil’s [плавает!] И на 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'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 мс для изображения 640×480).

Результат: Samsung S4 mini (2×1.7GHz) не может сжимать JPEG / конвертировать YUV в RGB в реальном времени (640×480 @ 30 кадров в секунду)

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

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

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

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

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

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

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

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

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

  • Преобразование RGB в черно-белый в OpenCV
  • Как создать и применить фильтр Гаусса в MATLAB без использования fspecial, imfilter или conv2?
  • преобразование строки базы 64 в изображение и сохранение ее
  • Как я могу нарисовать изображение программно на iPhone?
  • масштаб и rotation Соответствие шаблонов
  • Быстрое размытие растровых изображений для Android SDK
  • Перестановка шумовой монеты в круглую форму
  • Есть ли способ обнаружить, что изображение размыто?
  • Ширина windows и расчет центра изображения DICOM
  • Как определить текстовую область из изображения?
  • Как обрезать изображение с помощью C #?
  • Давайте будем гением компьютера.