将 Android camera2 api YUV_420_888 转换为 RGB

Posted

技术标签:

【中文标题】将 Android camera2 api YUV_420_888 转换为 RGB【英文标题】:Convert Android camera2 api YUV_420_888 to RGB 【发布时间】:2015-08-11 05:15:41 【问题描述】:

我正在编写一个应用程序,该应用程序获取相机信息,将其转换为 rgb,以便进行一些处理。

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

谁能告诉我应该如何将 YUV_420_888 转换为 RGB?

谢谢

【问题讨论】:

那是针对较旧的相机实施,这对我没有帮助。不过谢谢。 有人把YUV_420_888转成NV21(YUV_420_SP)吗? @ConstantinGeorgiu,上述问题你解决了吗? 我的解决方案将 media.image 作为输入并返回位图 ***.com/a/35994288/5148048)。 看看这个link我用这个方法解决我的问题。 【参考方案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;

【讨论】:

这是我找到的最快的方法。其他转换有 for 循环,这会减慢实时处理处理方案的速度。 请注意,这仅在底层缓冲区为 NV21 时有效,即当 uBuffer 和 vBuffer 重叠时,请参阅***.com/a/52740776/192373。 非常适合将 android CameraX 图像分析帧 (YUV) 转换为 RBG,谢谢。【参考方案2】:

在我的方法中,我使用 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 Mat。为 BGR(还不是 RGB)图像定义新的 Mat:

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

【讨论】:

【参考方案3】:

您是否尝试过使用此脚本?这是yydcdut 在this 问题上发布的答案

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

【讨论】:

【参考方案4】:

Use Shyam Kumar 的答案不适用于我的手机,但 Daniel Więcek 的答案是正确的。我调试它,发现 planes[i].getRowStride() 是 1216,planes[i].getPixelStride() 是 2。而图像宽度和高度都是1200。

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

【讨论】:

您可以在此处找到从 Image 到 NV21(可以馈送到 OpenCV)的优化 Java 转换器:***.com/a/52740776/192373。请注意,在大多数情况下,renderscript conversion 会比 CPU 更快,并且不需要 OpenCV。【参考方案5】:

比上面提到的“imageToMat”快大约 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();

【讨论】:

我看不出您的答案如何正确...imageToMat 和我测试过的更简单的函数会产生两个不同的字节数组。如果 OpenCV 可以从 Camera2 api 处理这种新的 YUV 格式,那就太好了。但是下一个转换: Imgproc.cvtColor(mYuvMat, bgrMat, mgproc.COLOR_YUV2BGR_I420);至关重要,对于我的所有测试,只有 imageToMat 产生了良好的结果。如果有 YUV2BGR_CAMERA2_API 你的答案会更好。也许现在是。现在是两个月后;) 这可能适用于某些设备,其中 yBuffer(即image.getPlanes(0))实际上是完整的 NV21 直接缓冲区。这种情况很少见。【参考方案6】:

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

这里的关键是 byte[] 中的“旧”数据为 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++;

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

(希望它对某人有所帮助,尽管已经过去了很多年......)

【讨论】:

您的答案看起来很有希望,确实让我免于崩溃到转换后的位图。可悲的是,生成的位图只有绿色和灰色的步幅...... 请注意,这只适用于底层缓冲区为 YV12 的情况,即当 uBuffer 和 vBuffer 的 pixelStride=1 时。

以上是关于将 Android camera2 api YUV_420_888 转换为 RGB的主要内容,如果未能解决你的问题,请参考以下文章

使用新的 Android camera2 api 从 YUV_420_888 进行 JPEG 编码时的绿色图像

在Android camera2下将YUV_420_888转换为位图的图像不正确

安卓camera2 API获取YUV420_888格式详解

使用 OpenCV 从 android Camera2 将 YUV 转换为 RGB ImageReader 时出现问题,输出图像为灰度

将 android.media.Image (YUV_420_888) 转换为位图

将android.media.Image(YUV_420_888)转换为Bitmap