【问题标题】:Convert Android camera2 api YUV_420_888 to RGB将 Android camera2 api YUV_420_888 转换为 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 作为输入并返回位图 stackoverflow.com/a/35994288/5148048)。
  • 看看这个link我用这个方法解决我的问题。

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


【解决方案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 重叠时,请参阅stackoverflow.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】:

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

    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,所以我不能发表评论,只能发布答案。

      【讨论】:

      【解决方案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 时。
      猜你喜欢
      • 1970-01-01
      • 2016-02-06
      • 1970-01-01
      • 1970-01-01
      • 2017-06-25
      • 2015-04-10
      • 2021-04-13
      • 2017-06-29
      • 1970-01-01
      相关资源
      最近更新 更多