【问题标题】:Reading a progressively encoded 9000x9000 JPEG in Java takes 1 minute用 Java 读取渐进编码的 9000x9000 JPEG 需要 1 分钟
【发布时间】:2015-06-24 15:36:03
【问题描述】:

使用javax.imageio.ImageIO 从磁盘加载高分辨率 (9000x9000) JPEG 时,在我的 scala 应用程序中需要超过 1 分钟。我尝试创建一个纯 Java 项目,但仍然需要很长时间 - 大约 30 秒。

这是我加载图像的方式:

File file = new File("/Users/the21st/slow2.jpg");
BufferedImage image = ImageIO.read(file);

有没有什么方法可以提高在 Java 中读取渐进式编码的大分辨率 JPEG 的性能?

有问题的图像是this one(版主,请不要再次重新上传到其他托管站点,以免编码/质量发生变化)

【问题讨论】:

  • @Bret 我没有意识到 StackOverflow 在上传到他们的服务器期间可能会调整大小和/或重新编码该图像。这是原始图像:dl.dropboxusercontent.com/u/73774/slow2.jpg(使用此链接编辑原始问题)
  • 嗯,看来渐进式编码是罪魁祸首。如果我将您的样本重新编码为基线,则阅读时间会从 ~40s -> ~2s 下降。
  • 可能你在 GC 搅动,尝试增加堆内存
  • 我已投票决定重新开放。 “有没有比 ImageIO 更快的替代方法来加载 JPEG 图像?”不一定是对工具的推荐。我有很多编程建议。
  • 由于使用了不同的代码路径,我的JPEG plugin for ImageIO 确实以大约两倍的速度读取图像(在我的计算机上 JRE:~18 秒,TM:~8 秒,ymmv)。这意味着如果您获得 JPEG 的 JRE ImageReader 并使用 readRaster 方法,您将看到同样的加速。似乎是由于渐进式编码,正如@barti_ddu 所指出的那样 PS:投票支持重新开放,但还不能回答......

标签: java jpeg javax.imageio


【解决方案1】:

ImageIO 的一个更快的替代方案是ImageMagick,有各种包装器用于通过 Java 连接 ImageMagick,例如 JMagick

要从 JMagick 获取 BufferedImage,您必须首先获取 MagickImage 的实例,可以这样做:

ImageInfo info = new ImageInfo(pathToImage);
MagickImage image = new MagickImage(info);

现在您可以使用我们自己8年前Jacob Nordfalk提供的方法将图像读入BufferedImagehere

public static BufferedImage magickImageToBufferedImage(MagickImage magickImage) throws Exception
{
    Dimension  dim = magickImage.getDimension();
    int size = dim.width * dim.height;
    byte[] pixels = new byte[size * 3];

    magickImage.dispatchImage(0, 0, dim.width, dim.height, "RGB", pixels);

    BufferedImage bimage = createInterleavedRGBImage(dim.width, dim.height, pixels);

    ColorModel cm = bimage.getColorModel();
    Raster raster = bimage.getData();
    WritableRaster writableRaster = null;

    writableRaster = (raster instanceof WritableRaster) ? (WritableRaster) raster : raster.createCompatibleWritableRaster();

    BufferedImage bufferedImage = new BufferedImage(cm, writableRaster, false, null);

    return bufferedImage;
}

然后是createInterleavedRGBImage 方法:

public static BufferedImage createInterleavedRGBImage(int imageWidth, int imageHeight, byte data[])
{
    int[] numBits = new int[3];
    int[] bandoffsets = new int[3];

    for (int i = 0; i < 3; i++) {
        numBits[i] = 8;
        bandoffsets[i] = i;
    }

    ComponentColorModel ccm = new ComponentColorModel(
        ColorSpace.getInstance(ColorSpace.CS_sRGB),
        numBits,
        false,
        false, //Alpha pre-multiplied
        Transparency.OPAQUE,
        DataBuffer.TYPE_BYTE
    );

    PixelInterleavedSampleModel csm = new PixelInterleavedSampleModel(
        DataBuffer.TYPE_BYTE,
        imageWidth,
        imageHeight,
        3, //Pixel stride
        imageWidth * 3, // Scanline stride
        bandoffsets
    );

    DataBuffer dataBuf = new DataBufferByte(data, imageWidth * imageHeight * 3);
    WritableRaster wr = Raster.createWritableRaster(csm, dataBuf, new Point(0, 0));
    return new BufferedImage(ccm, wr, false, null);
}

【讨论】:

    【解决方案2】:

    好的,这是我目前的发现(老实说,它们有点令人担忧……)。

    使用与 Oracle JRE 捆绑的 ImageIO 标准 JPEG 插件:

    BufferedImage image = ImageIO.read(file); 
    

    在我的计算机(MacBookPro/2.8GHz i7)上大约 18 秒读取图像。

    使用我的JPEG plugin for ImageIO,它使用稍微不同的代码路径(即,通过获取ImageReader 并调用readRaster() 方法,然后从中创建一个BufferedImage,您可能会得到相同的结果。代码很重要,所以如果您想查看代码,请参考项目页面):

    BufferedImage image = ImageIO.read(file); 
    

    在我的计算机上读取图像大约需要 8 秒

    使用我的 BufferedImageFactory 类和 AWT Toolkit

    BufferedImage image = new BufferedImageFactory(Toolkit.getDefaultToolkit().createImage(file.getAbsolutePat‌​h())).getBufferedImage();
    

    ~2.5 秒在我的计算机上读取图像。

    使用 sun.awt.codec 中已弃用的 JPEGImageDecoder 类:

    BufferedImage image = new JPEGImageDecoderImpl(new FileInputStream(file)).decodeAsBufferedImage();
    

    ~1.7 秒在我的计算机上读取图像。

    因此,这意味着我们应该能够在不到 2 秒的时间内读取该图像,即使在 Java 中也是如此。在这种情况下,JPEGImageReader 的表现简直荒谬,我真的很想知道为什么。如前所述,它似乎必须与渐进式解码,但仍然应该比这更好。

    更新:

    只是为了好玩,我创建了一个由 LibJPEG-Turbo Java API 支持的快速 PoC ImageReader 插件。它还不是很复杂,但它允许像这样的代码:

    BufferedImage image = ImageIO.read(file); 
    

    在我的计算机上读取图像。

    PS:I used to maintain ImageIO wrappers for JMagick(类似于@Jordan Doyle 提到的代码,但它允许您针对 ImageIO API 进行编程),但是我停止了,因为工作量太大。也许我必须重新考虑...如果您不介意依赖 JNI/本机代码安装,至少也值得检查一下他的解决方案。

    【讨论】:

    • 有没有办法修改您的 BufferedImageFactory 方法,使其也可以解码 CMYK jpeg?
    • @the21st 抱歉,Toolkit.createImage 方法似乎不支持 CMYK,所以它不起作用(我们也无法修复它)。不过,我的原始插件和 LibJPEG-Turbo 版本都可以。 ;-)
    • 对此线程的评论很晚。我的 PC 速度很慢,使用 Windows 10 / JDK14,并尝试加载图像:ImageIO.read(file) 需要 50 秒,但 JavaFX new javafx.scene.image.Image() 只需约 3.5 秒。
    • @DuncG 很有趣。 ImageIO 插件确实应该有一些捷径,至少可以避免渐进式更新。但是,您看到的差异仍然很大。
    猜你喜欢
    • 2018-04-04
    • 2012-07-18
    • 1970-01-01
    • 1970-01-01
    • 2020-10-30
    • 2018-09-30
    • 1970-01-01
    • 1970-01-01
    • 2019-06-10
    相关资源
    最近更新 更多