Java - dapatkan array piksel dari gambar

118

Saya mencari cara tercepat untuk mendapatkan data piksel (dalam bentuk int[][]) dari a BufferedImage. Tujuan saya adalah untuk dapat menangani piksel (x, y)dari gambar menggunakan int[x][y]. Semua metode yang saya temukan tidak melakukan ini (kebanyakan dari mereka mengembalikan int[]).

ryyst
sumber
Jika Anda khawatir tentang kecepatan, mengapa Anda ingin menyalin seluruh gambar ke array daripada hanya menggunakan getRGBdan setRGBsecara langsung?
Brad Mace
3
@bemace: Karena metode tersebut tampaknya melakukan lebih banyak pekerjaan daripada yang dipikirkan orang, menurut profil saya. Mengakses array tampaknya jauh lebih cepat.
ryyst
15
@bemace: Ini sebenarnya sangat intens: menggunakan array lebih dari 800% lebih cepat daripada menggunakan getRGBdan setRGBsecara langsung.
ryyst

Jawaban:

179

Saya baru saja bermain-main dengan subjek yang sama ini, yang merupakan cara tercepat untuk mengakses piksel. Saat ini saya mengetahui dua cara untuk melakukan ini:

  1. Menggunakan BufferedImage's getRGB() metode seperti yang dijelaskan dalam jawaban @ tskuzzy.
  2. Dengan mengakses larik piksel secara langsung menggunakan:

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

Jika Anda bekerja dengan gambar besar dan kinerja adalah masalah, metode pertama sama sekali bukan cara yang tepat. ItugetRGB() Metode menggabungkan alpha, merah, nilai-nilai hijau dan biru menjadi satu int dan kemudian mengembalikan hasil, yang dalam banyak kasus Anda akan melakukan hal yang sebaliknya untuk mendapatkan nilai-nilai ini kembali.

Metode kedua akan mengembalikan nilai merah, hijau dan biru secara langsung untuk setiap piksel, dan jika ada saluran alfa akan menambahkan nilai alfa. Menggunakan metode ini lebih sulit dalam hal menghitung indeks, tetapi jauh lebih cepat daripada pendekatan pertama.

Dalam aplikasi saya, saya dapat mengurangi waktu pemrosesan piksel lebih dari 90% dengan hanya beralih dari pendekatan pertama ke yang kedua!

Berikut adalah perbandingan yang telah saya siapkan untuk membandingkan dua pendekatan:

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

Bisakah Anda menebak hasilnya? ;)

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
sumber
10
Bagi mereka yang terlalu malas membaca kode, ada dua tes convertTo2DUsingGetRGBdan convertTo2DWithoutUsingGetRGB. Tes pertama rata-rata membutuhkan waktu 16 detik. Tes kedua rata-rata membutuhkan waktu 1,5 detik. Awalnya saya pikir "s" dan "ms" adalah dua kolom yang berbeda. @Mota, referensi bagus.
Jason
1
@Reddy Saya mencobanya, dan saya melihat perbedaan dalam ukuran file, yang saya tidak yakin mengapa! Namun, saya telah dapat mereproduksi nilai piksel yang tepat menggunakan kode ini (menggunakan saluran alfa): pastebin.com/zukCK2tu Anda mungkin perlu mengubah argumen ketiga dari konstruktor BufferedImage, tergantung pada gambar yang Anda hadapi . Semoga ini bisa sedikit membantu!
Motasim
4
@Mota Dalam convertTo2DUsingGetRGB mengapa Anda mengambil hasil [baris] [col] = image.getRGB (kolom, baris); sebagai ganti hasil [baris] [col] = image.getRGB (baris, kolom);
Kailash
6
Orang yang memperhatikan perbedaan warna dan / atau urutan byte yang salah: Kode @ Mota mengasumsikan pengurutan BGR . Anda harus memeriksa masuk BufferedImage's typemisalnya TYPE_INT_RGBatau TYPE_3BYTE_BGRdan menangani dengan tepat. Ini adalah salah satu hal yang bermanfaat getRGB()bagi Anda, yang membuatnya lebih lambat :-(
millhouse
2
Koreksi saya jika saya salah, tetapi bukankah akan lebih efisien jika digunakan |=daripada +=menggabungkan nilai dalam metode 2?
Ontonator
24

Sesuatu seperti ini?

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
sumber
11
Bukankah itu sangat tidak efisien? Saya BufferedImageakan menyimpan piksel menggunakan array int 2D, sih?
ryyst
1
Saya cukup yakin gambar disimpan secara internal sebagai struktur data satu dimensi. Jadi operasi akan memakan waktu O (W * H) tidak peduli bagaimana Anda melakukannya. Anda dapat menghindari overhead panggilan metode dengan menyimpannya ke dalam larik dimensi tunggal terlebih dahulu dan mengonversi larik dimensi tunggal menjadi larik 2D.
tskuzzy
4
@ryyst jika Anda ingin semua piksel dalam sebuah array, ini seefisien mungkin
Sean Patrick Floyd
1
+1, Saya rasa ini tidak mengakses Rasterbuffer data, yang tentunya merupakan hal yang baik karena menghasilkan akselerasi punting.
mre
2
@tskuzzy Metode ini lebih lambat. Periksa metode Mota, yang lebih cepat dari metode konvensional ini.
h4ck3d
20

Saya menemukan jawaban Mota memberi saya peningkatan kecepatan 10 kali lipat - jadi terima kasih Mota.

Saya telah membungkus kode dalam kelas yang nyaman yang mengambil BufferedImage di konstruktor dan mengekspos metode getRBG (x, y) yang setara yang menjadikannya sebagai pengganti kode menggunakan 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
sumber
Saya baru dalam memproses file gambar di java. Bisakah Anda menjelaskan mengapa membuat getRGB () dengan cara ini lebih cepat / lebih baik / lebih optimal daripada getRGB () Color API? Hargai!
mk7
@ mk7 Silakan lihat jawaban ini stackoverflow.com/a/12062932/363573 . Untuk lebih jelasnya ketik java why getrgb lambat di mesin pencari favorit Anda.
Stephan
10

Jawaban Mota sangat bagus kecuali BufferedImage Anda berasal dari Monochrome Bitmap. Bitmap Monokrom hanya memiliki 2 kemungkinan nilai untuk pikselnya (misalnya 0 = hitam dan 1 = putih). Ketika Monochrome Bitmap digunakan maka file

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

panggilan mengembalikan data mentah Pixel Array sedemikian rupa sehingga setiap byte berisi lebih dari satu piksel.

Jadi ketika Anda menggunakan gambar Monochrome Bitmap untuk membuat objek BufferedImage, berikut adalah algoritme yang ingin Anda gunakan:

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

Jika berguna, coba ini:

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

byte[] pixels = (byte[])imgBuffer.getRaster().getDataElements(0, 0, imgBuffer.getWidth(), imgBuffer.getHeight(), null);
C-Crestani
sumber
14
Penjelasan akan membantu
asheeshr
1

Berikut adalah implementasi FastRGB lain yang ditemukan di sini :

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

Apa ini?

Membaca gambar piksel demi piksel melalui metode getRGB BufferedImage cukup lambat, kelas ini adalah solusi untuk ini.

Idenya adalah Anda membuat objek dengan memberinya instance BufferedImage, dan itu membaca semua data sekaligus dan menyimpannya dalam array. Setelah Anda ingin mendapatkan piksel, Anda memanggil getRGB

Dependensi

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

Pertimbangan

Meskipun FastRGB membuat piksel membaca jauh lebih cepat, itu dapat menyebabkan penggunaan memori yang tinggi, karena hanya menyimpan salinan gambar. Jadi, jika Anda memiliki BufferedImage 4MB di memori, setelah Anda membuat instance FastRGB, penggunaan memori akan menjadi 8MB. Namun, Anda dapat mendaur ulang instance BufferedImage setelah Anda membuat FastRGB.

Berhati-hatilah agar tidak jatuh ke OutOfMemoryException saat menggunakannya di perangkat seperti ponsel Android, di mana RAM menjadi penghambat

Stephan
sumber
-1

Ini berhasil untuk saya:

BufferedImage bufImgs = ImageIO.read(new File("c:\\adi.bmp"));    
double[][] data = new double[][];
bufImgs.getData().getPixels(0,0,bufImgs.getWidth(),bufImgs.getHeight(),data[i]);    
APan
sumber
8
Apa variabelnya i?
Nicolas
ini adalah iterator untuk data
Cjen1