如何保存 YUV_420_888 图像?

Posted

技术标签:

【中文标题】如何保存 YUV_420_888 图像?【英文标题】:How to save a YUV_420_888 image? 【发布时间】:2015-11-06 04:59:40 【问题描述】:

我使用 camera2 API 构建了自己的相机应用。我从示例“camera2Raw”开始,添加了 YUV_420_888 支持而不是 JPEG。但是现在我想知道如何将图像保存在 ImageSaver 中!?

这是我的运行方法代码:

@Override
    public void run() 
        boolean success = false;
        int format = mImage.getFormat();
        switch(format) 
            case ImageFormat.RAW_SENSOR:
                DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult);
                FileOutputStream output = null;
                try 
                    output = new FileOutputStream(mFile);
                    dngCreator.writeImage(output, mImage);
                    success = true;
                 catch (IOException e) 
                    e.printStackTrace();
                 finally 
                    mImage.close();
                    closeOutput(output);
                
                break;
            
            case ImageFormat.YUV_420_888:
                ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                FileOutputStream output = null;
                try 
                    output = new FileOutputStream(mFile);
                    output.write(bytes);
                    success = true;
                 catch (IOException e) 
                    e.printStackTrace();
                 finally 
                    mImage.close();
                    closeOutput(output);
                
                break;
            
            default:
                Log.e(TAG, "Cannot save image, unexpected image format:" + format);

        
        // Decrement reference count to allow ImageReader to be closed to free up resources.
        mReader.close();

        // If saving the file succeeded, update MediaStore.
        if (success) 
            MediaScannerConnection.scanFile(mContext, new String[]  mFile.getPath(),
            /*mimeTypes*/ null, new MediaScannerConnection.MediaScannerConnectionClient() 
                @Override
                public void onMediaScannerConnected() 
                    // Do nothing
                

                @Override
                public void onScanCompleted(String path, Uri uri) 
                    Log.i(TAG, "Scanned " + path + ":");
                    Log.i(TAG, "-> uri=" + uri);
                
            );
        
    

我尝试像 JPEG 一样保存 YUV 图像,但这样我只能得到一个平面,并且保存的数据对我没有任何意义......

保存 YUV 图像的正确方法是什么?转换成RGB(那YUV是什么意思?)?还是使用 YuvImage 类?

【问题讨论】:

【参考方案1】:

通常,您将 YUV 图像保存为文件,因此没有内置函数可以这样做。此外,此类 YUV 数据没有标准的图像格式编码。 YUV 通常是一种中间形式的数据,便于相机流水线和之后转换为其他格式。

如果您真的打算这样做,您可以将三个通道的缓冲区作为未编码的字节数据写入文件,然后在其他地方打开并重建它。不过,请确保您也保存其他重要信息,例如步幅数据。这就是我所做的。以下是我使用的文件格式切换语句中的相关行,以及推理中的 cmets:

File file = new File(SAVE_DIR, mFilename);
FileOutputStream output = null;
ByteBuffer buffer;
byte[] bytes;
boolean success = false;

switch (mImage.getFormat())

    (... other image data format cases ...)

    // YUV_420_888 images are saved in a format of our own devising. First write out the
    // information necessary to reconstruct the image, all as ints: width, height, U-,V-plane
    // pixel strides, and U-,V-plane row strides. (Y-plane will have pixel-stride 1 always.)
    // Then directly place the three planes of byte data, uncompressed.
    //
    // Note the YUV_420_888 format does not guarantee the last pixel makes it in these planes,
    // so some cases are necessary at the decoding end, based on the number of bytes present.
    // An alternative would be to also encode, prior to each plane of bytes, how many bytes are
    // in the following plane. Perhaps in the future.
    case ImageFormat.YUV_420_888:
        // "prebuffer" simply contains the meta information about the following planes.
        ByteBuffer prebuffer = ByteBuffer.allocate(16);
        prebuffer.putInt(mImage.getWidth())
        .putInt(mImage.getHeight())
        .putInt(mImage.getPlanes()[1].getPixelStride())
        .putInt(mImage.getPlanes()[1].getRowStride());

        try 
            output = new FileOutputStream(file);
            output.write(prebuffer.array()); // write meta information to file
            // Now write the actual planes.
            for (int i = 0; i<3; i++)
                buffer = mImage.getPlanes()[i].getBuffer();
                bytes = new byte[buffer.remaining()]; // makes byte array large enough to hold image
                buffer.get(bytes); // copies image from buffer to byte array
                output.write(bytes);    // write the byte array to file
            
            success = true;
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
         finally 
            Log.v(appFragment.APP_TAG,"Closing image to free buffer.");
            mImage.close(); // close this to free up buffer for other images
            if (null != output) 
                try 
                    output.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
        break;
    

由于数据的交错方式完全取决于设备,因此稍后从该编码信息中提取 Y、U、V 通道可能具有挑战性。要查看如何读取和提取此类文件的 MATLAB 实现,请参阅here。

【讨论】:

非常酷!我只添加了一个“FileOutputStream output = null;”在第一次尝试之前,一切正常!非常感谢! 啊,是的,试图从其他代码片段中删除 sn-ps 的危险。对于那个很抱歉!我将修改上面的代码,以便将来找到它的人完全独立。 如果我以这种方式将YUV_420_888 图像保存到某个.jpg 文件中,则该图像文件不可读。 是的,这绝对不是jpg编码!您必须编写自己的解码程序来读取字节并自行将它们放入正确的层中。 那是保存文件的格式? JPG 或 PNG 或其他?【参考方案2】:

如果你有 YuvImage 对象,那么你可以像这样使用 compressToJpeg 函数将其转换为 Jpeg。

ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, width, height, null);
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 50, out);
byte[] imageBytes = out.toByteArray();
Bitmap image = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
iv.setImageBitmap(image);

您必须明确设置图像的宽度和高度。

【讨论】:

什么是“数据”?我有一个“图像”,我需要一个字节 []。我可以将图像的所有平面转换为单个字节 [] 吗? 另外YuvImage不支持YUV_420_888格式... 日志:“目前只支持ImageFormat.NV21和ImageFormat.YUY2” 数据不过是字节数组。是的,显然正如你所说,它不支持 YUV_420_888。

以上是关于如何保存 YUV_420_888 图像?的主要内容,如果未能解决你的问题,请参考以下文章

camera2 如何从图像读取器侦听器中的 YUV_420_888 图像中获取 Exif 数据

将 YUV_420_888 转换为 JPEG 并保存文件导致图像失真

如何正确使用带有 YUV_420_888 和 MediaCodec 的 ImageReader 将视频编码为 h264 格式?

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

将 YUV_420_888 中的图像从 Android 发送到 OpenCV Mat 中的 JNI 的最有效方法

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