Конвертировать Android-камеру2 api YUV_420_888 в RGB

Я пишу приложение, которое берет канал камеры, преобразует его в rgb, чтобы выполнить некоторую обработку.

Он отлично работает на старой версии камеры, которая использует формат NV21 Yuv. Проблема, с которой я столкнулась, - это новый формат Yuv, YUV_420_888. Изображение больше не преобразуется правильно в RGB в новой Camera2 Api, которая отправляет формат YUV_420_888 yuv вместо формата NV21 (YUV_420_SP).

Может кто-нибудь скажет мне, как мне преобразовать YUV_420_888 в RGB?

Спасибо

Ответ 1

В моем подходе я использую OpenCV Mat и script от https://gist.github.com/camdenfullmer/dfd83dfb0973663a7974

Прежде всего, вы конвертируете изображение YUV_420_888 в Mat с кодом в приведенной выше ссылке.

* mImage - это мой объект Image, который я получаю в ImageReader.OnImageAvailableListener

Mat mYuvMat = imageToMat(mImage);

public static Mat imageToMat(Image image) {
    ByteBuffer buffer;
    int rowStride;
    int pixelStride;
    int width = image.getWidth();
    int height = image.getHeight();
    int offset = 0;

    Image.Plane[] planes = image.getPlanes();
    byte[] data = new byte[image.getWidth() * image.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
    byte[] rowData = new byte[planes[0].getRowStride()];

    for (int i = 0; i < planes.length; i++) {
        buffer = planes[i].getBuffer();
        rowStride = planes[i].getRowStride();
        pixelStride = planes[i].getPixelStride();
        int w = (i == 0) ? width : width / 2;
        int h = (i == 0) ? height : height / 2;
        for (int row = 0; row < h; row++) {
            int bytesPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8;
            if (pixelStride == bytesPerPixel) {
                int length = w * bytesPerPixel;
                buffer.get(data, offset, length);

                if (h - row != 1) {
                    buffer.position(buffer.position() + rowStride - length);
                }
                offset += length;
            } else {


                if (h - row == 1) {
                    buffer.get(rowData, 0, width - pixelStride + 1);
                } else {
                    buffer.get(rowData, 0, rowStride);
                }

                for (int col = 0; col < w; col++) {
                    data[offset++] = rowData[col * pixelStride];
                }
            }
        }
    }

    Mat mat = new Mat(height + height / 2, width, CvType.CV_8UC1);
    mat.put(0, 0, data);

    return mat;
}

У нас есть 1 канал YUV Mat. Определите новый Mat для изображения BGR (пока еще не RGB):

Mat bgrMat = new Mat(mImage.getHeight(), mImage.getWidth(),CvType.CV_8UC4);

Я только начал изучать OpenCV таким образом, что это не обязательно должно быть 4-канальным Mat и вместо этого может быть 3-канальным, но это работает для меня. Теперь я использую метод преобразования цвета для изменения моего yuv Mat в bgr Mat.

Imgproc.cvtColor(mYuvMat, bgrMat, Imgproc.COLOR_YUV2BGR_I420);

Теперь мы можем выполнять всю обработку изображений, например, поиск контуров, цветов, кругов и т.д. Чтобы распечатать изображение на экране, нам нужно преобразовать его в растровое изображение:

Mat rgbaMatOut = new Mat();
Imgproc.cvtColor(bgrMat, rgbaMatOut, Imgproc.COLOR_BGR2RGBA, 0);
final Bitmap bitmap = Bitmap.createBitmap(bgrMat.cols(), bgrMat.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(rgbaMatOut, bitmap);

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

runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if(bitmap != null) {
                            mImageView.setImageBitmap(bitmap);
                        }
                    }
                });

Ответ 2

Camera2 YUV_420_888 в RGB Mat (opencv) в Java

@Override
    public void onImageAvailable(ImageReader reader){
        Image image = null;

        try {
            image = reader.acquireLatestImage();
            if (image != null) {

                byte[] nv21;
                ByteBuffer yBuffer = mImage.getPlanes()[0].getBuffer();
                ByteBuffer uBuffer = mImage.getPlanes()[1].getBuffer();
                ByteBuffer vBuffer = mImage.getPlanes()[2].getBuffer();

                int ySize = yBuffer.remaining();
                int uSize = uBuffer.remaining();
                int vSize = vBuffer.remaining();

                nv21 = new byte[ySize + uSize + vSize];

                //U and V are swapped
                yBuffer.get(nv21, 0, ySize);
                vBuffer.get(nv21, ySize, vSize);
                uBuffer.get(nv21, ySize + vSize, uSize);

                Mat mRGB = getYUV2Mat(nv21);



            }
        } catch (Exception e) {
            Log.w(TAG, e.getMessage());
        }finally{
            image.close();// don't forget to close
        }
    }



    public Mat getYUV2Mat(byte[] data) {
    Mat mYuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CV_8UC1);
    mYuv.put(0, 0, data);
    Mat mRGB = new Mat();
    cvtColor(mYuv, mRGB, Imgproc.COLOR_YUV2RGB_NV21, 3);
    return mRGB;
}

Ответ 4

Примерно в 10 раз быстрее, чем упомянутая выше "imageToMat" -функция выше, это код:

Image image = reader.acquireLatestImage();
...
Mat yuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CvType.CV_8UC1);
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
final byte[] data = new byte[buffer.limit()];
buffer.get(data);
yuv.put(0, 0, data);
...
image.close();

Ответ 5

Таким образом, я столкнулся с той же самой проблемой, где у меня был код, который взял старые данные формата byte [] YUV_420_SP из OnPreviewFrame() и преобразовал их в RGB.

Ключевым моментом здесь является то, что "старые" данные в байте [] имеют вид YYYYYY... CrCbCrCbCrCb, а "новые" данные из Camera2 API делятся на 3 плоскости: 0 = Y, 1 = Cb, 2 = Cr., откуда вы можете получить каждый байт [] s. Итак, все, что вам нужно сделать, это переупорядочить новые данные в виде одного массива, который соответствует "старому" формату, который вы можете передать существующим функциям toRGB():

Image.Plane[] planes = image.getPlanes(); // in YUV220_888 format
int acc = 0, i;
ByteBuffer[] buff = new ByteBuffer[planes.length];

for (i = 0; i < planes.length; i++) {
   buff[i] = planes[i].getBuffer();
   acc += buff[i].capacity();
}
byte[] data = new byte[acc],
   tmpCb = new byte[buff[1].capacity()] , tmpCr = new byte[buff[2].capacity()];

buff[0].get(data, 0, buff[0].capacity()); // Y
acc = buff[0].capacity();

buff[2].get(tmpCr, 0, buff[2].capacity()); // Cr
buff[1].get(tmpCb, 0, buff[1].capacity()); // Cb

for (i=0; i<tmpCb.length; i++) {
    data[acc] = tmpCr[i];
    data[acc + 1] = tmpCb[i];
    acc++;
}

..и теперь данные [] форматируются так же, как старый YUV_420_SP.

(надеюсь, что это кому-то поможет, несмотря на прошедшие годы..)

Ответ 6

Использовать Shyam Kumar ответ не подходит для моего телефона, но Даниэль Вичек прав. Я отлаживаю его, найдите плоскости [i].getRowStride() - 1216, плоскости [i].getPixelStride() - 2. В то время как ширина и высота изображения равна оба 1200.

Потому что моя репутация 3, поэтому я не могу комментировать, но опубликовать ответ.