Java - pobierz tablicę pikseli z obrazu


118

Szukam najszybszego sposobu na uzyskanie danych pikseli (w formularzu int[][]) z pliku BufferedImage. Moim celem jest możliwość adresowania piksela (x, y)z obrazu za pomocą int[x][y]. Nie działają wszystkie metody, które znalazłem (większość z nich zwraca int[]s).


Jeśli martwisz się o prędkość, dlaczego chcesz skopiować cały obraz do tablicy zamiast używać getRGBi setRGBbezpośrednio?
Brad Mace

3
@bemace: Ponieważ te metody wydają się wykonywać więcej pracy, niż mogłoby się wydawać, według mojego profilu. Dostęp do tablicy wydaje się znacznie szybszy.
ryyst

15
@bemace: To naprawdę bardzo intensywne: używanie tablicy jest o ponad 800% szybsze niż używanie getRGBi setRGBbezpośrednio.
ryyst

Odpowiedzi:


179

Po prostu bawiłem się tym samym tematem, który jest najszybszym sposobem uzyskania dostępu do pikseli. Obecnie znam dwa sposoby, aby to zrobić:

  1. Korzystanie z getRGB()metody BufferedImage, jak opisano w odpowiedzi @ tskuzzy.
  2. Uzyskując dostęp do tablicy pikseli bezpośrednio za pomocą:

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

Jeśli pracujesz z dużymi obrazami i problemem jest wydajność, pierwsza metoda absolutnie nie jest właściwą. getRGB()Metoda łączy alfa, czerwony, zielony i niebieski wartości w jednym int a następnie zwraca wynik, który w większości przypadków będziesz robić na odwrót, aby te wartości z powrotem.

Druga metoda zwróci wartości czerwonego, zielonego i niebieskiego bezpośrednio dla każdego piksela, a jeśli istnieje kanał alfa, doda wartość alfa. Korzystanie z tej metody jest trudniejsze pod względem obliczania wskaźników, ale jest znacznie szybsze niż pierwsze podejście.

W mojej aplikacji udało mi się skrócić czas przetwarzania pikseli o ponad 90%, po prostu przełączając się z pierwszego podejścia na drugie!

Oto porównanie, które skonfigurowałem, aby porównać dwa podejścia:

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

Czy potrafisz odgadnąć wynik? ;)

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
Dla tych, którzy są zbyt leniwi, aby przeczytać kod, są dwa testy convertTo2DUsingGetRGBi convertTo2DWithoutUsingGetRGB. Pierwszy test trwa średnio 16 sekund. Drugi test trwa średnio 1,5 sekundy. Na początku myślałem, że „s” i „ms” to dwie różne kolumny. @Mota, świetne odniesienie.
Jason

1
@Reddy Spróbowałem i widzę różnicę w rozmiarze pliku, której nie jestem pewien, dlaczego! Udało mi się jednak odtworzyć dokładne wartości pikseli za pomocą tego kodu (używając kanału alfa): pastebin.com/zukCK2tu Być może będziesz musiał zmodyfikować trzeci argument konstruktora BufferedImage, w zależności od obrazu, z którym masz do czynienia . Mam nadzieję, że to trochę pomoże!
Motasim

4
@Mota W convertTo2DUsingGetRGB dlaczego bierzesz wynik [wiersz] [kol] = obraz.getRGB (kol, wiersz); zamiast wyniku [wiersz] [kol] = obraz.getRGB (wiersz, kolumna);
Kailash

6
Osoby zauważające różnicę kolorów i / lub nieprawidłową kolejność bajtów: kod @ Moty zakłada porządkowanie BGR . Należy sprawdzić przychodzące BufferedImagejest typenp TYPE_INT_RGBlub TYPE_3BYTE_BGRi uchwyt odpowiednio. To jest jedna z rzeczy, które getRGB()robią dla ciebie, to spowalniają :-(
millhouse

2
Popraw mnie, jeśli się mylę, ale czy nie byłoby bardziej wydajne w użyciu |=zamiast +=łączenia wartości w metodzie 2?
Ontonator,

24

Coś takiego?

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
Czy to nie jest niesamowicie nieefektywne? W BufferedImagekażdym razie chciałbym przechowywać piksele przy użyciu tablicy int 2D?
ryyst

1
Jestem prawie pewien, że obraz jest przechowywany wewnętrznie jako jednowymiarowa struktura danych. Więc operacja zajmie O (W * H) bez względu na to, jak to zrobisz. Możesz uniknąć narzutu wywołania metody, przechowując go najpierw w jednowymiarowej tablicy i konwertując jednowymiarową tablicę na tablicę 2D.
tskuzzy

4
@ryyst, jeśli chcesz mieć wszystkie piksele w tablicy, jest to mniej więcej tak wydajne, jak to tylko możliwe
Sean Patrick Floyd

1
+1, nie sądzę, żeby to miało dostęp do Rasterbufora danych, co jest zdecydowanie dobrą rzeczą, ponieważ powoduje to przyspieszenie.
mre

2
@tskuzzy Ta metoda jest wolniejsza. Sprawdź metodę firmy Mota, która jest szybsza niż ta konwencjonalna.
h4ck3d

20

Okazało się, że odpowiedź Moty dała mi 10-krotny wzrost prędkości - dzięki Mota.

Zapakowałem kod w wygodną klasę, która przyjmuje BufferedImage w konstruktorze i ujawnia równoważną metodę getRBG (x, y), która sprawia, że ​​jest to kropla w zastępstwie kodu za pomocą 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;
    }
}

Jestem nowy w przetwarzaniu plików graficznych w Javie. Czy możesz wyjaśnić, dlaczego tworzenie getRGB () w ten sposób jest szybsze / lepsze / bardziej optymalne niż getRGB () w Color API? Doceń!
mk7

@ mk7 Zapoznaj się z tą odpowiedzią stackoverflow.com/a/12062932/363573 . Aby uzyskać więcej informacji, wpisz java, dlaczego getrgb działa wolno w Twojej ulubionej wyszukiwarce.
Stephan

10

Odpowiedź Moty jest świetna, chyba że twój BufferedImage pochodzi z Monochrome Bitmap. Monochromatyczna mapa bitowa ma tylko 2 możliwe wartości swoich pikseli (na przykład 0 = czarny i 1 = biały). Gdy używana jest monochromatyczna mapa bitowa, plik

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

call zwraca surowe dane Pixel Array w taki sposób, że każdy bajt zawiera więcej niż jeden piksel.

Więc kiedy używasz obrazu Monochrome Bitmap do tworzenia obiektu BufferedImage, to jest to algorytm, którego chcesz użyć:

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

Jeśli to przydatne, spróbuj tego:

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

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

14
Pomocne byłoby wyjaśnienie
asheeshr

1

Oto kolejna implementacja FastRGB znaleziona tutaj :

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

Co to jest?

Odczytywanie obrazu piksel po pikselu za pomocą metody getRGB firmy BufferedImage jest dość powolne, ta klasa jest rozwiązaniem tego problemu.

Chodzi o to, że konstruujesz obiekt, przekazując mu instancję BufferedImage, a następnie odczytuje on wszystkie dane na raz i zapisuje je w tablicy. Gdy chcesz uzyskać piksele, zadzwoń do getRGB

Zależności

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

Rozważania

Chociaż FastRGB znacznie przyspiesza odczyt pikseli, może to prowadzić do dużego zużycia pamięci, ponieważ po prostu przechowuje kopię obrazu. Jeśli więc masz w pamięci 4 MB BufferedImage, po utworzeniu instancji FastRGB użycie pamięci wyniesie 8 MB. Możesz jednak odtworzyć wystąpienie BufferedImage po utworzeniu FastRGB.

Uważaj, aby nie wpaść w OutOfMemoryException podczas używania go na urządzeniach takich jak telefony z Androidem, gdzie RAM jest wąskim gardłem


-1

To zadziałało dla mnie:

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
Jaka jest zmienna i?
Nicolas

to iterator dla danych
Cjen1
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.