将Android camera2 api YUV_420_888转换为RGB

时间:2015-05-28 15:28:48

标签: android android-camera android-5.0-lollipop rgb yuv

我正在编写一个带摄像头的应用程序,将其转换为rgb,以便进行一些处理。

它适用于使用NV21 Yuv格式的旧相机实现。 我遇到的问题是使用新的Yuv格式YUV_420_888。在新的Camera2 Api中,图像不再正确转换为RGB,后者发送YUV_420_888 yuv格式而不是NV21(YUV_420_SP)格式。

有人可以告诉我应该如何将YUV_420_888转换为RGB?

由于

7 个答案:

答案 0 :(得分:8)

在我的方法中,我使用OpenCV Mat和脚本 https://gist.github.com/camdenfullmer/dfd83dfb0973663a7974

首先,使用上面链接中的代码将YUV_420_888图像转换为Mat。

* mImage是我在ImageReader.OnImageAvailableListener中获得的Image对象

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垫。为BGR定义新的Mat(不是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我需要在UI线程上执行此操作。

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

答案 1 :(得分:1)

您是否尝试过使用此脚本?它是yydcdutthis问题

上发布的答案

https://github.com/pinguo-yuyidong/Camera2/blob/master/camera2/src/main/rs/yuv2rgb.rs

答案 2 :(得分:1)

Camera2 YUV_420_888到Java中的RGB Mat(opencv)

@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;
}

答案 3 :(得分:0)

比上面提到的&#34; imageToMat&#34;快了大约10倍 - 上面的代码就是这段代码:

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();

答案 4 :(得分:0)

所以我遇到了完全相同的问题,我有一个代码从OnPreviewFrame()获取旧的YUV_420_SP格式byte []数据并将其转换为RGB。

他们关键的是“老”&#39; byte []中的数据为YYYYYY ... CrCbCrCbCrCb,以及&#39; new&#39;来自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++;
}

..现在data []的格式与旧的YUV_420_SP一样。

(希望它可以帮助某人,尽管多年过去了。)

答案 5 :(得分:0)

使用Shyam Kumar的答案不适合我的手机,但DanielWięcek的答案正确。我对其进行调试,找到planes [i] .getRowStride()为1216,planes [i] .getPixelStride()为2。图像宽度和高度均为1200。

因为我的声誉是3,所以我无法发表评论,只能发表答案。

答案 6 :(得分:0)

由于您尚未将此问题标记为javandk。我的回复实现是从ndkc++的角度来看的:

我知道您想将AIMAGE_FORMAT_YUV_420_888转换为RGB b位。相反,如果在调用AIMAGE_FORMAT_RGB_888时要求AIMAGE_FORMAT,则可以直接获得AImageReader_new()格式。

/* 
 * Callback when image is available for processing 
 */
static void imageCallback(void* context, AImageReader* reader)
{
    AImage *image = nullptr;
    auto status = AImageReader_acquireNextImage(reader, &image);
    // Check status here ...

    // Try to process data without blocking the callback
    std::thread processor([=](){

        uint8_t *data = nullptr;
        int len = 0;
        AImage_getPlaneData(image, 0, &data, &len);

        // Process data here
        // ...

        AImage_delete(image);
    });
    processor.detach();
}

/*
 * Create RGB reader 
 */
AImageReader* createRGBReader()
{
    AImageReader* reader = nullptr;
    media_status_t status = AImageReader_new(640, 480, AIMAGE_FORMAT_RGB_888,
                     4, &reader);

    //if (status != AMEDIA_OK)
        // Handle errors here

    AImageReader_ImageListener listener{
            .context = nullptr,
            .onImageAvailable = imageCallback,
    };

    AImageReader_setImageListener(reader, &listener);

    return reader;
}

/* 
 * Create the surface for this reader
 */
ANativeWindow* createSurface(AImageReader* reader) 
{
    ANativeWindow *nativeWindow;
    AImageReader_getWindow(reader, &nativeWindow);

    return nativeWindow;
}


...

createSession()
{
    ...
    createSurface(createRGBReader());
    ...
}