Android ImageReader получает формат NV21?


У меня нет фона в изображении или графике, поэтому, пожалуйста, потерпите меня :)

Я использую JavaCV в одном из своих проектов. В примерах строится Frame, который имеет буфер определенного размера .

При использовании функции public void onPreviewFrame(byte[] data, Camera camera) В Android копирование этого массива байтов data не является проблемой, если вы объявите Frame как new Frame(frameWidth, frameHeight, Frame.DEPTH_UBYTE, 2);, где frameWidth и frameHeight объявлены как

Camera.Size previewSize = cameraParam.getPreviewSize();
int frameWidth = previewSize.width;
int frameHeight = previewSize.height;

Недавно Android добавил метод для захвата вашего экран. Естественно, я хотел захватить эти изображения, а также скрыть их в Frames. я модифицировалпример кода из Google , чтобы использоватьImageReader .

Это ImageReader строится как ImageReader.newInstance(DISPLAY_WIDTH, DISPLAY_HEIGHT, PixelFormat.RGBA_8888, 2);. Поэтому в настоящее время он использует пиксельный формат RGBA_8888. Я использую следующий код для копирования байтов в Frame, который создается как new Frame(DISPLAY_WIDTH, DISPLAY_HEIGHT, Frame.DEPTH_UBYTE, 2);:

ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
mImage.close();
((ByteBuffer) frame.image[0].position(0)).put(bytes);

Но это дает мне java.nio.BufferOverflowException. Я напечатал размеры обоих буферов, и размер буфера кадра равен 691200, тогда как bytes массив выше имеет размер 1413056. Выяснить, как строится это последнее число, не удалось, потому что я столкнулся с этим собственным вызовом. Так что ясно, что из этого ничего не выйдет.

Немного покопавшись, я обнаружил, что формат изображенияNV21 является "форматом по умолчанию для изображений предварительного просмотра камеры, если он не задан с помощью setPreviewFormat(int)", но классImageReader не поддерживает формат NV21 (см. параметр format). Так что это плохая примета. В в документации также говорится, что - Для андроида.аппаратура.camera2 API, формат YUV_420_888 рекомендуется для вывода YUV вместо этого."

Поэтому я попытался создать ImageReader, подобный этому ImageReader.newInstance(DISPLAY_WIDTH, DISPLAY_HEIGHT, ImageFormat.YUV_420_888, 2);, но это дает мне java.lang.UnsupportedOperationException: The producer output buffer format 0x1 doesn't match the ImageReader's configured buffer format 0x23., так что это тоже не сработает.

В качестве последнего средства я попытался преобразовать RGBA_8888 в YUV сам, используя, например, этот пост, но я не понимаю, как я могу получить int[] rgba в соответствии с ответом.

Итак, TL; DR как я могу получить изображение NV21 такие данные, как вы получаете в функции камеры Android public void onPreviewFrame(byte[] data, Camera camera), чтобы создать экземпляр my Frame и работать с ним с помощью ImageReader Android (и медиа-проекции)?

Edit (25-10-2016)

Я создал следующее преобразование runnable для перехода из формата RGBA в NV21:

private class updateImage implements Runnable {

    private final Image mImage;

    public updateImage(Image image) {
        mImage = image;
    }

    @Override
    public void run() {

        int mWidth = mImage.getWidth();
        int mHeight = mImage.getHeight();

        // Four bytes per pixel: width * height * 4.
        byte[] rgbaBytes = new byte[mWidth * mHeight * 4];
        // put the data into the rgbaBytes array.
        mImage.getPlanes()[0].getBuffer().get(rgbaBytes);

        mImage.close(); // Access to the image is no longer needed, release it.

        // Create a yuv byte array: width * height * 1.5 ().
        byte[] yuv = new byte[mWidth * mHeight * 3 / 2];
        RGBtoNV21(yuv, rgbaBytes, mWidth, mHeight);
        ((ByteBuffer) yuvImage.image[0].position(0)).put(yuv);
    }

    void RGBtoNV21(byte[] yuv420sp, byte[] argb, int width, int height) {
        final int frameSize = width * height;

        int yIndex = 0;
        int uvIndex = frameSize;

        int A, R, G, B, Y, U, V;
        int index = 0;
        int rgbIndex = 0;

        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {

                R = argb[rgbIndex++];
                G = argb[rgbIndex++];
                B = argb[rgbIndex++];
                A = argb[rgbIndex++]; // Ignored right now.

                // RGB to YUV conversion according to
                // https://en.wikipedia.org/wiki/YUV#Y.E2.80.B2UV444_to_RGB888_conversion
                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;

                // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor
                // of 2 meaning for every 4 Y pixels there are 1 V and 1 U.
                // Note the sampling is every other pixel AND every other scanline.
                yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
                if (i % 2 == 0 && index % 2 == 0) {
                    yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));
                    yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
                }
                index++;
            }
        }
    }
}

Объект yuvImage инициализируется как yuvImage = new Frame(DISPLAY_WIDTH, DISPLAY_HEIGHT, Frame.DEPTH_UBYTE, 2);, DISPLAY_WIDTH и DISPLAY_HEIGHT являются просто двумя целыми числами, задающими размер отображения. Это код, в котором фоновый обработчик обрабатывает onImageReady:

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {
            mBackgroundHandler.post(new updateImage(reader.acquireNextImage()));
        }

    };

...

mImageReader = ImageReader.newInstance(DISPLAY_WIDTH, DISPLAY_HEIGHT, PixelFormat.RGBA_8888, 2);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);

Методы работают, и я, по крайней мере, не получаю никаких ошибок, но выходное изображение искажено. Что происходит не так в моем обращении? Пример создаваемого образа:

пример искаженного изображения

Edit (15-11-2016)

Я изменил функцию RGBtoNV21 следующим образом:

void RGBtoNV21(byte[] yuv420sp, int width, int height) {
    try {
        final int frameSize = width * height;

        int yIndex = 0;
        int uvIndex = frameSize;
        int pixelStride = mImage.getPlanes()[0].getPixelStride();
        int rowStride = mImage.getPlanes()[0].getRowStride();
        int rowPadding = rowStride - pixelStride * width;
        ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();

        Bitmap bitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height, Bitmap.Config.ARGB_8888);

        int A, R, G, B, Y, U, V;
        int offset = 0;

        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {

                // Useful link: https://stackoverflow.com/questions/26673127/android-imagereader-acquirelatestimage-returns-invalid-jpg

                R = (buffer.get(offset) & 0xff) << 16;     // R
                G = (buffer.get(offset + 1) & 0xff) << 8;  // G
                B = (buffer.get(offset + 2) & 0xff);       // B
                A = (buffer.get(offset + 3) & 0xff) << 24; // A
                offset += pixelStride;

                int pixel = 0;
                pixel |= R;     // R
                pixel |= G;  // G
                pixel |= B;       // B
                pixel |= A; // A
                bitmap.setPixel(j, i, pixel);

                // RGB to YUV conversion according to
                // https://en.wikipedia.org/wiki/YUV#Y.E2.80.B2UV444_to_RGB888_conversion
//                        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;

                Y = (int) Math.round(R *  .299000 + G *  .587000 + B *  .114000);
                U = (int) Math.round(R * -.168736 + G * -.331264 + B *  .500000 + 128);
                V = (int) Math.round(R *  .500000 + G * -.418688 + B * -.081312 + 128);

                // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor
                // of 2 meaning for every 4 Y pixels there are 1 V and 1 U.
                // Note the sampling is every other pixel AND every other scanline.
                yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
                if (i % 2 == 0 && j % 2 == 0) {
                    yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));
                    yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
                }
            }
            offset += rowPadding;
        }

        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath(), "/Awesomebitmap.png");
        FileOutputStream fos = new FileOutputStream(file);
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
    } catch (Exception e) {
        Timber.e(e, "Converting image to NV21 went wrong.");
    }
}
Теперь изображение больше не искажено, но цветность отключена.

Неправильный цвет цветности

Правая сторона-это растровое изображение, которое создается в этой области. петля, левая сторона-это NV21, сохраненный в образе. Таким образом, пиксели RGB обрабатываются правильно. Очевидно, что цветность отключена, но преобразование RGB в YUV должно быть таким же, как показано в Википедии. Что здесь может быть не так?

1 7

1 ответ:

Вообще говоря, смысл ImageReader заключается в том, чтобы предоставить вам необработанный доступ к пикселям, отправленным на поверхность, с минимальными накладными расходами, поэтому пытаться заставить его выполнять цветовые преобразования не имеет смысла.

Для камеры вы можете выбрать один из двух выходных форматов (NV21 или YV12), поэтому выберите YV12. Это ваши сырые данные ЮВ. Для захвата экрана выход всегда будет RGB, поэтому вам нужно выбрать RGBA_8888 (формат 0x1) для вашего ImageReader, а не YUV_420_888 (формат 0x23) . Если вам нужен YUV для этого, вам придется сделать преобразование самостоятельно. ImageReader дает вам серию объектов Plane, а не byte[], поэтому вам нужно будет адаптироваться к этому.