Java - รับอาร์เรย์พิกเซลจากรูปภาพ


118

ฉันกำลังมองหาวิธีที่เร็วที่สุดในการรับข้อมูลพิกเซล (int แบบฟอร์มint[][]) จากไฟล์BufferedImage. เป้าหมายของฉันคือสามารถกำหนดพิกเซล(x, y)จากรูปภาพโดยใช้ไฟล์int[x][y]. วิธีการทั้งหมดที่ฉันพบไม่ทำเช่นนี้ (ส่วนใหญ่ส่งคืนint[]s)


หากคุณกังวลเกี่ยวกับความเร็วทำไมคุณถึงต้องการคัดลอกภาพทั้งหมดไปยังอาร์เรย์แทนที่จะใช้getRGBและsetRGBโดยตรง
Brad Mace

3
@bemace: เนื่องจากวิธีการเหล่านั้นดูเหมือนจะได้ผลมากกว่าที่คิดตามการทำโปรไฟล์ของฉัน การเข้าถึงอาร์เรย์ดูเหมือนจะเร็วกว่า
ryyst

15
@bemace: มันรุนแรงมากจริงๆ : การใช้อาร์เรย์นั้นเร็วกว่าการใช้getRGBและsetRGBโดยตรงมากกว่า 800%
ryyst

คำตอบ:


179

ฉันแค่เล่นกับเรื่องเดียวกันนี้ซึ่งเป็นวิธีที่เร็วที่สุดในการเข้าถึงพิกเซล ปัจจุบันฉันรู้สองวิธีในการทำสิ่งนี้:

  1. ใช้getRGB()วิธีการของ BufferedImage ตามที่อธิบายไว้ในคำตอบของ @tskuzzy
  2. โดยการเข้าถึงอาร์เรย์พิกเซลโดยตรงโดยใช้:

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

หากคุณกำลังทำงานกับภาพขนาดใหญ่และประสิทธิภาพเป็นปัญหาวิธีแรกไม่ใช่วิธีที่จะไปอย่างแน่นอน getRGB()วิธีการรวมอัลฟา, สีแดง, สีเขียวและค่าสีฟ้าเป็นหนึ่ง int แล้วผลตอบแทนซึ่งในกรณีส่วนใหญ่คุณจะทำย้อนกลับที่จะได้รับค่าเหล่านี้กลับมา

วิธีที่สองจะส่งคืนค่าสีแดงสีเขียวและสีน้ำเงินสำหรับแต่ละพิกเซลโดยตรงและหากมีช่องอัลฟาระบบจะเพิ่มค่าอัลฟา การใช้วิธีนี้ยากกว่าในแง่ของการคำนวณดัชนี แต่จะเร็วกว่าวิธีแรกมาก

ในแอปพลิเคชันของฉันฉันสามารถลดเวลาในการประมวลผลพิกเซลได้มากกว่า 90% เพียงแค่เปลี่ยนจากวิธีแรกไปเป็นวิธีที่สอง!

นี่คือการเปรียบเทียบที่ฉันได้ตั้งค่าเพื่อเปรียบเทียบสองแนวทาง:

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

คุณสามารถเดาผลลัพธ์ได้หรือไม่? ;)

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
สำหรับผู้ที่ขี้เกียจอ่านโค้ดมีสองการทดสอบconvertTo2DUsingGetRGBและconvertTo2DWithoutUsingGetRGB. การทดสอบครั้งแรกโดยเฉลี่ยใช้เวลา 16 วินาที การทดสอบครั้งที่สองโดยเฉลี่ยใช้เวลา 1.5 วินาที ตอนแรกฉันคิดว่า "s" และ "ms" เป็นสองคอลัมน์ที่แตกต่างกัน @ โมตาข้อมูลอ้างอิงที่ดี
Jason

1
@Reddy ฉันลองดูแล้วและฉันเห็นความแตกต่างของขนาดไฟล์ซึ่งฉันไม่แน่ใจว่าทำไม! อย่างไรก็ตามฉันสามารถสร้างค่าพิกเซลที่แน่นอนได้โดยใช้รหัสนี้ (โดยใช้ช่องอัลฟา): pastebin.com/zukCK2tuคุณอาจต้องแก้ไขอาร์กิวเมนต์ที่สามของตัวสร้าง BufferedImage ขึ้นอยู่กับภาพที่คุณกำลังจัดการ . หวังว่านี่จะช่วยได้เล็กน้อย!
Motasim

4
@Mota ใน convertTo2DUsingGetRGB ทำไมคุณถึงได้ผลลัพธ์ [row] [col] = image.getRGB (col, row); แทนที่จะเป็นผลลัพธ์ [row] [col] = image.getRGB (row, col);
Kailash

6
ผู้ที่สังเกตเห็นความแตกต่างของสีและ / หรือการเรียงลำดับไบต์ที่ไม่ถูกต้อง: รหัสของ @ Mota ถือว่าเป็นการสั่งซื้อBGR คุณควรตรวจสอบเข้ามาBufferedImage's typeเช่นTYPE_INT_RGBหรือTYPE_3BYTE_BGRและจัดการอย่างเหมาะสม นี่เป็นหนึ่งในสิ่งที่getRGB()ทำเพื่อคุณซึ่งทำให้ช้าลง :-(
millhouse

2
แก้ไขฉันถ้าฉันผิด แต่จะไม่มีประสิทธิภาพมากกว่าที่จะใช้|=แทน+=การรวมค่าในวิธีที่ 2 หรือไม่?
Ontonator

24

อะไรทำนองนี้?

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
ไม่ได้มีประสิทธิภาพอย่างไม่น่าเชื่อ? ฉันBufferedImageจะเก็บพิกเซลโดยใช้อาร์เรย์ int 2D หรือไม่?
ryyst

1
ฉันค่อนข้างมั่นใจว่ารูปภาพถูกเก็บไว้ภายในเป็นโครงสร้างข้อมูลมิติเดียว ดังนั้นการดำเนินการจะใช้ O (W * H) ไม่ว่าคุณจะทำอย่างไร คุณสามารถหลีกเลี่ยงค่าใช้จ่ายในการเรียกเมธอดได้โดยเก็บไว้ในอาร์เรย์มิติเดียวก่อนแล้วแปลงอาร์เรย์มิติเดียวเป็นอาร์เรย์ 2D
tskuzzy

4
@ryyst ถ้าคุณต้องการพิกเซลทั้งหมดในอาร์เรย์สิ่งนี้มีประสิทธิภาพเท่าที่จะเป็นไปได้
Sean Patrick Floyd

1
+1 ฉันไม่คิดว่าสิ่งนี้เข้าถึงRasterบัฟเฟอร์ข้อมูลซึ่งเป็นสิ่งที่ดีอย่างแน่นอนเนื่องจากส่งผลให้เกิดการเร่งความเร็ว
MRE

2
@tskuzzy วิธีนี้ช้ากว่า ตรวจสอบวิธีโดย Mota ซึ่งเร็วกว่าวิธีเดิมนี้
h4ck3d

20

ฉันพบว่าคำตอบของ Mota ทำให้ความเร็วเพิ่มขึ้น 10 เท่า - ขอบคุณ Mota

ฉันได้รวมโค้ดไว้ในคลาสที่สะดวกซึ่งใช้ BufferedImage ในตัวสร้างและแสดงเมธอด getRBG (x, y) ที่เทียบเท่าซึ่งทำให้การแทนที่โค้ดลดลงโดยใช้ 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;
    }
}

ฉันเพิ่งเริ่มประมวลผลไฟล์ภาพใน java คุณอธิบายได้ไหมว่าทำไมการสร้าง getRGB () ด้วยวิธีนี้จึงเร็ว / ดีกว่า / เหมาะสมกว่า getRGB () ของ Color API ซาบซึ้ง!
mk7

@ mk7 โปรดดูที่คำตอบนี้stackoverflow.com/a/12062932/363573 สำหรับรายละเอียดเพิ่มเติมให้พิมพ์java ทำไม getrgb ถึงช้าในเครื่องมือค้นหาที่คุณชื่นชอบ
Stephan

10

คำตอบของ Mota นั้นยอดเยี่ยมเว้นแต่ว่า BufferedImage ของคุณมาจาก Monochrome Bitmap Monochrome Bitmap มีเพียง 2 ค่าที่เป็นไปได้สำหรับพิกเซล (เช่น 0 = black และ 1 = white) เมื่อใช้ Monochrome Bitmap แล้วไฟล์

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

call ส่งคืนข้อมูล Raw Pixel Array ในลักษณะที่แต่ละไบต์มีมากกว่าหนึ่งพิกเซล

ดังนั้นเมื่อคุณใช้ภาพ Monochrome Bitmap เพื่อสร้างวัตถุ BufferedImage ของคุณนี่คืออัลกอริทึมที่คุณต้องการใช้:

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

หากมีประโยชน์ให้ลองทำดังนี้

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

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

14
คำอธิบายจะเป็นประโยชน์
asheeshr

1

นี่คือการใช้งาน FastRGB อื่นที่พบที่นี่ :

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

นี่คืออะไร?

การอ่านพิกเซลภาพทีละพิกเซลผ่านเมธอด getRGB ของ BufferedImage นั้นค่อนข้างช้าคลาสนี้เป็นทางออกสำหรับสิ่งนี้

แนวคิดก็คือคุณสร้างวัตถุโดยป้อนอินสแตนซ์ BufferedImage และอ่านข้อมูลทั้งหมดพร้อมกันและเก็บไว้ในอาร์เรย์ เมื่อคุณต้องการรับพิกเซลคุณเรียก getRGB

การอ้างอิง

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

การพิจารณา

แม้ว่า FastRGB จะทำให้การอ่านพิกเซลเร็วขึ้นมาก แต่ก็อาจนำไปสู่การใช้หน่วยความจำสูงเนื่องจากเพียงแค่เก็บสำเนาของภาพ ดังนั้นหากคุณมี BufferedImage ขนาด 4MB ในหน่วยความจำเมื่อคุณสร้างอินสแตนซ์ FastRGB การใช้หน่วยความจำจะกลายเป็น 8MB อย่างไรก็ตามคุณสามารถรีไซเคิลอินสแตนซ์ BufferedImage ได้หลังจากที่คุณสร้าง FastRGB

ระวังอย่าตกอยู่ในOutOfMemoryExceptionเมื่อใช้งานบนอุปกรณ์เช่นโทรศัพท์ Android ที่ RAM เป็นคอขวด


-1

สิ่งนี้ใช้ได้ผลสำหรับฉัน:

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
ตัวแปรiคืออะไร?
Nicolas

มันเป็นตัววนซ้ำสำหรับข้อมูล
Cjen1
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.