Java - obtenir un tableau de pixels à partir de l'image

118

Je suis à la recherche du moyen le plus rapide d'obtenir des données de pixels (dans le formulaire int[][]) à partir d'un fichier BufferedImage. Mon objectif est de pouvoir adresser le pixel (x, y)de l'image en utilisant int[x][y]. Toutes les méthodes que j'ai trouvées ne le font pas (la plupart retournent int[]).

Ryyst
la source
Si vous vous inquiétez de la vitesse, pourquoi voulez-vous copier l'image entière dans un tableau au lieu de simplement utiliser getRGBet setRGBdirectement?
Brad Mace
3
@bemace: Parce que ces méthodes semblent faire plus de travail qu'on ne le pense, d'après mon profilage. L'accès à un tableau semble beaucoup plus rapide.
ryyst le
15
@bemace: C'est en fait vraiment intense: l'utilisation d'un tableau est plus de 800% plus rapide que l'utilisation getRGBet setRGBdirectement.
ryyst le

Réponses:

179

Je jouais juste avec ce même sujet, qui est le moyen le plus rapide d'accéder aux pixels. Je connais actuellement deux façons de procéder:

  1. Utilisation de la getRGB()méthode de BufferedImage comme décrit dans la réponse de @ tskuzzy.
  2. En accédant directement au tableau de pixels en utilisant:

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

Si vous travaillez avec de grandes images et que les performances sont un problème, la première méthode n'est absolument pas la voie à suivre. La getRGB()méthode combine les valeurs alpha, rouge, verte et bleue en un seul entier, puis renvoie le résultat, ce qui dans la plupart des cas, vous ferez l'inverse pour récupérer ces valeurs.

La deuxième méthode retournera les valeurs rouge, verte et bleue directement pour chaque pixel, et s'il y a un canal alpha, elle ajoutera la valeur alpha. L'utilisation de cette méthode est plus difficile en termes de calcul d'indices, mais elle est beaucoup plus rapide que la première approche.

Dans mon application, j'ai pu réduire le temps de traitement des pixels de plus de 90% en passant simplement de la première approche à la seconde!

Voici une comparaison que j'ai configurée pour comparer les deux approches:

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

Pouvez-vous deviner la sortie? ;)

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
la source
10
Pour ceux qui sont trop paresseux pour lire le code, il existe deux tests convertTo2DUsingGetRGBet convertTo2DWithoutUsingGetRGB. Le premier test dure en moyenne 16 secondes. Le deuxième test dure en moyenne 1,5 seconde. Au début, je pensais que les «s» et «ms» étaient deux colonnes différentes. @Mota, excellente référence.
Jason
1
@Reddy J'ai essayé, et je vois une différence dans la taille du fichier, je ne sais pas pourquoi! J'ai cependant pu reproduire les valeurs exactes des pixels en utilisant ce code (en utilisant le canal alpha): pastebin.com/zukCK2tu Vous devrez peut-être modifier le troisième argument du constructeur BufferedImage, en fonction de l'image à laquelle vous avez affaire . J'espère que ça aide un peu!
Motasim
4
@Mota In convertTo2DUsingGetRGB pourquoi prenez-vous result [row] [col] = image.getRGB (col, row); au lieu du résultat [row] [col] = image.getRGB (row, col);
Kailash le
6
Personnes remarquant une différence de couleur et / ou un ordre incorrect des octets: le code de @ Mota suppose un ordre BGR . Vous devriez vérifier les entrants BufferedImagede typepar exemple , TYPE_INT_RGBou TYPE_3BYTE_BGRet poignée de façon appropriée. C'est l'une des choses qui getRGB()fait pour vous, qui
ralentit
2
Corrigez-moi si je me trompe, mais ne serait-il pas plus efficace d'utiliser |=au lieu de +=combiner les valeurs de la méthode 2?
Ontonator
24

Quelque chose comme ça?

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
la source
11
N'est-ce pas incroyablement inefficace? J'aurais cependant BufferedImagestocké les pixels en utilisant un tableau 2D int, de toute façon?
ryyst le
1
Je suis presque sûr que l'image est stockée en interne sous la forme d'une structure de données unidimensionnelle. L'opération prendra donc O (W * H), peu importe comment vous le faites. Vous pouvez éviter la surcharge de l'appel de méthode en le stockant d'abord dans un tableau unidimensionnel et en convertissant le tableau unidimensionnel en tableau 2D.
tskuzzy le
4
@ryyst si vous voulez tous les pixels d'un tableau, c'est à peu près aussi efficace que possible
Sean Patrick Floyd
1
+1, je ne pense pas que cela accède à la Rastermémoire tampon de données du, ce qui est certainement une bonne chose car cela entraîne une accélération de la course.
mre
2
@tskuzzy Cette méthode est plus lente. Vérifiez la méthode de Mota, qui est plus rapide que cette méthode conventionnelle.
h4ck3d
20

J'ai trouvé que la réponse de Mota m'a donné une augmentation de vitesse de 10 fois - alors merci Mota.

J'ai enveloppé le code dans une classe pratique qui prend BufferedImage dans le constructeur et expose une méthode getRBG (x, y) équivalente qui en fait une goutte de remplacement pour le code utilisant 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
la source
Je suis nouveau dans le traitement des fichiers image en java. Pouvez-vous expliquer pourquoi faire de getRGB () de cette manière est plus rapide / meilleur / plus optimal que getRGB () de l'API Color? Appréciez!
mk7 le
@ mk7 Veuillez consulter cette réponse stackoverflow.com/a/12062932/363573 . Pour plus de détails, tapez java pourquoi getrgb est lent dans votre moteur de recherche préféré.
Stephan le
10

La réponse de Mota est excellente à moins que votre BufferedImage ne provienne d'un bitmap monochrome. Un bitmap monochrome n'a que 2 valeurs possibles pour ses pixels (par exemple 0 = noir et 1 = blanc). Lorsqu'un bitmap monochrome est utilisé, le

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

call renvoie les données brutes de la matrice de pixels de telle sorte que chaque octet contient plus d'un pixel.

Ainsi, lorsque vous utilisez une image bitmap monochrome pour créer votre objet BufferedImage, c'est l'algorithme que vous souhaitez utiliser:

/**
 * 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
la source
4

Si cela est utile, essayez ceci:

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

byte[] pixels = (byte[])imgBuffer.getRaster().getDataElements(0, 0, imgBuffer.getWidth(), imgBuffer.getHeight(), null);
C-Crestani
la source
14
Une explication serait utile
asheeshr
1

Voici une autre implémentation FastRGB trouvée ici :

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

Qu'est-ce que c'est?

La lecture d'une image pixel par pixel via la méthode getRGB de BufferedImage est assez lente, cette classe est la solution pour cela.

L'idée est que vous construisez l'objet en lui fournissant une instance de BufferedImage, et il lit toutes les données à la fois et les stocke dans un tableau. Une fois que vous voulez obtenir des pixels, vous appelez getRGB

Dépendances

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

Considérations

Bien que FastRGB accélère la lecture des pixels, il peut entraîner une utilisation élevée de la mémoire, car il stocke simplement une copie de l'image. Donc, si vous avez une BufferedImage de 4 Mo dans la mémoire, une fois que vous avez créé l'instance FastRGB, l'utilisation de la mémoire devient 8 Mo. Vous pouvez toutefois recycler l'instance BufferedImage après avoir créé le FastRGB.

Veillez à ne pas tomber dans OutOfMemoryException lorsque vous l'utilisez sur des appareils tels que les téléphones Android, où la RAM est un goulot d'étranglement

Stéphan
la source
-1

Cela a fonctionné pour moi:

BufferedImage bufImgs = ImageIO.read(new File("c:\\adi.bmp"));    
double[][] data = new double[][];
bufImgs.getData().getPixels(0,0,bufImgs.getWidth(),bufImgs.getHeight(),data[i]);    
Une casserole
la source
8
Quelle est la variable i?
Nicolas
it's the iterator for data
Cjen1