Java - 从图像中获取像素数组

Posted

技术标签:

【中文标题】Java - 从图像中获取像素数组【英文标题】:Java - get pixel array from image 【发布时间】:2011-09-25 08:03:27 【问题描述】:

我正在寻找从BufferedImage 获取像素数据(格式为int[][])的最快方法。我的目标是能够使用int[x][y] 从图像中处理像素(x, y)。我发现的所有方法都没有这样做(大多数都返回int[]s)。

【问题讨论】:

如果你担心速度,为什么要将整个图像复制到一个数组而不是直接使用getRGBsetRGB @bemace:根据我的分析,因为这些方法似乎做的工作比人们想象的要多。访问数组似乎更快。 @bemace:实际上真的很激烈:使用数组比直接使用getRGBsetRGB 快800% 以上。 【参考方案1】:

我只是在玩同样的主题,这是访问像素的最快方法。我目前知道两种方法:

    使用 BufferedImage 的getRGB() 方法,如@tskuzzy 的回答中所述。

    通过使用直接访问像素数组:

    byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
    

如果您正在处理大图像并且性能是一个问题,那么第一种方法绝对不是要走的路。 getRGB() 方法将 alpha、red、green 和 blue 值组合到一个 int 中,然后返回结果,在大多数情况下,您将执行相反的操作来取回这些值。

第二种方法将直接返回每个像素的红色、绿色和蓝色值,如果有 alpha 通道,它将添加 alpha 值。使用这种方法在计算指数方面比较困难,但比第一种方法快得多。

在我的应用程序中,只需从第一种方法切换到第二种方法,我就能将处理像素的时间减少 90% 以上!

这是我设置的比较两种方法的比较:

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import javax.imageio.ImageIO;

public class PerformanceTest 

   public static void main(String[] args) throws IOException 

      BufferedImage hugeImage = ImageIO.read(PerformanceTest.class.getResource("12000X12000.jpg"));

      System.out.println("Testing convertTo2DUsingGetRGB:");
      for (int i = 0; i < 10; i++) 
         long startTime = System.nanoTime();
         int[][] result = convertTo2DUsingGetRGB(hugeImage);
         long endTime = System.nanoTime();
         System.out.println(String.format("%-2d: %s", (i + 1), toString(endTime - startTime)));
      

      System.out.println("");

      System.out.println("Testing convertTo2DWithoutUsingGetRGB:");
      for (int i = 0; i < 10; i++) 
         long startTime = System.nanoTime();
         int[][] result = convertTo2DWithoutUsingGetRGB(hugeImage);
         long endTime = System.nanoTime();
         System.out.println(String.format("%-2d: %s", (i + 1), toString(endTime - startTime)));
      
   

   private static int[][] convertTo2DUsingGetRGB(BufferedImage image) 
      int width = image.getWidth();
      int height = image.getHeight();
      int[][] result = new int[height][width];

      for (int row = 0; row < height; row++) 
         for (int col = 0; col < width; col++) 
            result[row][col] = image.getRGB(col, row);
         
      

      return result;
   

   private static int[][] convertTo2DWithoutUsingGetRGB(BufferedImage image) 

      final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
      final int width = image.getWidth();
      final int height = image.getHeight();
      final boolean hasAlphaChannel = image.getAlphaRaster() != null;

      int[][] result = new int[height][width];
      if (hasAlphaChannel) 
         final int pixelLength = 4;
         for (int pixel = 0, row = 0, col = 0; pixel + 3 < pixels.length; pixel += pixelLength) 
            int argb = 0;
            argb += (((int) pixels[pixel] & 0xff) << 24); // alpha
            argb += ((int) pixels[pixel + 1] & 0xff); // blue
            argb += (((int) pixels[pixel + 2] & 0xff) << 8); // green
            argb += (((int) pixels[pixel + 3] & 0xff) << 16); // red
            result[row][col] = argb;
            col++;
            if (col == width) 
               col = 0;
               row++;
            
         
       else 
         final int pixelLength = 3;
         for (int pixel = 0, row = 0, col = 0; pixel + 2 < pixels.length; pixel += pixelLength) 
            int argb = 0;
            argb += -16777216; // 255 alpha
            argb += ((int) pixels[pixel] & 0xff); // blue
            argb += (((int) pixels[pixel + 1] & 0xff) << 8); // green
            argb += (((int) pixels[pixel + 2] & 0xff) << 16); // red
            result[row][col] = argb;
            col++;
            if (col == width) 
               col = 0;
               row++;
            
         
      

      return result;
   

   private static String toString(long nanoSecs) 
      int minutes    = (int) (nanoSecs / 60000000000.0);
      int seconds    = (int) (nanoSecs / 1000000000.0)  - (minutes * 60);
      int millisecs  = (int) ( ((nanoSecs / 1000000000.0) - (seconds + minutes * 60)) * 1000);


      if (minutes == 0 && seconds == 0)
         return millisecs + "ms";
      else if (minutes == 0 && millisecs == 0)
         return seconds + "s";
      else if (seconds == 0 && millisecs == 0)
         return minutes + "min";
      else if (minutes == 0)
         return seconds + "s " + millisecs + "ms";
      else if (seconds == 0)
         return minutes + "min " + millisecs + "ms";
      else if (millisecs == 0)
         return minutes + "min " + seconds + "s";

      return minutes + "min " + seconds + "s " + millisecs + "ms";
   

你能猜出输出吗? ;)

Testing convertTo2DUsingGetRGB:
1 : 16s 911ms
2 : 16s 730ms
3 : 16s 512ms
4 : 16s 476ms
5 : 16s 503ms
6 : 16s 683ms
7 : 16s 477ms
8 : 16s 373ms
9 : 16s 367ms
10: 16s 446ms

Testing convertTo2DWithoutUsingGetRGB:
1 : 1s 487ms
2 : 1s 940ms
3 : 1s 785ms
4 : 1s 848ms
5 : 1s 624ms
6 : 2s 13ms
7 : 1s 968ms
8 : 1s 864ms
9 : 1s 673ms
10: 2s 86ms

BUILD SUCCESSFUL (total time: 3 minutes 10 seconds)

【讨论】:

对于那些懒得看代码的人,有两个测试convertTo2DUsingGetRGBconvertTo2DWithoutUsingGetRGB。第一次测试平均需要 16 秒。第二次测试平均需要 1.5 秒。起初我认为“s”和“ms”是两个不同的列。 @Mota,很好的参考。 @Reddy 我试了一下,我确实看到文件大小有所不同,我不知道为什么!但是,我已经能够使用此代码(使用 alpha 通道)重现确切的像素值:pastebin.com/zukCK2tu 您可能需要修改 BufferedImage 构造函数的第三个参数,具体取决于您正在处理的图像。希望这会有所帮助! @Mota In convertTo2DUsingGetRGB 你为什么要取 result[row][col] = image.getRGB(col, row);而不是 result[row][col] = image.getRGB(row, col); 人们注意到颜色差异和/或不正确的字节顺序:@Mota 的代码假定 BGR 顺序。您应该检查传入的BufferedImagetype,例如TYPE_INT_RGBTYPE_3BYTE_BGR 并妥善处理。这是getRGB() 为你做的事情之一,这让它变慢了:-( 如果我错了,请纠正我,但是使用|=而不是+=来组合方法2中的值不是更有效吗?【参考方案2】:

这样的?

int[][] pixels = new int[w][h];

for( int i = 0; i < w; i++ )
    for( int j = 0; j < h; j++ )
        pixels[i][j] = img.getRGB( i, j );

【讨论】:

这不是非常低效吗?虽然BufferedImage 会使用 2D int 数组存储像素,但无论如何,我会? 我很确定图像在内部存储为一维数据结构。因此,无论您如何操作,操作都将花费 O(W*H)。您可以通过首先将其存储到一维数组中并将一维数组转换为二维数组来避免方法调用开销。 @ryyst 如果你想要一个数组中的所有像素,这和它的效率差不多 +1,我不认为这会访问Raster的数据缓冲区,这绝对是一件好事,因为这会导致加速踢。 @tskuzzy 这个方法比较慢。通过 Mota 检查方法,比这种传统方法更快。【参考方案3】:

我发现 Mota 的回答让我的速度提高了 10 倍 - 所以感谢 Mota。

我已经将代码封装在一个方便的类中,该类在构造函数中采用 BufferedImage 并公开了一个等效的 getRBG(x,y) 方法,这使得它可以替代使用 BufferedImage.getRGB(x,y) 的代码

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;

public class FastRGB


    private int width;
    private int height;
    private boolean hasAlphaChannel;
    private int pixelLength;
    private byte[] pixels;

    FastRGB(BufferedImage image)
    

        pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        width = image.getWidth();
        height = image.getHeight();
        hasAlphaChannel = image.getAlphaRaster() != null;
        pixelLength = 3;
        if (hasAlphaChannel)
        
            pixelLength = 4;
        

    

    int getRGB(int x, int y)
    
        int pos = (y * pixelLength * width) + (x * pixelLength);

        int argb = -16777216; // 255 alpha
        if (hasAlphaChannel)
        
            argb = (((int) pixels[pos++] & 0xff) << 24); // alpha
        

        argb += ((int) pixels[pos++] & 0xff); // blue
        argb += (((int) pixels[pos++] & 0xff) << 8); // green
        argb += (((int) pixels[pos++] & 0xff) << 16); // red
        return argb;
    

【讨论】:

我是使用 java 处理图像文件的新手。您能解释一下为什么以这种方式制作 getRGB() 比 Color API 的 getRGB() 更快/更好/更优化吗?欣赏! @mk7 请看一下这个答案***.com/a/12062932/363573。欲了解更多详情,请在您最喜欢的搜索引擎中输入 java 为什么 getrgb 很慢【参考方案4】:

除非您的 BufferedImage 来自单色位图,否则 Mota 的回答非常好。单色位图的像素只有 2 个可能的值(例如 0 = 黑色和 1 = 白色)。当使用单色位图时,

final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();

调用以每个字节包含多个像素的方式返回原始像素数组数据。

因此,当您使用单色位图图像创建 BufferedImage 对象时,这就是您要使用的算法:

/**
 * This returns a true bitmap where each element in the grid is either a 0
 * or a 1. A 1 means the pixel is white and a 0 means the pixel is black.
 * 
 * If the incoming image doesn't have any pixels in it then this method
 * returns null;
 * 
 * @param image
 * @return
 */
public static int[][] convertToArray(BufferedImage image)


    if (image == null || image.getWidth() == 0 || image.getHeight() == 0)
        return null;

    // This returns bytes of data starting from the top left of the bitmap
    // image and goes down.
    // Top to bottom. Left to right.
    final byte[] pixels = ((DataBufferByte) image.getRaster()
            .getDataBuffer()).getData();

    final int width = image.getWidth();
    final int height = image.getHeight();

    int[][] result = new int[height][width];

    boolean done = false;
    boolean alreadyWentToNextByte = false;
    int byteIndex = 0;
    int row = 0;
    int col = 0;
    int numBits = 0;
    byte currentByte = pixels[byteIndex];
    while (!done)
    
        alreadyWentToNextByte = false;

        result[row][col] = (currentByte & 0x80) >> 7;
        currentByte = (byte) (((int) currentByte) << 1);
        numBits++;

        if ((row == height - 1) && (col == width - 1))
        
            done = true;
        
        else
        
            col++;

            if (numBits == 8)
            
                currentByte = pixels[++byteIndex];
                numBits = 0;
                alreadyWentToNextByte = true;
            

            if (col == width)
            
                row++;
                col = 0;

                if (!alreadyWentToNextByte)
                
                    currentByte = pixels[++byteIndex];
                    numBits = 0;
                
            
        
    

    return result;

【讨论】:

【参考方案5】:

如果有用,试试这个:

BufferedImage imgBuffer = ImageIO.read(new File("c:\\image.bmp"));

byte[] pixels = (byte[])imgBuffer.getRaster().getDataElements(0, 0, imgBuffer.getWidth(), imgBuffer.getHeight(), null);

【讨论】:

解释一下会很有帮助【参考方案6】:

这是here 发现的另一个 FastRGB 实现:

public class FastRGB 
    public int width;
    public int height;
    private boolean hasAlphaChannel;
    private int pixelLength;
    private byte[] pixels;

    FastRGB(BufferedImage image) 
        pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        width = image.getWidth();
        height = image.getHeight();
        hasAlphaChannel = image.getAlphaRaster() != null;
        pixelLength = 3;
        if (hasAlphaChannel)
            pixelLength = 4;
    

    short[] getRGB(int x, int y) 
        int pos = (y * pixelLength * width) + (x * pixelLength);
        short rgb[] = new short[4];
        if (hasAlphaChannel)
            rgb[3] = (short) (pixels[pos++] & 0xFF); // Alpha
        rgb[2] = (short) (pixels[pos++] & 0xFF); // Blue
        rgb[1] = (short) (pixels[pos++] & 0xFF); // Green
        rgb[0] = (short) (pixels[pos++] & 0xFF); // Red
        return rgb;
    


这是什么?

通过 BufferedImage 的 getRGB 方法逐像素读取图像非常慢,这个类就是解决这个问题的方法。

这个想法是你通过给它一个 BufferedImage 实例来构造对象,它一次读取所有数据并将它们存储在一个数组中。一旦你想得到像素,你调用 getRGB

依赖关系

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;

注意事项

虽然 FastRGB 可以更快地读取像素,但它可能会导致高内存使用,因为它只是存储图像的副本。因此,如果内存中有一个 4MB 的 BufferedImage,一旦创建了 FastRGB 实例,内存使用量将变为 8MB。但是,您可以在创建 FastRGB 后回收 BufferedImage 实例。

在内存是瓶颈的android手机等设备上使用时注意不要陷入OutOfMemoryException

【讨论】:

【参考方案7】:

这对我有用:

BufferedImage bufImgs = ImageIO.read(new File("c:\\adi.bmp"));    
double[][] data = new double[][];
bufImgs.getData().getPixels(0,0,bufImgs.getWidth(),bufImgs.getHeight(),data[i]);    

【讨论】:

变量i是什么? 它是数据的迭代器

以上是关于Java - 从图像中获取像素数组的主要内容,如果未能解决你的问题,请参考以下文章

将像素值从 PXCImage 获取到 2D 数组

从像素数组问题中获取 NSImage (Swift)

已解决:从线性数组中获取相邻像素(如 context.getImageData 中)

Re: 快速从 UIImage/CGImage 获取像素数据作为数组

04 图像像素的读写操作

如何从图像中获取像素的颜色?