Хотя в документации предлагается указать, в каком формате данные изображения должны поступать с камеры, на практике у вас часто есть один из них: 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);
Если по какой-либо причине вы не можете это сделать, вы можете выполнить преобразование вручную. Некоторые проблемы, которые необходимо преодолеть при этом:
Данные поступают в байтовом массиве. По определению, байты являются числами со знаком, что означает, что они идут от -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.