Java - obtener matriz de píxeles de la imagen

118

Estoy buscando la forma más rápida de obtener datos de píxeles (en el formulario int[][]) de un archivo BufferedImage. Mi objetivo es poder abordar el píxel (x, y)de la imagen usando int[x][y]. Todos los métodos que he encontrado no hacen esto (la mayoría devuelve int[]s).

Ryyst
fuente
Si le preocupa la velocidad, ¿por qué quiere copiar la imagen completa en una matriz en lugar de usar getRGBy setRGBdirectamente?
Brad Mace
3
@bemace: Porque esos métodos parecen hacer más trabajo de lo que uno podría pensar, según mi perfil. Acceder a una matriz parece mucho más rápido.
Ryyst
15
@bemace: Es realmente muy intenso: usar una matriz es más del 800% más rápido que usar getRGBy setRGBdirectamente.
Ryyst

Respuestas:

179

Estaba jugando con este mismo tema, que es la forma más rápida de acceder a los píxeles. Actualmente conozco dos formas de hacer esto:

  1. Usando el getRGB()método de BufferedImage como se describe en la respuesta de @ tskuzzy.
  2. Accediendo a la matriz de píxeles directamente usando:

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

Si está trabajando con imágenes grandes y el rendimiento es un problema, el primer método no es en absoluto el camino a seguir. El getRGB()método combina los valores alfa, rojo, verde y azul en un único int y luego devuelve el resultado, que en la mayoría de los casos hará lo contrario para recuperar estos valores.

El segundo método devolverá los valores rojo, verde y azul directamente para cada píxel, y si hay un canal alfa, agregará el valor alfa. Usar este método es más difícil en términos de cálculo de índices, pero es mucho más rápido que el primer enfoque.

En mi aplicación, pude reducir el tiempo de procesamiento de los píxeles en más del 90% simplemente cambiando del primer enfoque al segundo.

Aquí hay una comparación que configuré para comparar los dos enfoques:

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

¿Puedes adivinar la salida? ;)

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)
Motasim
fuente
10
Para aquellos que son demasiado perezosos para leer el código, hay dos pruebas convertTo2DUsingGetRGBy convertTo2DWithoutUsingGetRGB. La primera prueba dura en promedio 16 segundos. La segunda prueba dura en promedio 1,5 segundos. Al principio pensé que la "s" y la "ms" eran dos columnas diferentes. @Mota, gran referencia.
Jason
1
@Reddy Lo intenté, y veo una diferencia en el tamaño del archivo, ¡que no estoy seguro de por qué! Sin embargo, he podido reproducir los valores de píxeles exactos usando este código (usando el canal alfa): pastebin.com/zukCK2tu Es posible que deba modificar el tercer argumento del constructor BufferedImage, dependiendo de la imagen con la que esté tratando . ¡Espero que esto ayude un poco!
Motasim
4
@Mota En convertTo2DUsingGetRGB, ¿por qué toma result [fila] [col] = image.getRGB (col, fila); en lugar de resultado [fila] [col] = image.getRGB (fila, columna);
Kailash
6
Personas que notan una diferencia de color y / o un orden de bytes incorrecto: el código de @ Mota asume un pedido de BGR . Debería comprobar los p BufferedImage. typeEj. TYPE_INT_RGBO los entrantes TYPE_3BYTE_BGRy manejarlos de forma adecuada. Esta es una de las cosas quegetRGB() hace por ti, que lo hace más lento :-(
millhouse
2
Corrígeme si me equivoco, pero ¿no sería más eficiente usarlo en |=lugar de +=combinar los valores en el método 2?
Ontonador
24

¿Algo como esto?

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 );
tskuzzy
fuente
11
¿No es increíblemente ineficiente? Sin embargo, BufferedImage¿almacenaría los píxeles usando una matriz int 2D, de todos modos?
Ryyst
1
Estoy bastante seguro de que la imagen se almacena internamente como una estructura de datos unidimensional. Entonces, la operación tomará O (W * H) sin importar cómo lo haga. Podría evitar la sobrecarga de llamadas al método almacenándola primero en una matriz unidimensional y convertir la matriz unidimensional en una matriz 2D.
tskuzzy
4
@ryyst si desea todos los píxeles en una matriz, esto es lo más eficiente posible
Sean Patrick Floyd
1
+1, no creo que esto acceda al Rasterbúfer de datos del ', lo que definitivamente es algo bueno ya que da como resultado una aceleración de despeje.
mre el
2
@tskuzzy Este método es más lento. Compruebe el método de Mota, que es más rápido que este método convencional.
h4ck3d
20

Descubrí que la respuesta de Mota me dio un aumento de velocidad de 10 veces, así que gracias Mota.

Envolví el código en una clase conveniente que toma BufferedImage en el constructor y expone un método getRBG (x, y) equivalente que lo convierte en un reemplazo para el código que usa 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;
    }
}
Robert Sutton
fuente
Soy nuevo en el procesamiento de archivos de imagen en java. ¿Puede explicar por qué hacer getRGB () de esta manera es más rápido / mejor / más óptimo que getRGB () de la API de color? ¡Apreciar!
mk7
@ mk7 Por favor, eche un vistazo a esta respuesta stackoverflow.com/a/12062932/363573 . Para obtener más detalles, escriba java por qué getrgb es lento en su motor de búsqueda favorito.
Stephan
10

La respuesta de Mota es excelente a menos que su Imagen de búfer provenga de un mapa de bits monocromo. Un mapa de bits monocromo tiene solo 2 valores posibles para sus píxeles (por ejemplo, 0 = negro y 1 = blanco). Cuando se utiliza un mapa de bits monocromo,

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

call devuelve los datos sin procesar de Pixel Array de tal manera que cada byte contiene más de un píxel.

Entonces, cuando usa una imagen de mapa de bits monocromo para crear su objeto BufferedImage, este es el algoritmo que desea usar:

/**
 * 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;
}
CatGuardian
fuente
4

Si es útil, intente esto:

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

byte[] pixels = (byte[])imgBuffer.getRaster().getDataElements(0, 0, imgBuffer.getWidth(), imgBuffer.getHeight(), null);
C-Crestani
fuente
14
Una explicación sería útil
asheeshr
1

Aquí hay otra implementación de FastRGB que se encuentra aquí :

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

¿Que es esto?

Leer una imagen píxel a píxel a través del método getRGB de BufferedImage es bastante lento, esta clase es la solución para esto.

La idea es que construya el objeto alimentándolo con una instancia de BufferedImage, y lea todos los datos a la vez y los almacene en una matriz. Una vez que desee obtener píxeles, llame a getRGB

Dependencias

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

Consideraciones

Aunque FastRGB hace que la lectura de píxeles sea mucho más rápida, podría conducir a un alto uso de memoria, ya que simplemente almacena una copia de la imagen. Entonces, si tiene una imagen de búfer de 4 MB en la memoria, una vez que cree la instancia de FastRGB, el uso de la memoria se convertiría en 8 MB. Sin embargo, puede reciclar la instancia de BufferedImage después de crear FastRGB.

Tenga cuidado de no caer en OutOfMemoryException cuando lo use en dispositivos como teléfonos Android, donde la RAM es un cuello de botella

Stephan
fuente
-1

Esto funcionó para mí:

BufferedImage bufImgs = ImageIO.read(new File("c:\\adi.bmp"));    
double[][] data = new double[][];
bufImgs.getData().getPixels(0,0,bufImgs.getWidth(),bufImgs.getHeight(),data[i]);    
Una sartén
fuente
8
Cual es la variable i?
Nicolas
es el iterador de datos
Cjen1