Java - lấy mảng pixel từ hình ảnh


118

Tôi đang tìm cách nhanh nhất để lấy dữ liệu pixel (trong biểu mẫu int[][]) từ a BufferedImage. Mục tiêu của tôi là có thể xử lý pixel (x, y)từ hình ảnh bằng cách sử dụng int[x][y]. Tất cả các phương pháp tôi đã tìm thấy đều không làm được điều này (hầu hết chúng đều trả về int[]s).


Nếu bạn lo lắng về tốc độ, tại sao bạn muốn sao chép toàn bộ hình ảnh vào một mảng thay vì chỉ sử dụng getRGBsetRGBtrực tiếp?
Brad Mace

3
@bemace: Bởi vì những phương pháp đó dường như thực hiện nhiều công việc hơn người ta có thể nghĩ, theo hồ sơ của tôi. Truy cập một mảng có vẻ nhanh hơn.
ryyst

15
@bemace: Nó thực sự rất dữ dội: sử dụng một mảng nhanh hơn 800% so với sử dụng getRGBsetRGBtrực tiếp.
ryyst

Câu trả lời:


179

Tôi chỉ đang chơi với cùng một chủ đề này, đó là cách nhanh nhất để truy cập pixel. Tôi hiện biết hai cách để làm điều này:

  1. Sử dụng getRGB()phương pháp của BufferedImage như được mô tả trong câu trả lời của @ tskuzzy.
  2. Bằng cách truy cập trực tiếp vào mảng pixel bằng cách sử dụng:

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

Nếu bạn đang làm việc với hình ảnh lớn và hiệu suất là một vấn đề, thì phương pháp đầu tiên hoàn toàn không phải là cách tốt nhất. Các getRGB()phương pháp kết hợp các alpha, màu đỏ, xanh lá cây và xanh da trời vào một int và sau đó trả về kết quả, mà trong hầu hết các trường hợp, bạn sẽ làm điều ngược lại để có được những giá trị trở lại.

Phương thức thứ hai sẽ trả về các giá trị đỏ, lục và lam trực tiếp cho mỗi pixel và nếu có kênh alpha, nó sẽ thêm giá trị alpha. Sử dụng phương pháp này khó hơn về mặt tính toán các chỉ số, nhưng nhanh hơn nhiều so với cách tiếp cận đầu tiên.

Trong ứng dụng của mình, tôi đã có thể giảm hơn 90% thời gian xử lý pixel chỉ bằng cách chuyển từ cách tiếp cận đầu tiên sang cách tiếp cận thứ hai!

Đây là một so sánh mà tôi đã thiết lập để so sánh hai cách tiếp cận:

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

Bạn có thể đoán đầu ra? ;)

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
Đối với những người quá lười biếng để đọc mã, có hai bài kiểm tra convertTo2DUsingGetRGBconvertTo2DWithoutUsingGetRGB. Bài kiểm tra đầu tiên trung bình mất 16 giây. Bài kiểm tra thứ hai trung bình mất 1,5 giây. Lúc đầu, tôi nghĩ "s" và "ms" là hai cột khác nhau. @Mota, tài liệu tham khảo tuyệt vời.
Jason

1
@Reddy Tôi đã thử và tôi thấy có sự khác biệt về kích thước tệp mà tôi không rõ tại sao! Tuy nhiên, tôi có thể tạo lại các giá trị pixel chính xác bằng mã này (sử dụng kênh alpha): pastebin.com/zukCK2tu Bạn có thể cần sửa đổi đối số thứ ba của hàm tạo BufferedImage, tùy thuộc vào hình ảnh bạn đang xử lý . Mong cái này giúp được chút ít!
Motasim

4
@Mota Trong convertTo2DUsingGetRGB tại sao bạn lấy kết quả [row] [col] = image.getRGB (col, row); thay vì result [row] [col] = image.getRGB (row, col);
Kailash

6
Mọi người nhận thấy sự khác biệt về màu sắc và / hoặc thứ tự byte không chính xác: Mã của @ Mota giả định thứ tự BGR . Bạn nên kiểm tra đến BufferedImage's typeví dụ TYPE_INT_RGBhay TYPE_3BYTE_BGRvà xử lý một cách thích hợp. Đây là một trong những điều mà getRGB()làm cho bạn, mà làm cho nó chậm hơn :-(
Millhouse

2
Hãy sửa cho tôi nếu tôi sai, nhưng sẽ không hiệu quả hơn nếu sử dụng |=thay vì +=kết hợp các giá trị trong phương pháp 2?
Ontonator

24

Một cái gì đó như thế này?

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
Đó không phải là cực kỳ kém hiệu quả? Dù sao thì tôi cũng BufferedImagesẽ lưu trữ các pixel bằng cách sử dụng mảng int 2D?
ryyst

1
Tôi khá chắc chắn rằng hình ảnh được lưu trữ bên trong dưới dạng cấu trúc dữ liệu đơn chiều. Vì vậy, hoạt động sẽ mất O (W * H) cho dù bạn thực hiện như thế nào. Bạn có thể tránh chi phí cuộc gọi phương thức bằng cách lưu trữ nó vào một mảng đơn chiều trước và chuyển đổi mảng một chiều thành mảng 2D.
tskuzzy

4
@ryyst nếu bạn muốn tất cả các pixel trong một mảng, đây là về hiệu quả như nó được
Sean Patrick Floyd

1
+1, tôi không nghĩ điều này truy cập vào Rasterbộ đệm dữ liệu của, đây chắc chắn là một điều tốt vì điều đó dẫn đến việc tăng tốc.
mre

2
@tskuzzy Phương pháp này chậm hơn. Kiểm tra phương pháp bằng Mota, nhanh hơn phương pháp thông thường này.
h4ck3d

20

Tôi thấy câu trả lời của Mota đã giúp tôi tăng tốc độ gấp 10 lần - vì vậy, cảm ơn Mota.

Tôi đã gói mã trong một lớp thuận tiện lấy BufferedImage trong hàm tạo và hiển thị một phương thức getRBG (x, y) tương đương khiến nó giảm thay thế cho mã bằng 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;
    }
}

Tôi mới xử lý tệp hình ảnh trong java. Bạn có thể giải thích lý do tại sao tạo getRGB () theo cách này nhanh hơn / tốt hơn / tối ưu hơn getRGB của API màu () không? Đánh giá !
mk7

@ mk7 Vui lòng xem câu trả lời này stackoverflow.com/a/12062932/363573 . Để biết thêm chi tiết, gõ java tại sao getrgb chậm trong công cụ tìm kiếm yêu thích của bạn.
Stephan

10

Câu trả lời của Mota là tuyệt vời trừ khi BufferedImage của bạn đến từ một Bitmap đơn sắc. Một Bitmap đơn sắc chỉ có 2 giá trị khả dĩ cho các pixel của nó (ví dụ: 0 = đen và 1 = trắng). Khi một Bản đồ bit đơn sắc được sử dụng thì

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

lệnh gọi trả về dữ liệu Mảng Pixel thô theo kiểu sao cho mỗi byte chứa nhiều hơn một pixel.

Vì vậy, khi bạn sử dụng hình ảnh Bitmap đơn sắc để tạo đối tượng BufferedImage thì đây là thuật toán bạn muốn sử dụng:

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

Nếu hữu ích, hãy thử điều này:

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

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

14
Một lời giải thích sẽ là hữu ích
asheeshr

1

Đây là một triển khai FastRGB khác được tìm thấy ở đây :

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

Cái này là cái gì?

Việc đọc từng pixel ảnh thông qua phương thức getRGB của BufferedImage khá chậm, lớp này là giải pháp cho việc này.

Ý tưởng là bạn xây dựng đối tượng bằng cách cung cấp cho nó một cá thể BufferedImage, và nó đọc tất cả dữ liệu cùng một lúc và lưu trữ chúng trong một mảng. Khi bạn muốn lấy pixel, bạn gọi getRGB

Sự phụ thuộc

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

Cân nhắc

Mặc dù FastRGB giúp việc đọc pixel nhanh hơn nhiều, nhưng nó có thể dẫn đến việc sử dụng bộ nhớ cao, vì nó chỉ lưu trữ một bản sao của hình ảnh. Vì vậy, nếu bạn có 4MB BufferedImage trong bộ nhớ, khi bạn tạo phiên bản FastRGB, mức sử dụng bộ nhớ sẽ trở thành 8MB. Tuy nhiên, bạn có thể tái chế cá thể BufferedImage sau khi tạo FastRGB.

Hãy cẩn thận để không rơi vào OutOfMemoryException khi sử dụng nó trên các thiết bị như điện thoại Android, nơi RAM là nút cổ chai


-1

Điều này đã làm việc cho tôi:

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
Biến là igì?
Nicolas

nó là trình lặp cho dữ liệu
Cjen1
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.