【问题标题】:Converting YUV_420_888 to JPEG and saving file results distorted image将 YUV_420_888 转换为 JPEG 并保存文件导致图像失真
【发布时间】:2017-10-16 18:02:04
【问题描述】:

我在我的 git repo 中使用了https://stackoverflow.com/a/40152147/2949966 中提供的ImageUtil 类:https://github.com/ahasbini/cameraview/tree/camera_preview_imp(注意实现在camera_preview_imp 分支中)来实现帧预览回调。 ImageReader 设置为预览ImageFormat.YUV_420_888 格式的帧,该格式将使用ImageUtil 类转换为ImageFormat.JPEG 并将其发送到帧回调。演示应用每 50 帧将回调中的一帧保存到文件中。所有保存的帧图像都出现扭曲,如下所示:

如果我通过在Camera2 中进行以下更改,将ImageReader 改为使用ImageFormat.JPEG

mPreviewImageReader = ImageReader.newInstance(previewSize.getWidth(),
    previewSize.getHeight(), ImageFormat.JPEG, /* maxImages */ 2);
mCamera.createCaptureSession(Arrays.asList(surface, mPreviewImageReader.getSurface()),
    mSessionCallback, null);

图像正常显示,没有任何失真,但是帧速率显着下降并且视图开始滞后。因此我相信ImageUtil 类没有正确转换。

【问题讨论】:

  • final image 失真是图像写入文件吗?
  • 我的错误,已编辑问题以消除混乱。
  • 在哪里可以看到onImageAvailable(ImageReader reader) (ImageReader.OnImageAvailableListener) 方法?
  • Camera2 类中的mOnPreviewAvailableListener 变量中。
  • 请链接 :) 我找不到。

标签: android image android-camera


【解决方案1】:

@volodymyr-kulyk 提供的解决方案没有考虑图像中平面的行步长。下面的代码可以解决问题(image 属于 android.media.Image 类型):

data = NV21toJPEG(YUV420toNV21(image), image.getWidth(), image.getHeight(), 100);

以及实现:

private static byte[] NV21toJPEG(byte[] nv21, int width, int height, int quality) {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
    yuv.compressToJpeg(new Rect(0, 0, width, height), quality, out);
    return out.toByteArray();
}

private static byte[] YUV420toNV21(Image image) {
    Rect crop = image.getCropRect();
    int format = image.getFormat();
    int width = crop.width();
    int height = crop.height();
    Image.Plane[] planes = image.getPlanes();
    byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
    byte[] rowData = new byte[planes[0].getRowStride()];

    int channelOffset = 0;
    int outputStride = 1;
    for (int i = 0; i < planes.length; i++) {
        switch (i) {
            case 0:
                channelOffset = 0;
                outputStride = 1;
                break;
            case 1:
                channelOffset = width * height + 1;
                outputStride = 2;
                break;
            case 2:
                channelOffset = width * height;
                outputStride = 2;
                break;
        }

        ByteBuffer buffer = planes[i].getBuffer();
        int rowStride = planes[i].getRowStride();
        int pixelStride = planes[i].getPixelStride();

        int shift = (i == 0) ? 0 : 1;
        int w = width >> shift;
        int h = height >> shift;
        buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
        for (int row = 0; row < h; row++) {
            int length;
            if (pixelStride == 1 && outputStride == 1) {
                length = w;
                buffer.get(data, channelOffset, length);
                channelOffset += length;
            } else {
                length = (w - 1) * pixelStride + 1;
                buffer.get(rowData, 0, length);
                for (int col = 0; col < w; col++) {
                    data[channelOffset] = rowData[col * pixelStride];
                    channelOffset += outputStride;
                }
            }
            if (row < h - 1) {
                buffer.position(buffer.position() + rowStride - length);
            }
        }
    }
    return data;
}

方法是从以下link获取的。

【讨论】:

  • 我为这些进步奋斗了整个下午,直到我到达这篇文章。我想给你100票!!!!
  • 顺便说一下,转换有点慢,我的设备每帧需要40ms~140ms。
  • @SiraLam 感谢您的反馈。如果您愿意,您可以在该问题上开一个赏金并奖励它答案;P。关于处理,是的,没错,这是一个缓慢的过程,图像对话本质上往往是这样的。为了能够更快地处理更多的帧,这需要一定程度的多线程和线程之间的一些同步来实现流水线效果。由于帧处理时间约为 100 毫秒,因此用户在查看相机流时不会真正感到延迟
  • 我认为 :P 好吧,用户不会觉得预览有任何延迟,因为我的预览使用的表面与我用于这些帧的表面不同。一旦我使用另一个线程进行转换工作,预览仍然非常流畅。事实上,我正在使用这些框架来进行面部检测并在这些面部上叠加一些东西......遗憾的是,使用 Camera 1 API 这既简单又快速:(
  • 小米A1,使用JPEG格式的图像阅读器应用程序崩溃,转换为YUV_420_888然后使用你的方法。非常感谢。
【解决方案2】:

更新了 ImageUtil

public final class ImageUtil {

    public static byte[] NV21toJPEG(byte[] nv21, int width, int height, int quality) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        yuv.compressToJpeg(new Rect(0, 0, width, height), quality, out);
        return out.toByteArray();
    }

    // nv12: true = NV12, false = NV21
    public static byte[] YUV_420_888toNV(ByteBuffer yBuffer, ByteBuffer uBuffer, ByteBuffer vBuffer, boolean nv12) {
        byte[] nv;

        int ySize = yBuffer.remaining();
        int uSize = uBuffer.remaining();
        int vSize = vBuffer.remaining();

        nv = new byte[ySize + uSize + vSize];

        yBuffer.get(nv, 0, ySize);
        if (nv12) {//U and V are swapped
            vBuffer.get(nv, ySize, vSize);
            uBuffer.get(nv, ySize + vSize, uSize);
        } else {
            uBuffer.get(nv, ySize , uSize);
            vBuffer.get(nv, ySize + uSize, vSize);
        }
        return nv;
    }

    public static byte[] YUV_420_888toI420SemiPlanar(ByteBuffer yBuffer, ByteBuffer uBuffer, ByteBuffer vBuffer,
                                                     int width, int height, boolean deInterleaveUV) {
        byte[] data = YUV_420_888toNV(yBuffer, uBuffer, vBuffer, deInterleaveUV);
        int size = width * height;
        if (deInterleaveUV) {
            byte[] buffer = new byte[3 * width * height / 2];

            // De-interleave U and V
            for (int i = 0; i < size / 4; i += 1) {
                buffer[i] = data[size + 2 * i + 1];
                buffer[size / 4 + i] = data[size + 2 * i];
            }
            System.arraycopy(buffer, 0, data, size, size / 2);
        } else {
            for (int i = size; i < data.length; i += 2) {
                byte b1 = data[i];
                data[i] = data[i + 1];
                data[i + 1] = b1;
            }
        }
        return data;
    }
}

以JPEG格式写入文件byte[] data的操作:

//image.getPlanes()[0].getBuffer(), image.getPlanes()[1].getBuffer()
//image.getPlanes()[2].getBuffer(), image.getWidth(), image.getHeight()
byte[] nv21 = ImageUtil.YUV_420_888toI420SemiPlanar(yBuffer, uBuffer, vBuffer, width, height, false);
byte[] data = ImageUtil.NV21toJPEG(nv21, width, height, 100);
//now write `data` to file

!!!处理后不要忘记关闭图像!!!

image.close();

【讨论】:

【解决方案3】:

Camera2 YUV_420_888 转 Jpeg in Java(Android):

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

            String savingFilepath = getYUV2jpg(nv21);



        }
    } catch (Exception e) {
        Log.w(TAG, e.getMessage());
    }finally{
        image.close();// don't forget to close
    }
}

  public String getYUV2jpg(byte[] data) {
    File imageFile = new File("your parent directory", "picture.jpeg");//no i18n
    BufferedOutputStream bos = null;
    try {
        bos = new BufferedOutputStream(new FileOutputStream(imageFile));
        bos.write(data);
        bos.flush();
        bos.close();
    } catch (IOException e) {

        return e.getMessage();
    } finally {
        try {
            if (bos != null) {
                bos.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    return imageFile.getAbsolutePath();
}

注意:处理图像旋转问题。

【讨论】:

  • 这不起作用 :( 之后格式仍然不是 jpeg。
【解决方案4】:

我认为这里与 YUV 的 NV 和 YV 格式存在一些混淆。 NV(半平面)具有交错的 U/V。 YV(平面)没有。所以这里进行的转换是 YV12/21 而不是 NV12/21。

【讨论】:

    猜你喜欢
    • 2012-12-21
    • 2015-07-13
    • 1970-01-01
    • 2015-11-06
    • 1970-01-01
    • 2015-04-10
    • 2019-05-29
    • 1970-01-01
    相关资源
    最近更新 更多