Java - görüntüden piksel dizisi alın


118

Bir .pst int[][]dosyasından piksel verilerini (formda ) almanın en hızlı yolunu arıyorum BufferedImage. Amacım (x, y)kullanarak görüntüden pikselleri ele alabilmek int[x][y]. Bulduğum tüm yöntemler bunu yapmıyor (çoğu int[]s döndürüyor).


Hız konusunda endişeliyseniz, neden getRGBve setRGBdoğrudan kullanmak yerine görüntünün tamamını bir diziye kopyalamak istiyorsunuz ?
Brad Mace

3
@bemace: Çünkü benim profilime göre bu yöntemler birinin düşündüğünden daha fazla iş yapıyor gibi görünüyor. Bir diziye erişim çok daha hızlı görünüyor.
ryyst

15
@bemace: Aslında çok yoğun: Bir dizi kullanmak getRGB, setRGBdoğrudan kullanmaktan% 800'den daha hızlıdır .
ryyst

Yanıtlar:


179

Ben de aynı konuyla oynuyordum, bu piksellere erişmenin en hızlı yolu. Şu anda bunu yapmanın iki yolunu biliyorum:

  1. getRGB()@ Tskuzzy'nin cevabında açıklandığı gibi BufferedImage yöntemini kullanma .
  2. Piksel dizisine doğrudan şu şekilde erişerek:

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

Büyük görüntülerle çalışıyorsanız ve performans bir sorunsa, ilk yöntem kesinlikle uygulanacak yol değildir. getRGB()Yöntem bir int içine alfa, kırmızı, yeşil ve mavi değerleri birleştirir ve daha sonra çoğu durumda geri bu değerleri almak için ters yapacağız sonucunu döndürür.

İkinci yöntem, her piksel için doğrudan kırmızı, yeşil ve mavi değerleri döndürür ve bir alfa kanalı varsa alfa değerini ekler. Bu yöntemi kullanmak endeksleri hesaplamak açısından daha zordur, ancak ilk yaklaşımdan çok daha hızlıdır.

Uygulamamda, sadece ilk yaklaşımdan ikinciye geçerek piksellerin işlenme süresini% 90'dan fazla azaltabildim!

İşte iki yaklaşımı karşılaştırmak için kurduğum bir karşılaştırma:

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

Çıktıyı tahmin edebilir misin? ;)

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
Kodu okuyamayacak kadar tembel olanlar için iki test var convertTo2DUsingGetRGBve convertTo2DWithoutUsingGetRGB. İlk test ortalama 16 saniye sürmektedir. İkinci test ortalama 1,5 saniye sürer. İlk başta "s" ve "ms" nin iki farklı sütun olduğunu düşündüm. @Mota, harika referans.
Jason

1
@ Reddy Bir denedim ve dosya boyutunda bir fark görüyorum, neden olduğundan emin değilim! Bununla birlikte, bu kodu kullanarak (alfa kanalını kullanarak) tam piksel değerlerini yeniden üretebildim: pastebin.com/zukCK2tu İlgilendiğiniz görüntüye bağlı olarak BufferedImage yapıcısının üçüncü bağımsız değişkenini değiştirmeniz gerekebilir . Umarım bu biraz yardımcı olur!
Motasim

4
@Mota convertTo2DUsingGetRGB'de neden sonuç alıyorsunuz [satır] [col] = image.getRGB (sütun, satır); sonuç yerine [satır] [col] = image.getRGB (satır, sütun);
Kailash

6
Renk farkı ve / veya yanlış bayt sıralaması fark eden kişiler: @ Mota'nın kodu bir BGR sıralaması varsayar . Gelen kontrol etmelisiniz BufferedImage'ın typeörn TYPE_INT_RGBveya TYPE_3BYTE_BGRuygun şekilde ve kolu. Bu getRGB()sizin için yaptığı şeylerden biri, işi yavaşlatan :-(
millhouse

2
Yanılıyorsam düzelt ama 2. yöntemdeki değerleri birleştirmek |=yerine kullanmak daha verimli olmaz +=mı?
Ontonator

24

Bunun gibi bir şey mi?

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
Bu inanılmaz derecede verimsiz değil mi? Yine BufferedImagede bir 2D int dizisi kullanarak pikselleri depolamak isterdim ?
ryyst

1
Görüntünün tek boyutlu bir veri yapısı olarak dahili olarak saklandığından oldukça eminim. Yani nasıl yaparsanız yapın operasyon O (W * H) alacaktır. İlk önce tek boyutlu bir dizide depolayarak ve tek boyutlu diziyi bir 2D diziye dönüştürerek yöntem çağrısı ek yükünden kaçınabilirsiniz.
tskuzzy

4
@ryyst Bir dizideki tüm pikselleri istiyorsanız, bu olabildiğince verimli
Sean Patrick Floyd

1
+1, bunun Rasterveri arabelleğine eriştiğini sanmıyorum , bu kesinlikle iyi bir şey çünkü bu hızlanmaya neden oluyor.
mre

2
@tskuzzy Bu yöntem daha yavaştır. Bu geleneksel yöntemden daha hızlı olan Mota yöntemini kontrol edin.
h4ck3d

20

Mota'nın cevabının bana 10 kat hız artışı sağladığını gördüm - bu yüzden teşekkürler Mota.

Kodu, yapıcıda BufferedImage'ı alan ve BufferedImage.getRGB (x, y) kullanarak kodun yerini almasını sağlayan eşdeğer bir getRBG (x, y) yöntemini ortaya çıkaran uygun bir sınıfa sardım.

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

Java'da görüntü dosyalarını işleme konusunda yeniyim. GetRGB () 'yi bu şekilde yapmanın Color API's getRGB ()' den neden daha hızlı / daha iyi / daha uygun olduğunu açıklayabilir misiniz? Takdir etmek !
mk7

@ mk7 Lütfen bu yanıta bir göz atın stackoverflow.com/a/12062932/363573 . Daha fazla ayrıntı için java yazın , favori arama motorunuzda getrgb neden yavaş ?
Stephan

10

Mota'nın cevabı, BufferedImage'ınız Siyah Beyaz Bitmap'ten gelmediği sürece harika. Tek Renkli Bitmap'in pikselleri için yalnızca 2 olası değeri vardır (örneğin 0 = siyah ve 1 = beyaz). Tek Renkli Bitmap kullanıldığında

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

call, ham Piksel Dizisi verilerini, her bayt birden fazla piksel içerecek şekilde döndürür.

Dolayısıyla, BufferedImage nesnenizi oluşturmak için Tek Renkli Bitmap görüntüsü kullandığınızda, kullanmak istediğiniz algoritma budur:

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

Yararlıysa şunu deneyin:

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

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

14
Bir açıklama yardımcı olacaktır
asheeshr

1

Burada başka bir FastRGB uygulama buldum olduğunu burada :

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

Bu nedir?

BufferedImage'ın getRGB yöntemi ile bir görüntüyü piksel piksel okumak oldukça yavaştır, bu sınıf bunun çözümüdür.

Buradaki fikir, nesneyi bir BufferedImage örneğini besleyerek oluşturmanız ve tüm verileri bir kerede okuyup bir dizide depolamasıdır. Pikselleri almak istediğinizde getRGB'yi çağırırsınız

Bağımlılıklar

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

hususlar

FastRGB piksel okumayı çok daha hızlı hale getirse de, yalnızca görüntünün bir kopyasını sakladığı için yüksek bellek kullanımına neden olabilir. Dolayısıyla, bellekte 4MB BufferedImage varsa, FastRGB örneğini oluşturduğunuzda bellek kullanımı 8MB olur. Ancak FastRGB'yi oluşturduktan sonra BufferedImage örneğini geri dönüştürebilirsiniz.

RAM'in darboğaz olduğu Android telefonlar gibi cihazlarda kullanırken OutOfMemoryException'a girmemeye dikkat edin


-1

Bu benim için çalıştı:

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
Değişken inedir?
Nicolas

veriler için yineleyici
Cjen1
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.