【问题标题】:Android Camera2 API YUV_420_888 to JPEGAndroid Camera2 API YUV_420_888 转 JPEG
【发布时间】:2016-10-17 15:49:20
【问题描述】:

我正在使用 OnImageAvailableListener:

获取预览帧
@Override
public void onImageAvailable(ImageReader reader) {
    Image image = null;
    try {
        image = reader.acquireLatestImage();
        Image.Plane[] planes = image.getPlanes();
        ByteBuffer buffer = planes[0].getBuffer();
        byte[] data = new byte[buffer.capacity()];
        buffer.get(data);
        //data.length=332803; width=3264; height=2448
        Log.e(TAG, "data.length=" + data.length + "; width=" + image.getWidth() + "; height=" + image.getHeight());
        //TODO data processing
    } catch (Exception e) {
        e.printStackTrace();
    }
    if (image != null) {
        image.close();
    }
}

每次数据长度不同,但图像宽度和高度相同。
主要问题:data.length 对于 3264x2448 这样的分辨率来说太小了。
数据数组的大小应该是 3264*2448=7,990,272,而不是 300,000 - 600,000。
怎么了?


imageReader = ImageReader.newInstance(3264, 2448, ImageFormat.JPEG, 5);

【问题讨论】:

    标签: java android android-camera2 camera2


    【解决方案1】:

    我通过使用 YUV_420_888 图像格式并手动将其转换为 JPEG 图像格式解决了这个问题。

    imageReader = ImageReader.newInstance(MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT, 
                                          ImageFormat.YUV_420_888, 5);
    imageReader.setOnImageAvailableListener(this, null);
    

    Surface imageSurface = imageReader.getSurface();
    List<Surface> surfaceList = new ArrayList<>();
    //...add other surfaces
    previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    previewRequestBuilder.addTarget(imageSurface);
                surfaceList.add(imageSurface);
    cameraDevice.createCaptureSession(surfaceList,
                        new CameraCaptureSession.StateCallback() {
    //...implement onConfigured, onConfigureFailed for StateCallback
    }, null);
    

    @Override
    public void onImageAvailable(ImageReader reader) {
        Image image = reader.acquireLatestImage();
        if (image != null) {
            //converting to JPEG
            byte[] jpegData = ImageUtils.imageToByteArray(image);
            //write to file (for example ..some_path/frame.jpg)
            FileManager.writeFrame(FILE_NAME, jpegData);
            image.close();
        }
    }
    

    public final class ImageUtil {
    
        public static byte[] imageToByteArray(Image image) {
            byte[] data = null;
            if (image.getFormat() == ImageFormat.JPEG) {
                Image.Plane[] planes = image.getPlanes();
                ByteBuffer buffer = planes[0].getBuffer();
                data = new byte[buffer.capacity()];
                buffer.get(data);
                return data;
            } else if (image.getFormat() == ImageFormat.YUV_420_888) {
                data = NV21toJPEG(
                        YUV_420_888toNV21(image),
                        image.getWidth(), image.getHeight());
            }
            return data;
        }
    
        private static byte[] YUV_420_888toNV21(Image image) {
            byte[] nv21;
            ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
            ByteBuffer vuBuffer = image.getPlanes()[2].getBuffer();
    
            int ySize = yBuffer.remaining();
            int vuSize = vuBuffer.remaining();
    
            nv21 = new byte[ySize + vuSize];
    
            yBuffer.get(nv21, 0, ySize);
            vuBuffer.get(nv21, ySize, vuSize);
    
            return nv21;
        }
    
        private static byte[] NV21toJPEG(byte[] nv21, int width, int height) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
            yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out);
            return out.toByteArray();
        }
    }
    

    public final class FileManager {
        public static void writeFrame(String fileName, byte[] data) {
            try {
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileName));
                bos.write(data);
                bos.flush();
                bos.close();
    //            Log.e(TAG, "" + data.length + " bytes have been written to " + filesDir + fileName + ".jpg");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    【讨论】:

    • 我已经使用答案中的ImageUtil 类将YUV_420_888 转换为JPEG 但是我没有得到正确的输出。通过使用此类,是否可以将输出byte[] 保存到 JPEG 文件中,或者甚至可以将其显示在SurfaceView 上?我正在尝试这样做,但是我得到了扭曲的图像。
    • data = NV21toJPEG(YUV_420_888toNV21(image), image.getWidth(), image.getHeight()); 会变形吗?
    • 是的,我已按原样放置类并实现了与您的实现类似的写入文件,但改用FileOutputStream,最终图像失真。我可以通过 git repo 向你展示完整的实现,我会稍微推一下。
    • 这个YUV_420_888toNV21()不靠谱,见stackoverflow.com/a/52740776/192373
    • @AlexCohn 谢谢,我同意你的看法。我会测试你的解决方案。
    【解决方案2】:

    我不确定,但我认为您只采用了 YUV_420_888 格式的平面之一(亮度部分)。

    就我而言,我通常以这种方式将图像转换为 byte[]。

                Image m_img;
                Log.v(LOG_TAG,"Format -> "+m_img.getFormat());
                Image.Plane Y = m_img.getPlanes()[0];
                Image.Plane U = m_img.getPlanes()[1];
                Image.Plane V = m_img.getPlanes()[2];
    
                int Yb = Y.getBuffer().remaining();
                int Ub = U.getBuffer().remaining();
                int Vb = V.getBuffer().remaining();
    
                data = new byte[Yb + Ub + Vb];
                //your data length should be this byte array length.
    
                Y.getBuffer().get(data, 0, Yb);
                U.getBuffer().get(data, Yb, Ub);
                V.getBuffer().get(data, Yb+ Ub, Vb);
                final int width = m_img.getWidth();
                final int height = m_img.getHeight();
    

    我使用这个字节缓冲区转换为 rgb。

    希望这会有所帮助。

    干杯。 乌奈。

    【讨论】:

    • 感谢您的回复!目前我使用的是ImageFormat.JPEG,所以m_img.getPlanes().length 等于1。
    • 好的,如果我使用ImageFormat.YUV_420_888,如何将rgb缓冲区压缩为JPEG
    • 太好了,不客气,我不知道你使用这个 byteArray 的目的是什么。对不起,我的延迟回答。
    【解决方案3】:

    您的代码正在请求压缩的 JPEG 格式图像。每一帧它们的大小都会发生变化,而且它们会比未压缩的图像小得多。如果您除了保存 JPEG 图像之外什么都不做,只需将 byte[] 数据中的内容保存到磁盘即可。

    如果您想对 JPEG 进行实际操作,可以使用 BitmapFactory.decodeByteArray() 将其转换为位图,例如,虽然效率很低。

    或者你可以切换到 YUV,效率更高,但是你需要做更多的工作才能从中得到一个 Bitmap。

    【讨论】:

    • 转换为位图并返回每一帧只是为了解码不是一种选择。
    • 您当前的解决方案似乎是将YUV数据重新洗牌为NV21,然后将其压缩为JPEG;目前尚不清楚您想要 YUV 做什么,但如果您只想保存数据,那么只要求 JPEG 肯定更便宜。您可以同时要求 JPEG 和 YUV,具体取决于分辨率,这可能是最有效的选择,尽管 JPEG 输出的帧速率可能低于 30fps。
    • 我正在使用 NV21 进行 QR 识别/解码。 JPEG 用于向服务器发送帧。
    猜你喜欢
    • 2015-08-11
    • 1970-01-01
    • 2015-06-21
    • 2017-06-25
    • 2017-07-30
    • 1970-01-01
    • 2016-07-12
    • 1970-01-01
    • 2023-03-14
    相关资源
    最近更新 更多