如何从流中读取图像?

bla*_*ith 7 java png image stream

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

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Run Code Online (Sandbox Code Playgroud)

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

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

我怀疑ImageIO.createImageInputStream()ImageIO可能适合该法案,但我不知道如何使用它逐步读取.此外,JDK上有类PNGMetadataPNGImageReader可用的类rt.jar似乎很有用,但我没有找到它们的用法的简单示例.

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

har*_*ldK 10

用于读取和操作图像的Java API并不像您想象的那样基于流.的ImageInputStream仅仅是一个方便的包装,允许读入byte来自不同输入(S和其他原始类型RandomAccessFileS,InputStream小号等).

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

不过,正如我所看到的,您可以通过多种方式实现最终目标,从而能够处理大型图像:

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

    有点像:

    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...
         }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 立即读取整个图像,进入内存映射缓冲区.随意尝试一些我为此目的而做的实验课(使用nio).读取将比读取纯存储器图像慢,并且处理速度也会变慢.但是,如果您一次对图像的较小区域进行计算,则可能与内存中的某些优化一样快.我已经使用这些类将> 1 GB图像读入32 MB JVM(实际内存消耗当然要大得多).

    再次,这是一个例子:

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

  4. JAI可能有一些可以帮助你的东西.我不是一个忠实的粉丝,因为甲骨文有许多未解决的错误和缺乏爱心和发展.但值得一试.我认为他们也支持tileable和基于磁盘的RenderedImages.再次,我将留下这个选项供您进一步探索.


leo*_*loy 5

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

  • 这仅与“块”中的组织略有相关,因为 PNG 文件是逐行编码的,因此原则上可以逐行读取。

  • 上述假设在隔行 PNG 的情况下不成立 - 但不应期望以隔行格式存储巨大的 PNG 图像

  • 虽然一些 PNG 库允许渐进(逐行)解码(例如:libpng),但标准的 Java API 并没有给你。

我遇到了这个问题,最终我编写了自己的 Java 库:PNGJ。它相当成熟,它允许逐行读取PNG图像,最小化内存消耗,并以相同的方式写入它们(即使是隔行扫描的PNG也可以读取,但在这种情况下内存问题不会消失。)如果您只需要做一些“本地”图像处理(根据当前值和邻居修改每个像素值,然后写回)这应该会有所帮助。