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

12

概述

  1. 我正在读取一个包含图像数据的大型二进制文件。
  2. 对数据执行累计计数剪切分析[需要使用与图像大小相同的另一个数组]。
  3. 将数据在0到255之间拉伸,并逐像素存储在BufferedImage中,以在JPanel上绘制图像。
  4. 在这个图像上,使用AffineTransform进行缩放。

问题

  1. 小图片(<0.5GB)

    1.1 当我增加缩放比例进行缩放时,达到一定点后就会抛出异常:

java.lang.OutOfMemoryError: Java heap space.

以下是用于缩放的代码-

    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的repaint方法出了一些问题。

  3. 我尝试以低分辨率加载图像,在这里像素被读取和少数列和行被跳过。但问题是如何决定要跳过多少像素,因为图像大小不同,因此我无法决定如何决定“跳跃”参数。

    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++;
    }

问题是要决定跳过多少列和行,即应设置什么值的jump。

  1. 我尝试设置Xmx,但图像大小会变化,而且我们无法动态设置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>

  • 为此,我尝试查找程序分配的内存:

  •  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();
    }
    

    以下是获得的结果
    内存分配
    这种方法失败了,因为可用内存随着我的代码使用而增加。 因此,对于我来说,它将是无用的来决定跳跃的值。

    预期结果

    一种在加载图像之前访问可用内存量的方法,以便我可以使用它来决定跳跃的值。是否有其他的替代方法来决定跳跃值(即可以降低分辨率的程度)。


    请在您的问题中添加您尝试过的Xmx值。 - Ellen Spertus
    1
    1. 将Xmx设置为任意大的数字(100 GB)怎么样?在这种情况下,当系统无法提供时,JVM将会崩溃。使用Oracle JVM获取系统内存的方法在此处:https://dev59.com/gG035IYBdhLWcg3wSN-r。然而,如果您不需要将整个图像加载到内存中(我猜它是原始的?),为什么不逐行(或逐像素)读取它呢?据我所知,CCC不需要将整个图像加载到内存中。
    - rAndom69
    如果您需要在内存中使用整个图像。要在面板上使用Graphics绘制它,您需要整个图像,对吗? - Tarun Maganti
    @Ellen Spertus 使用手动设置Xmx值意味着,您不能像上面提到的那样拥有任意大的图像。此外,我们可以在启动JVM后设置Xmx值吗?如果存在无法重新启动JVM的情况怎么办? - Tarun Maganti
    @EllenSpertus 我已经在问题中添加了这些值。 - Harshita
    2个回答

    2

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

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

    例如:

    // 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
    

    线程可以帮助您获取图像的一部分 在Java中从非常大的图像文件中读取区域

    线程可以帮助您缩放图像(我的快速搜索结果 :) )如何在Java中调整图像大小?

    线程可以帮助您合并缓冲图像:合并两个图像

    您始终可以微调代码片段 :)


    2
    1. OutOfMemoryError是一个自我解释的错误 - 你的内存已经用尽。这并不是指你机器上的物理内存,而是JVM达到了-xmx设置的上限。
    2. 你的设置测试没有太大意义,因为你试图将3.8GB大小的图像放入512MB的内存块中。这是行不通的 - 你不能将10升的水放入5升的瓶子中。对于内存使用,你需要至少是图像大小的3倍,因为你要分别存储每个像素,它由3个字节(RGB)组成。而且这只是纯图像数据。剩下的是整个应用程序和数据对象结构的开销+计算所需的额外空间,还有可能有很多我没提到的东西,我甚至都不知道。
    3. 你不想"动态设置"。将其设置为系统中最大可能的值(试错法)。JVM不会占用那么多的内存,除非它需要它。通过其他-X设置,您可以告诉JVM释放未使用的内存,因此您不必担心JVM冻结未使用的内存。
    4. 我从来没有在图像处理应用程序上工作过。Photoshop或Gimp是否能够打开并处理这样大的图像,从而得到一些有用的结果呢?也许你应该在那里寻找处理这么多数据的线索(如果它正在运作)。
    5. 如果上面的观点只是一种天真,因为你需要这个科学目的(除非你是扁平论者:)),你将需要科学级的硬件。
    6. 有一件事情我想到的是根本不要将图像读入内存,而是即时处理它。这可以将内存消耗减少到兆字节的数量级。

    仔细查看ImageReader API,因为它提示(readTile方法)可能只能读取图像的某个区域(例如缩放)


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接