如何在Java中处理RAM中的大量数据/图像?

时间:2019-05-14 18:53:24

标签: java graphics2d

摘要

  1. 我正在读取一个包含图像数据的大二进制文件。
  2. 对数据进行累积计数切割分析[需要另一个与图像大小相同的数组]。
  3. 将数据BufferedImage逐像素扩展到0到25​​5之间,以在JPanel上绘制图像。
  4. 在此图像上,使用AffineTransform进行缩放。

问题

  1. 小图片(<.5GB)

    1.1当我增加执行缩放的比例因子时,   抛出点异常:-

  

java.lang.OutOfMemoryError:Java堆空间。

下面是用于缩放的代码-

    scaled = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
    Graphics2D g2d = (Graphics2D)scaled.createGraphics();
    AffineTransform transformer = new AffineTransform();
    transformer.scale(scaleFactor, scaleFactor); 
    g2d.setTransform(transformer);
  1. 大图(> 1.5GB)
    • 在加载大图像(> 1.5GB)时,发生与 1.1,即使图像足够小,可以加载,有时也会出现相同的错误。

尝试过的解决方案

  1. 我尝试使用BigBufferedImage代替BufferedImage来存储拉伸的数据。 BigBufferedImage image = BigBufferedImage.create(newCol,newRow, BufferedImage.TYPE_INT_ARGB);
  2. 但是无法将其传递给g2d.drawImage(image, 0, 0, this); 因为JPanel的重绘方法由于某种原因而停止。

  3. 我尝试以低分辨率加载图像,该图像读取像素并且跳/跳过了几行。但是问题是如何确定随着图像大小的变化而跳过多少像素,因此我无法决定如何确定 jump 参数。

    MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY,0, inChannel.size());
    buffer.order(ByteOrder.LITTLE_ENDIAN);
    FloatBuffer floatBuffer = buffer.asFloatBuffer();
    for(int i=0,k=0;i<nrow;i=i+jump)  /*jump is the value to be skipped, nrow is height of image*/
    {
        for(int j=0,l=0;j<ncol1;j=j+jump)   //ncol is width of image
        {
                index=(i*ncol)+j;
                oneDimArray[(k*ncolLessRes)+l] = floatBuffer.get(index);//oneDimArray is initialised to size of Low Resolution image.
                l++;
        }
        k++;
    }

问题是要确定要跳过多少列和一行,即应设置跳转的值。

  1. 我尝试设置Xmx,但是图像大小有所不同,我们cannot dynamically set Xmx值。 这是一些值-

table, th, td {
  border: 1px solid black;
}
<table style="width:100%">
  <tr>
    <th>Image Size</th>
    <th>Xmx</th>
    <th>Xms</th>
    <th>Problem</th>
  </tr>
  <tr>
    <td>83Mb</td>
    <td>512m</td>
    <td>256m</td>
    <td>working</td>
  </tr>
  <tr>
    <td>83Mb</td>
    <td>3096m</td>
    <td>2048m</td>
    <td>System hanged</td>
  </tr>
   <tr>
    <td>3.84Gb</td>
    <td>512m</td>
    <td>256m</td>
    <td>java.lang.OutOfMemoryError: Java heap space
  </tr>
  <tr>
    <td>3.84Gb</td>
    <td>3096m</td>
    <td>512m</td>
    <td>java.lang.OutOfMemoryError: Java heap space
  </tr>
</table>

  1. 为此,我尝试查找分配给程序的内存:-
 try(BufferedWriter bw= new BufferedWriter(new FileWriter(dtaFile,true))){
    Runtime runtime=Runtime.getRuntime();
    runtime.gc();
    double oneMB=Math.pow(2,20);
    long[] arr= Instream.range(0,(int)(10.432*long.BYTES*Math.pow(2,20))).asLongStream().toArray();
    runtime.gc();
    long freeMemory= runtime.freeMemory();
    long totalMemory= runtime.totalMemory();
    long usedMemory= totalMemory-freeMemory;
    long maxMemory= runtime.maxMemory();
    String fileLine= String.format(" %9.3f  %9.3f   %9.3f " , usedMemory/oneMb, freeMemory/oneMB, totalMemory/oneMb, maxMemory/oneMB);
    bw.write();
}

获得了以下结果
Memory Allocation
这种方法失败了,因为根据我的代码使用情况,可用内存增加了。结果,对我来说决定跳下去是没有用的。

预期结果

一种在加载图像之前访问可用内存量的方法,这样我就可以用它来决定跳转的值。还有其他选择来决定 jump 值的方法(即,我可以降低多少分辨率吗?)。

2 个答案:

答案 0 :(得分:1)

  1. OutOfMemoryError是自我解释-您的内存不足。这就是在说您的计算机上没有物理RAM,而是JVM达到了-xmx设置所设置的内存分配上限
  2. 当您尝试将3,8GB大小的图像放入512MB内存块时,您的xmx设置测试毫无意义。它无法工作-您不能在5升的瓶子中放入10升的水。对于内存使用,您至少需要图像x3的大小,因为您要分别存储每个像素并包含3个字节(RGB)。那仅用于纯图像数据。剩下的就是整个应用程序和数据对象的结构开销,以及计算所需的额外空间,可能还有很多我没有提到的,甚至我都不知道。
  3. 您不想“动态设置” -xmx。将其设置为系统中的最大可能值(尝试和错误)。除非需要,否则JVM不会占用那么多内存。通过附加的-X设置,您可以告诉JVM释放未使用的内存,因此您不必担心JVM会“冻结”未使用的内存。
  4. 我从未从事过图像处理应用程序。 Photoshop或Gimp是否能够打开并处理如此大的图像?也许您应该寻找有关在那里处理大量数据的线索(如果正在运行)
  5. 如果上述要点只是天真,因为您出于科学目的需要它(除非您是平凡的人,否则这不是Photoshop或Gimp的目的):您将需要科学级的硬件。
  6. 我想到的一件事是根本不将图像读入内存,而是即时对其进行处理。这样可以将内存消耗减少到几兆字节。

仔细研究ImageReader API(建议使用readTile方法),可能只读取图像区域(例如,用于放大)

答案 1 :(得分:1)

您可以读取图像的特定部分,然后以降低的分辨率缩放图像以进行显示。

因此,在您的情况下,您可以分块读取图像(读取图像部分就像我们逐行从数据库读取数据一样)

例如:

// Define the portion / row size 50px or 100px
int rowHeight = 50;
int rowsToScan = imageHeight / rowHeight;
if(imageHeight % rowHeight > 0) rowsToScan++;

int x = 0;
int y = 0;
int w = imageWidth;
int h = rowHeight;

ArrayList<BufferedImage> scaledImagePortions = new ArrayList<>();

for(int i = 1; i <= rowsToScan; i++) {
    // Read the portion of an image scale it
    // and push the scaled version in lets say array
    BufferedImage scalledPortionOfImage = this.getScaledPortionOfImage(img, x, y, w, h);
    scaledImagePortions.add(scalledPortionOfImage);

    y = (rowHeight * i);
}

// Create single image out of scaled images portions

可以帮助您获得图像Read region from very large image file in Java

的部分的线程

可以帮助您缩放图像的线程(我的快速搜索结果:)) how to resize Image in java?

可以帮助您合并缓冲图像的线程:Merging two images

您始终可以调整代码段:)