Зеленые изображения при выполнении кодировки 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;
}

Ответ 6

Работало ли указанное преобразование? потому что я попробовал это с помощью renderscript, скопировав первую и последнюю плоскость, и я все равно получил зеленое отфильтрованное изображение, подобное тому, которое было сверху.