如何从流中读取图像



有人可能认为BufferedImage是在Java中处理图像的最佳选项。虽然它很方便,但当阅读巨大的图像时,它通常会出现:

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

增加虚拟机大小不是一个解决方案,因为在我的情况下,一些输入文件确实很大。

因此,我正在寻找如何从流中逐步读取图像的方法。

我怀疑ImageIO中的ImageIO.createImageInputStream()可能符合要求,但我不确定如何使用它来逐步读取块。还有类PNGMetadata&JDK的rt.jar上提供了PNGImageReader,这似乎很有用,但我没有找到它们使用的简单示例。

这是一条路,还是有更好的选择?

用于读取和操作图像的Java API并不像您想象的那样真正基于流。ImageInputStream只是一个方便的包装器,它允许从不同的输入(RandomAccessFiles、InputStreams等)读取bytes和其他基元类型。

我一直在考虑创建一个用于读取"像素流"的API,以允许在不使用太多内存的情况下链接处理过滤器。但这种需求从未严重到值得付出努力的程度。如果你想听到更多的想法或有一个有效的实现,请随时雇用我。;-)

尽管如此,在我看来,你有多种选择来实现你的最终目标,即能够处理大图像:

  1. 按原样使用BufferedImageImageIO API读取较小部分的图像以节省内存。由于实现的原因,这对于某些格式将非常有效,而对于其他格式则效率较低(即,默认的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...
         }
    }
    
  2. 一次将整个图像读取到内存映射的缓冲区中。请随意尝试我为此目的制作的一些实验课程(使用nio)。读取将比读取纯内存图像慢,并且处理也将更慢。但是,如果你一次对图像的较小区域进行计算,经过一些优化,速度可能与内存中的速度一样快。我已经使用这些类将>1GB的映像读取到32MB的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...
    
  3. 某些格式,如未压缩的TIFF、BMP、PPM等,可以将像素保存在文件中,从而可以直接对其进行内存映射以进行操作。需要一些工作,但应该是可能的。TIFF还支持可能有帮助的平铺。我将把这个选项作为练习,可以随意使用我上面链接的课程作为起点或灵感。;-)

  4. JAI可能有什么可以帮助你的。我不是一个大粉丝,因为它有很多未解决的错误,而且缺乏对Oracle的热爱和开发。但值得一看。我认为他们也支持可平铺和基于磁盘的RenderedImage。同样,我将把这个选项留给您进一步探索。

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

  • 这与组织的"块"关系不大,尤其是PNG文件是逐行编码的,因此原则上可以逐行读取。

  • 上述假设在交错PNG的情况下被打破,但人们不应该期望以交错格式存储巨大的PNG图像

  • 虽然一些PNG库允许渐进式(逐行)解码(例如:libpng),但标准的Java API并没有提供这种功能。

我遇到了这个问题,最后我编写了自己的Java库:PNGJ。它非常成熟,可以逐行读取PNG图像,最大限度地减少内存消耗,并以相同的方式写入(即使是交错的PNG也可以读取,但在这种情况下,内存问题不会消失。)如果您只需要进行一些"局部"图像处理(根据当前值和邻居修改每个像素值,然后将其写回),这应该会有所帮助。

相关内容

  • 没有找到相关文章

最新更新