如何从流中读取图像?

时间:2014-11-07 11:38:29

标签: java image stream png

有人可能会建议BufferedImage是用Java处理图像的最佳选择。虽然很方便,但在阅读大量图像时,它往往最终会出现:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

增加VM大小不是解决方案,因为在我的情况下,某些输入文件非常庞大。

所以我正在寻找如何从流中逐步读取图像的方式。

我怀疑来自ImageIO.createImageInputStream()的{​​{1}}可能适合该帐单,但我不确定如何使用它来逐步阅读chunks。 此外,还有课程ImageIO& JDK PNGMetadata上提供的PNGImageReader似乎很有用,但我没有找到它们用法的简单示例。

这是要走的路,还是有更好的选择?

2 个答案:

答案 0 :(得分:10)

用于读取和操作图像的Java API并不像您想象的那样基于流。 ImageInputStream只是一个便利包装器,允许从不同的输入(byte s,RandomAccessFile等)中读取InputStream和其他原始类型。

我一直在考虑创建一个用于读取“像素流”的API,以便在不使用大量内存的情况下链接处理过滤器。但是,从来没有认真对待这种努力。如果您想听到更多想法或有可行的实施,请随意雇用我。 ; - )

不过,正如我所看到的,你有多种选择来实现你的最终目标,能够处理大型图像:

  1. 按原样使用BufferedImage,使用ImageIO API以较小的部分读取图像以节省内存。由于实现的原因,这对某些格式非常有效,对其他格式效率较低(即默认JPEGImageReader将在将较小的区域移交给Java堆之前读取本机内存中的整个映像,但是{{{ 1}}可能没问题。)

    有些事情:

    PNGImageReader
  2. 立即读取整个图像到内存映射缓冲区。请尽量尝试我为此目的制作的experimental classes(使用ImageInputStream stream = ImageIO.createImageInputStream(input); ImageReader reader = ImageIO.getImageReaders(stream).next(); // TODO: Test hasNext() reader.setInput(stream); int width = reader.getWidth(0); int height = reader.getHeight(0); ImageReadParam param = reader.getDefaultReadParam(); for (int y = 0; y < height; y += 100) { for (int x = 0; x < width; x += 100) { param.setSourceRegion(new Rectangle(x, y, 100, 100)); // TODO: Bounds check // Read a 100 x 100 tile from the image BufferedImage region = reader.read(0, param); // ...process region as needed... } } )。读取将比读取纯存储器图像慢,并且处理速度也会变慢。但是,如果您一次对图像的较小区域进行计算,则可能与内存中的某些优化一样快。我读过&gt;使用这些类将1 GB映像转换为32 MB JVM(实际内存消耗当然要大得多)。

    再次,这是一个例子:

    nio
  3. 某些格式(如未压缩的TIFF,BMP,PPM等)会保留文件中的像素,使其可以直接对其进行内存映射以对其进行操作。需要一些工作,但应该是可能的。 TIFF还支持可能有用的瓷砖。我将此选项作为练习,随意使用我上面链接的课程作为起点或灵感。 ; - )

  4. JAI可能有一些可以帮到你的东西。我不是一个忠实的粉丝,因为甲骨文有许多未解决的错误和缺乏爱心和发展。但值得一试。我认为他们也支持tileable和基于磁盘的ImageInputStream stream = ImageIO.createImageInputStream(input); ImageReader reader = ImageIO.getImageReaders(stream).next(); // TODO: Test hasNext() reader.setInput(stream); int width = reader.getWidth(0); int height = reader.getHeight(0); ImageTypeSpecifier spec = reader.getImageTypes(0).next(); // TODO: Test hasNext(); BufferedImage image = MappedImageFactory.createCompatibleMappedImage(width, height, spec) ImageReadParam param = reader.getDefaultReadParam(); param.setDestination(image); image = reader.read(0, param); // Will return same image as created above // ...process image as needed... 。再次,我将留下这个选项供您进一步探索。

答案 1 :(得分:4)

内存问题肯定与解码过程本身无关,而是将整个映像作为BufferedImage存储在内存中。可以逐步读取PNG图像,但是:

  • 这只是与&#34; chunks&#34;中的组织有轻微关系,更多是因为PNG文件是逐行编码的,因此它们可以在-in原理上阅读线由行。

  • 上述假设在隔行扫描PNG的情况下有所突破 - 但人们不应期望以隔行格式存储巨大的PNG图像

  • 虽然有些PNG库允许逐行(逐行)解码(例如:libpng),但标准Java API并不能满足您的要求。

我遇到了这个问题,最后我编写了自己的Java库:PNGJ。它非常成熟,它允许逐行读取PNG图像,最大限度地减少内存消耗,并以相同的方式写入它们(甚至可以读取隔行扫描的PNG,但在这种情况下,内存问题不会消失。) 如果你只需要做一些&#34;本地&#34;图像处理(根据当前值和邻居修改每个像素值,并将其写回)这应该有所帮助。