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

时间:2011-06-29 16:41:37

标签: java bufferedimage javax.imageio

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

7 个答案:

答案 0 :(得分:160)

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

  1. 使用@ tskuzzy的答案中描述的BufferedImage的getRGB()方法。
  2. 使用以下方法直接访问像素数组:

    byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
    
  3. 如果您正在处理大型图像并且性能是一个问题,那么第一种方法绝对不是您的选择。 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 < 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 < 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)
    

答案 1 :(得分:22)

这样的东西?

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 );

答案 2 :(得分:15)

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

我已经将代码包装在一个方便的类中,该类在构造函数中获取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;
    }
}

答案 3 :(得分:9)

除非您的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;
}

答案 4 :(得分:4)

如果有用,试试这个:

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

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

答案 5 :(得分:1)

这对我有用:

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

答案 6 :(得分:1)

这里是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实例。

在RAM成为瓶颈的Android手机等设备上使用它时,请小心不要陷入 OutOfMemoryException