Java: ottieni l'array di pixel dall'immagine


118

Sto cercando il modo più veloce per ottenere i dati dei pixel (nel modulo int[][]) da un file BufferedImage. Il mio obiettivo è essere in grado di indirizzare i pixel (x, y)dell'immagine utilizzando int[x][y]. Tutti i metodi che ho trovato non lo fanno (la maggior parte restituisce int[]s).


Se sei preoccupato per la velocità, perché vuoi copiare l'intera immagine su un array invece di usare getRGBe setRGBdirettamente?
Brad Mace

3
@bemace: perché questi metodi sembrano fare più lavoro di quanto si possa pensare, secondo la mia profilazione. L'accesso a un array sembra molto più veloce.
Ryyst

15
@bemace: In realtà è davvero intenso: l'uso di un array è più dell'800% più veloce rispetto all'uso getRGBe setRGBdirettamente.
ryyst

Risposte:


179

Stavo solo giocando con lo stesso soggetto, che è il modo più veloce per accedere ai pixel. Al momento conosco due modi per farlo:

  1. Utilizzando il getRGB()metodo di BufferedImage come descritto nella risposta di @ tskuzzy.
  2. Accedendo direttamente all'array di pixel utilizzando:

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

Se stai lavorando con immagini di grandi dimensioni e le prestazioni sono un problema, il primo metodo non è assolutamente la strada da percorrere. Il getRGB()metodo combina i valori alfa, rosso, verde e blu in un int e quindi restituisce il risultato, che nella maggior parte dei casi farai il contrario per recuperare questi valori.

Il secondo metodo restituirà i valori rosso, verde e blu direttamente per ogni pixel e se è presente un canale alfa aggiungerà il valore alfa. L'utilizzo di questo metodo è più difficile in termini di calcolo degli indici, ma è molto più veloce del primo approccio.

Nella mia applicazione sono riuscito a ridurre il tempo di elaborazione dei pixel di oltre il 90% semplicemente passando dal primo approccio al secondo!

Ecco un confronto che ho impostato per confrontare i due approcci:

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

Riuscite a indovinare l'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)

10
Per chi è troppo pigro per leggere il codice, ci sono due test convertTo2DUsingGetRGBe convertTo2DWithoutUsingGetRGB. Il primo test dura in media 16 secondi. Il secondo test dura in media 1,5 secondi. All'inizio ho pensato che "s" e "ms" fossero due colonne diverse. @Mota, ottimo riferimento.
Jason

1
@Reddy ho provato e vedo una differenza nella dimensione del file, che non so perché! Tuttavia, sono stato in grado di riprodurre i valori esatti dei pixel usando questo codice (usando il canale alfa): pastebin.com/zukCK2tu Potrebbe essere necessario modificare il terzo argomento del costruttore BufferedImage, a seconda dell'immagine con cui hai a che fare . Spero che questo aiuti un po '!
Motasim

4
@Mota In convertTo2DUsingGetRGB perché prendi il risultato [riga] [col] = image.getRGB (col, riga); invece del risultato [riga] [col] = image.getRGB (riga, colonna);
Kailash

6
Persone che notano una differenza di colore e / o un errato ordinamento dei byte: il codice di @ Mota presuppone un ordine BGR . Dovresti controllare BufferedImage, typead esempio, il messaggio in entrata TYPE_INT_RGBo TYPE_3BYTE_BGRe gestirlo in modo appropriato. Questa è una delle cose che getRGB()fa per te, che lo rende più lento :-(
millhouse

2
Correggimi se sbaglio, ma non sarebbe più efficiente da usare |=invece di +=combinare i valori nel metodo 2?
Ontonator

24

Qualcosa come questo?

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

11
Non è incredibilmente inefficiente? Avrei comunque BufferedImagememorizzato i pixel usando un array int 2D, comunque?
Ryyst

1
Sono abbastanza sicuro che l'immagine sia archiviata internamente come una struttura dati unidimensionale. Quindi l'operazione richiederà O (W * H) indipendentemente da come lo fai. È possibile evitare il sovraccarico della chiamata al metodo archiviandolo prima in un array unidimensionale e convertendo l'array unidimensionale in un array 2D.
tskuzzy

4
@ryyst se vuoi tutti i pixel in un array, questo è quanto più efficiente possibile
Sean Patrick Floyd,

1
+1, non credo che questo acceda al Rasterbuffer di dati di, il che è sicuramente una buona cosa poiché si traduce in accelerazione del punting.
mre

2
@tskuzzy Questo metodo è più lento. Controlla il metodo di Mota, che è più veloce di questo metodo convenzionale.
h4ck3d

20

Ho scoperto che la risposta di Mota mi ha dato un aumento di velocità di 10 volte, quindi grazie Mota.

Ho racchiuso il codice in una comoda classe che prende BufferedImage nel costruttore ed espone un metodo getRBG (x, y) equivalente che lo rende una sostituzione del codice usando 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;
    }
}

Sono nuovo nell'elaborazione dei file di immagine in java. Puoi spiegare perché rendere getRGB () in questo modo è più veloce / migliore / ottimale rispetto a getRGB () dell'API Color? Apprezzo!
mk7

@ mk7 Dai un'occhiata a questa risposta stackoverflow.com/a/12062932/363573 . Per maggiori dettagli digita java perché getrgb è lento nel tuo motore di ricerca preferito.
Stephan

10

La risposta di Mota è ottima a meno che la tua BufferedImage non provenga da una Bitmap monocromatica. Una bitmap monocromatica ha solo 2 possibili valori per i suoi pixel (ad esempio 0 = nero e 1 = bianco). Quando viene utilizzata una bitmap monocromatica, il file

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

call restituisce i dati grezzi di Pixel Array in modo tale che ogni byte contenga più di un pixel.

Quindi, quando usi un'immagine Bitmap monocromatica per creare il tuo oggetto BufferedImage, questo è l'algoritmo che vuoi usare:

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

Se utile, prova questo:

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

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

14
Sarebbe utile una spiegazione
asheeshr

1

Ecco un'altra implementazione FastRGB trovata qui :

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

Cos'è questo?

La lettura di un'immagine pixel per pixel tramite il metodo getRGB di BufferedImage è piuttosto lenta, questa classe è la soluzione per questo.

L'idea è che tu costruisca l'oggetto alimentandolo con un'istanza di BufferedImage, che legge tutti i dati contemporaneamente e li memorizza in un array. Una volta che vuoi ottenere pixel, chiama getRGB

dipendenze

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

considerazioni

Sebbene FastRGB renda la lettura dei pixel molto più veloce, potrebbe comportare un elevato utilizzo della memoria, poiché memorizza semplicemente una copia dell'immagine. Quindi, se hai una BufferedImage da 4 MB nella memoria, una volta creata l'istanza FastRGB, l'utilizzo della memoria diventerà 8 MB. È tuttavia possibile riciclare l'istanza BufferedImage dopo aver creato FastRGB.

Fare attenzione a non cadere in OutOfMemoryException quando lo si utilizza su dispositivi come i telefoni Android, dove la RAM è un collo di bottiglia


-1

Questo ha funzionato per 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]);    

8
Qual è la variabile i?
Nicolas

è l'iteratore per i dati
Cjen1
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.