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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6524196/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 06:38:10  来源:igfitidea点击:

Java - get pixel array from image

javabufferedimagejavax.imageio

提问by ryyst

I'm looking for the fastest way to get pixel data (int the form int[][]) from a BufferedImage. My goal is to be able to address pixel (x, y)from the image using int[x][y]. All the methods I have found do not do this (most of them return int[]s).

我正在寻找int[][]BufferedImage. 我的目标是能够(x, y)使用int[x][y]. 我发现的所有方法都没有这样做(大多数都返回int[]s)。

采纳答案by Motasim

I was just playing around with this same subject, which is the fastest way to access the pixels. I currently know of two ways for doing this:

我只是在玩这个相同的主题,这是访问像素的最快方法。我目前知道有两种方法可以做到这一点:

  1. Using BufferedImage's getRGB()method as described in @tskuzzy's answer.
  2. By accessing the pixels array directly using:

    byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
    
  1. 使用 BufferedImage 的getRGB()方法,如@tskuzzy 的回答中所述。
  2. 通过直接使用以下方法访问像素数组:

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

If you are working with large images and performance is an issue, the first method is absolutely not the way to go. The getRGB()method combines the alpha, red, green and blue values into one int and then returns the result, which in most cases you'll do the reverse to get these values back.

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

The second method will return the red, green and blue values directly for each pixel, and if there is an alpha channel it will add the alpha value. Using this method is harder in terms of calculating indices, but is much faster than the first approach.

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

In my application I was able to reduce the time of processing the pixels by more than 90% by just switching from the first approach to the second!

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

Here is a comparison I've setup to compare the two approaches:

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

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

Can you guess the output? ;)

你能猜出输出吗?;)

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)

回答by tskuzzy

Something like this?

像这样的东西?

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

回答by C-Crestani

If useful, try this:

如果有用,试试这个:

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

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

回答by APan

This worked for me:

这对我有用:

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

回答by Robert Sutton

I found Mota's answer gave me a 10 times speed increase - so thanks Mota.

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

I've wrapped up the code in a convenient class which takes the BufferedImage in the constructor and exposes an equivalent getRBG(x,y) method which makes it a drop in replacement for code using BufferedImage.getRGB(x,y)

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

回答by CatGuardian

Mota's answer is great unless your BufferedImage came from a Monochrome Bitmap. A Monochrome Bitmap has only 2 possible values for its pixels (for example 0 = black and 1 = white). When a Monochrome Bitmap is used then the

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

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

call returns the raw Pixel Array data in such a fashion that each byte contains more than one pixel.

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

So when you use a Monochrome Bitmap image to create your BufferedImage object then this is the algorithm you want to use:

因此,当您使用单色位图图像创建 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;
}

回答by Stephan

Here is another FastRGB implementation found 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;
    }
}


What is this?

这是什么?

Reading an image pixel by pixel through BufferedImage's getRGB method is quite slow, this class is the solution for this.

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

The idea is that you construct the object by feeding it a BufferedImage instance, and it reads all the data at once and stores them in an array. Once you want to get pixels, you call getRGB

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

Dependencies

依赖关系

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

Considerations

注意事项

Although FastRGB makes reading pixels much faster, it could lead to high memory usage, as it simply stores a copy of the image. So if you have a 4MB BufferedImage in the memory, once you create the FastRGB instance, the memory usage would become 8MB. You can however, recycle the BufferedImage instance after you create the FastRGB.

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

Be careful to not fall into OutOfMemoryExceptionwhen using it on devices such as Android phones, where RAM is a bottleneck

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