Java-이미지에서 픽셀 배열 가져 오기


118

에서 픽셀 데이터 (형식 내 int[][]) 를 얻는 가장 빠른 방법을 찾고 BufferedImage있습니다. 내 목표는를 (x, y)사용하여 이미지의 픽셀을 처리 할 수있는 것 int[x][y]입니다. 내가 찾은 모든 메소드는 이것을 수행하지 않습니다 (대부분은 int[]s를 반환 합니다).


속도가 걱정된다면 getRGB, setRGB직접 사용하는 대신 전체 이미지를 배열에 복사하려는 이유는 무엇입니까?
Brad Mace

3
@bemace : 내 프로파일 링에 따르면 이러한 방법은 생각보다 더 많은 작업을 수행하는 것으로 보입니다. 어레이에 액세스하는 것이 훨씬 빨라 보입니다.
ryyst

15
@bemace : 그것은 사실입니다 정말 강렬 : 배열을 사용하여 800 % 빠른 사용하는 것보다 getRGBsetRGB직접.
ryyst

답변:


179

저는 픽셀에 접근하는 가장 빠른 방법 인이 동일한 주제를 가지고 놀았습니다. 현재이 작업을 수행하는 두 가지 방법을 알고 있습니다.

  1. getRGB()@tskuzzy의 답변에 설명 된대로 BufferedImage의 방법을 사용 합니다.
  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
코드를 읽기에 너무 게으른 사람들을 위해 두 가지 테스트 convertTo2DUsingGetRGBconvertTo2DWithoutUsingGetRGB. 첫 번째 테스트는 평균 16 초가 걸립니다. 두 번째 테스트는 평균 1.5 초가 걸립니다. 처음에는 "s"와 "ms"가 두 개의 다른 열이라고 생각했습니다. @Mota, 훌륭한 참조.
Jason

1
@Reddy 시도해 보았는데 파일 크기에 차이가 있습니다. 이유를 모르겠습니다! 그러나 다음 코드를 사용하여 정확한 픽셀 값을 재현 할 수있었습니다 (알파 채널 사용) : pastebin.com/zukCK2tu 처리중인 이미지에 따라 BufferedImage 생성자의 세 번째 인수를 수정해야 할 수도 있습니다. . 이것이 도움이되기를 바랍니다!
Motasim

4
@Mota In convertTo2DUsingGetRGB 왜 결과를 취합니까 [row] [col] = image.getRGB (col, row); 결과 대신 [row] [col] = image.getRGB (row, col);
Kailash 2014

6
색상 차이 및 / 또는 잘못된 바이트 순서를 인식하는 사람들 : @Mota의 코드는 BGR 순서를 가정합니다 . 들어오는 BufferedImagetype예를 확인 TYPE_INT_RGB하거나 TYPE_3BYTE_BGR적절하게 처리 해야합니다 . 이것은 것들 중 하나입니다 getRGB()느린 :-(을 만드는, 당신을 위해 수행
millhouse

2
내가 틀렸다면 정정하지만 방법 2의 값을 결합하는 |=대신 사용하는 것이 더 효율적이지 +=않습니까?
Ontonator 2015 년

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어쨌든 2D int 배열을 사용하여 픽셀을 저장 했을 까요?
ryyst

1
이미지가 내부적으로 1 차원 데이터 구조로 저장되어 있다고 확신합니다. 따라서 작업은 어떻게하더라도 O (W * H)가 걸립니다. 먼저 1 차원 배열에 저장하고 1 차원 배열을 2D 배열로 변환하여 메서드 호출 오버 헤드를 피할 수 있습니다.
tskuzzy 2011-06-29

4
당신은 배열의 모든 픽셀을 원하는 경우가수록 @ryyst이 약 효율적이다
숀 패트릭 플로이드

1
+1, 나는 이것이 Raster의 데이터 버퍼에 액세스한다고 생각하지 않습니다 . 이는 가속 펀팅을 초래하기 때문에 확실히 좋은 것입니다.
mre

2
@tskuzzy이 방법은 더 느립니다. Mota의 방법을 확인하십시오.이 방법은 기존 방법보다 빠릅니다.
h4ck3d

20

Mota의 답변으로 속도가 10 배 빨라졌습니다. Mota에게 감사드립니다.

생성자에서 BufferedImage를 가져오고 BufferedImage.getRGB (x, y)를 사용하여 코드를 대체하는 동등한 getRBG (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 ()를 만드는 것이 Color API의 getRGB ()보다 더 빠르고 / 더 좋고 / 더 최적 인 이유를 설명 할 수 있습니까? 평가하다 !
mk7

@ mk7이 답변을 살펴보십시오 . stackoverflow.com/a/12062932/363573 . 더 자세한 내용을 보려면 java를 입력 하십시오. 선호하는 검색 엔진에서 getrgb가 느린 이유 .
Stephan

10

Mota의 대답은 BufferedImage가 단색 비트 맵에서 온 것이 아니라면 훌륭합니다. 단색 비트 맵에는 픽셀에 대해 가능한 값이 2 개만 있습니다 (예 : 0 = 검정 및 1 = 흰색). 모노크롬 비트 맵을 사용하면

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

호출은 각 바이트에 둘 이상의 픽셀을 포함하는 방식으로 원시 픽셀 배열 데이터를 반환합니다.

따라서 모노크롬 비트 맵 이미지를 사용하여 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;
    }
}

이게 뭐야?

BufferedImage의 getRGB 메소드를 통해 이미지를 픽셀 단위로 읽는 것은 매우 느립니다.이 클래스가 이에 대한 해결책입니다.

아이디어는 BufferedImage 인스턴스를 공급하여 객체를 구성하고 모든 데이터를 한 번에 읽고 배열에 저장하는 것입니다. 픽셀을 얻으려면 getRGB를 호출합니다.

의존성

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

고려 사항

FastRGB를 사용하면 픽셀 읽기가 훨씬 빨라지지만 단순히 이미지 사본을 저장하기 때문에 메모리 사용량이 높아질 수 있습니다. 따라서 메모리에 4MB BufferedImage가있는 경우 FastRGB 인스턴스를 생성하면 메모리 사용량이 8MB가됩니다. 그러나 FastRGB를 생성 한 후 BufferedImage 인스턴스를 재활용 할 수 있습니다.

RAM이 병목 현상 인 Android 폰과 같은 장치에서 사용할 때 OutOfMemoryException에 빠지지 않도록주의하십시오.


-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.