Зеленые изображения при выполнении кодировки JPEG из YUV_420_888 с использованием новой камеры Android2 api
Я пытаюсь использовать новую api-камеру. Улавливание пакетов происходило слишком медленно, поэтому я использую формат YUV_420_888 в ImageReader и делаю JPEG, который позже будет показан, как было предложено в следующем сообщении:
Вспышка захвата камеры Android2 слишком медленная
Проблема в том, что я получаю зеленые изображения при попытке кодирования JPEG из YUV_420_888 с использованием RenderScript следующим образом:
RenderScript rs = RenderScript.create(mContext);
ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.RGBA_8888(rs));
Type.Builder yuvType = new Type.Builder(rs, Element.YUV(rs)).setX(width).setY(height).setYuvFormat(ImageFormat.YUV_420_888);
Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
in.copyFrom(data);
yuvToRgbIntrinsic.setInput(in);
yuvToRgbIntrinsic.forEach(out);
Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
out.copyTo(bmpout);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmpout.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] jpegBytes = baos.toByteArray();
переменная данных (данные YUV_420_888) получается из:
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
Что я делаю неправильно в кодировке JPEG, чтобы получить изображения только зеленым?
Заранее спасибо
Отредактировано: Это пример изображений зеленого цвета, которые я получаю:
https://drive.google.com/file/d/0B1yCC7QDeEjdaXF2dVp6NWV6eWs/view?usp=sharing
Ответы
Ответ 1
Итак, есть несколько уровней ответов на этот вопрос.
Во-первых, я не верю, что есть прямой способ скопировать изображение YUV_420_888 в RS Allocation, даже если выделение имеет формат YUV_420_888.
Итак, если вы не используете изображение для чего-либо еще, кроме этого шага кодирования JPEG, вы можете просто использовать выделение в качестве выхода для камеры напрямую, используя Распределение # getSurface и Распределение # ioReceive. Затем вы можете выполнить преобразование YUV- > RGB и прочитать растровое изображение.
Однако обратите внимание, что файлы JPEG под капотом фактически хранят данные YUV, поэтому, когда вы пытаетесь сжать JPEG, Bitmap собирается выполнить другое преобразование RGB- > YUV, поскольку оно сохраняет файл. Для максимальной эффективности вы должны напрямую передавать данные YUV в кодировщик JPEG, который может принять его, и полностью исключить дополнительные шаги преобразования. К сожалению, это невозможно с помощью общедоступных API-интерфейсов, поэтому вам придется отказаться от кода JNI и включить собственную копию libjpeg или эквивалентную библиотеку кодирования JPEG.
Если вам не нужно сохранять файлы JPEG очень быстро, вы можете swizle данные YUV_420_888 в байт NV21 [], а затем использовать YuvImage, хотя вам нужно обратить внимание на шаги в пикселях и рядах ваших данных YUV_420_888 и правильно сопоставьте его с NV21 - YUV_420_888 является гибким и может представлять несколько различных типов макетов памяти (включая NV21) и может отличаться на разных устройствах. Поэтому при изменении макета на NV21 важно убедиться, что вы правильно выполняете отображение.
Ответ 2
Мне удалось заставить это работать, ответ, предоставленный panonski, не совсем прав, и большая проблема заключается в том, что этот формат YUV_420_888
охватывает множество различных макетов памяти, где формат NV21
очень специфичен (я не знаю, я знаю, почему формат по умолчанию был изменен таким образом, для меня это не имеет смысла)
Обратите внимание, что этот метод может быть довольно медленным по нескольким причинам.
-
Поскольку NV21
чередует каналы цветности, а YUV_420_888
включает в себя форматы с непереплетенными каналами цветности, единственным надежным вариантом (который я знаю) является выполнение побайтовой копии. Мне интересно узнать, есть ли уловка, чтобы ускорить этот процесс, я подозреваю, что он есть. Я предоставляю вариант только в оттенках серого, потому что эта часть является очень быстрой копией строки за строкой.
-
При захвате кадров с камеры их байты будут отмечены как защищенные, что означает, что прямой доступ невозможен, и они должны быть скопированы для непосредственного управления.
-
Изображение, похоже, хранится в порядке обратного байта, поэтому после преобразования окончательный массив нужно отменить. Это может быть только моя камера, и я подозреваю, что здесь есть еще один трюк, который может ускорить это.
В любом случае вот код:
private byte[] getRawCopy(ByteBuffer in) {
ByteBuffer rawCopy = ByteBuffer.allocate(in.capacity());
rawCopy.put(in);
return rawCopy.array();
}
private void fastReverse(byte[] array, int offset, int length) {
int end = offset + length;
for (int i = offset; i < offset + (length / 2); i++) {
array[i] = (byte)(array[i] ^ array[end - i - 1]);
array[end - i - 1] = (byte)(array[i] ^ array[end - i - 1]);
array[i] = (byte)(array[i] ^ array[end - i - 1]);
}
}
private ByteBuffer convertYUV420ToN21(Image imgYUV420, boolean grayscale) {
Image.Plane yPlane = imgYUV420.getPlanes()[0];
byte[] yData = getRawCopy(yPlane.getBuffer());
Image.Plane uPlane = imgYUV420.getPlanes()[1];
byte[] uData = getRawCopy(uPlane.getBuffer());
Image.Plane vPlane = imgYUV420.getPlanes()[2];
byte[] vData = getRawCopy(vPlane.getBuffer());
// NV21 stores a full frame luma (y) and half frame chroma (u,v), so total size is
// size(y) + size(y) / 2 + size(y) / 2 = size(y) + size(y) / 2 * 2 = size(y) + size(y) = 2 * size(y)
int npix = imgYUV420.getWidth() * imgYUV420.getHeight();
byte[] nv21Image = new byte[npix * 2];
Arrays.fill(nv21Image, (byte)127); // 127 -> 0 chroma (luma will be overwritten in either case)
// Copy the Y-plane
ByteBuffer nv21Buffer = ByteBuffer.wrap(nv21Image);
for(int i = 0; i < imgYUV420.getHeight(); i++) {
nv21Buffer.put(yData, i * yPlane.getRowStride(), imgYUV420.getWidth());
}
// Copy the u and v planes interlaced
if(!grayscale) {
for (int row = 0; row < imgYUV420.getHeight() / 2; row++) {
for (int cnt = 0, upix = 0, vpix = 0; cnt < imgYUV420.getWidth() / 2; upix += uPlane.getPixelStride(), vpix += vPlane.getPixelStride(), cnt++) {
nv21Buffer.put(uData[row * uPlane.getRowStride() + upix]);
nv21Buffer.put(vData[row * vPlane.getRowStride() + vpix]);
}
}
fastReverse(nv21Image, npix, npix);
}
fastReverse(nv21Image, 0, npix);
return nv21Buffer;
}
Ответ 3
Если я правильно понял ваше описание, я вижу в вашем коде как минимум две проблемы:
-
Кажется, вы передаете только часть Y вашего изображения в код преобразования YUV- > RGB, потому что похоже, что вы используете только первую плоскость в ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
, игнорируя U и V.
-
Я еще не знаком с этими типами Renderscript, но похоже, что Element.RGBA_8888 и Bitmap.Config.ARGB_8888 относятся к небольшому различному порядку байтов, поэтому вам может понадобиться выполнить некоторую работу по переупорядочению.
Обе проблемы могут быть причиной зеленого цвета результирующего изображения.
Ответ 4
Вам нужно использовать RenderScript?
Если нет, вы можете преобразовать изображение с YUV на N21, а затем из N21 в JPEG без каких-либо причудливых структур.
Сначала вы берете плоскость 0 и 2, чтобы получить N21:
private byte[] convertYUV420ToN21(Image imgYUV420) {
byte[] rez = new byte[0];
ByteBuffer buffer0 = imgYUV420.getPlanes()[0].getBuffer();
ByteBuffer buffer2 = imgYUV420.getPlanes()[2].getBuffer();
int buffer0_size = buffer0.remaining();
int buffer2_size = buffer2.remaining();
rez = new byte[buffer0_size + buffer2_size];
buffer0.get(rez, 0, buffer0_size);
buffer2.get(rez, buffer0_size, buffer2_size);
return rez;
}
Затем вы можете использовать встроенный метод YuvImage для сжатия в JPEG. Аргументы w
и h
- это ширина и высота вашего файла изображения.
private byte[] convertN21ToJpeg(byte[] bytesN21, int w, int h) {
byte[] rez = new byte[0];
YuvImage yuv_image = new YuvImage(bytesN21, ImageFormat.NV21, w, h, null);
Rect rect = new Rect(0, 0, w, h);
ByteArrayOutputStream output_stream = new ByteArrayOutputStream();
yuv_image.compressToJpeg(rect, 100, output_stream);
rez = output_stream.toByteArray();
return rez;
}
Ответ 5
Это ответ/вопрос.
На нескольких подобных постах рекомендуется использовать этот script:
https://github.com/pinguo-yuyidong/Camera2/blob/master/camera2/src/main/rs/yuv2rgb.rs
Но я не знаю, как его использовать. Советы приветствуются.
Ответ 6
Работало ли указанное преобразование? потому что я попробовал это с помощью renderscript, скопировав первую и последнюю плоскость, и я все равно получил зеленое отфильтрованное изображение, подобное тому, которое было сверху.