有人可能会建议BufferedImage
是用Java处理图像的最佳选择。虽然很方便,但在阅读大量图像时,它往往最终会出现:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
增加VM大小不是解决方案,因为在我的情况下,某些输入文件非常庞大。
所以我正在寻找如何从流中逐步读取图像的方式。
我怀疑来自ImageIO.createImageInputStream()
的{{1}}可能适合该帐单,但我不确定如何使用它来逐步阅读chunks。
此外,还有课程ImageIO
& JDK PNGMetadata
上提供的PNGImageReader
似乎很有用,但我没有找到它们用法的简单示例。
这是要走的路,还是有更好的选择?
答案 0 :(得分:10)
用于读取和操作图像的Java API并不像您想象的那样基于流。 ImageInputStream
只是一个便利包装器,允许从不同的输入(byte
s,RandomAccessFile
等)中读取InputStream
和其他原始类型。
我一直在考虑创建一个用于读取“像素流”的API,以便在不使用大量内存的情况下链接处理过滤器。但是,从来没有认真对待这种努力。如果您想听到更多想法或有可行的实施,请随意雇用我。 ; - )
不过,正如我所看到的,你有多种选择来实现你的最终目标,能够处理大型图像:
按原样使用BufferedImage
,使用ImageIO
API以较小的部分读取图像以节省内存。由于实现的原因,这对某些格式非常有效,对其他格式效率较低(即默认JPEGImageReader
将在将较小的区域移交给Java堆之前读取本机内存中的整个映像,但是{{{ 1}}可能没问题。)
有些事情:
PNGImageReader
立即读取整个图像到内存映射缓冲区。请尽量尝试我为此目的制作的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
某些格式(如未压缩的TIFF,BMP,PPM等)会保留文件中的像素,使其可以直接对其进行内存映射以对其进行操作。需要一些工作,但应该是可能的。 TIFF还支持可能有用的瓷砖。我将此选项作为练习,随意使用我上面链接的课程作为起点或灵感。 ; - )
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;图像处理(根据当前值和邻居修改每个像素值,并将其写回)这应该有所帮助。