Java-画像からピクセル配列を取得する


118

からピクセルデータ(フォームのint[][])を取得する最速の方法を探していBufferedImageます。私の目標は(x, y)、を使用して画像からピクセルをアドレス指定できるようにすることint[x][y]です。私が見つけたすべてのメソッドはこれを行いません(それらのほとんどはを返しますint[])。


速度が心配な場合はgetRGBsetRGB直接および直接使用するのではなく、なぜ画像全体を配列にコピーしたいのですか?
Brad Mace、2011年

3
@bemace:これらのメソッドは、私のプロファイリングによると、思ったよりも多くの作業を行うように見えるためです。配列へのアクセスははるかに高速に見えます。
ryyst '06 / 06/29

15
@bemace:実際には非常に激しいです。配列を使用するgetRGBと、setRGB直接使用するよりも800%以上速くなります。
ryyst '06 / 06/29

回答:


179

私はちょうど同じ主題で遊んでいました、それはピクセルにアクセスする最も速い方法です。私は現在、これを行う2つの方法を知っています。

  1. getRGB()@tskuzzyの回答に記載されているBufferedImageのメソッドを使用します。
  2. 以下を使用してピクセル配列に直接アクセスします。

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

大きな画像で作業していて、パフォーマンスが問題である場合、最初の方法は絶対にうまくいきません。このgetRGB()メソッドは、アルファ、赤、緑、青の値を1つのintに結合し、結果を返します。ほとんどの場合、これらの値を逆にしてこれらの値を取得します。

2番目のメソッドは、各ピクセルの赤、緑、青の値を直接返します。アルファチャネルがある場合は、アルファ値を追加します。この方法を使用すると、インデックスの計算は難しくなりますが、最初の方法よりはるかに高速です。

私のアプリケーションでは、最初のアプローチから2番目のアプローチに切り替えるだけで、ピクセルの処理時間を90%以上削減できました。

2つのアプローチを比較するために設定した比較を以下に示します。

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
コードを読むのが面倒な人のために、2つのテストconvertTo2DUsingGetRGBとがありconvertTo2DWithoutUsingGetRGBます。最初のテストは平均で16秒かかります。2番目のテストの平均所要時間は1.5秒です。最初、「s」と「ms」は2つの異なる列だと思いました。@Mota、素晴らしいリファレンス。
Jason

1
@Reddy試してみましたが、ファイルサイズに違いが見られますが、理由はわかりません!ただし、このコードを使用して正確なピクセル値を再現できました(アルファチャネルを使用)。pastebin.com / zukCK2tu処理する画像によっては、BufferedImageコンストラクタの3番目の引数を変更する必要がある場合があります。これが少し役立つことを願っています!
モタシム2013

4
@Mota convertTo2DUsingGetRGBでは、なぜ結果をとるのですか[row] [col] = image.getRGB(col、row); result [row] [col] = image.getRGB(row、col);の代わりに
Kailash、2014

6
色の違いや不正なバイトの順序に気づく人々:@MotaのコードはBGRの順序を前提としています。着信BufferedImagetypeeg を確認するTYPE_INT_RGBTYPE_3BYTE_BGR、適切に処理する必要があります。これはgetRGB()あなたのために
働く

2
私が間違っている場合は私を修正しますが、方法2で値を組み合わせる|=代わりに使用する方が効率的ではないでしょう+=か?
オントネーター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 '06 / 06/29

1
画像は1次元のデータ構造として内部的に保存されていると思います。したがって、操作はどのように実行してもO(W * H)になります。最初に1次元配列に格納し、1次元配列を2D配列に変換することで、メソッド呼び出しのオーバーヘッドを回避できます。
tskuzzy、2011年

4
@ryyst配列内のすべてのピクセルが必要な場合、これはほぼ同じくらい効率的です
Sean Patrick Floyd

1
+1、私はこれがRasterのデータバッファーにアクセスするとは思いません。これは、加速パントを引き起こすため、間違いなく良いことです。
mre

2
@tskuzzyこの方法は遅いです。この従来の方法よりも高速なMotaによる方法を確認してください。
h4ck3d

20

モタの答えは私に10倍の速度向上を与えてくれたので、モタに感謝します。

コンストラクターで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が遅い理由を入力してください。
ステファン

10

あなたの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 '20 / 07/20
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.