Java - получить массив пикселей из изображения

118

Я ищу самый быстрый способ получить данные о пикселях (в форме int[][]) из файла BufferedImage. Моя цель - иметь возможность обращаться к пикселям (x, y)изображения, используя int[x][y]. Все методы, которые я нашел, этого не делают (большинство из них возвращают int[]s).

ryyst
источник
Если вас беспокоит скорость, почему вы хотите скопировать все изображение в массив, а не просто использовать getRGBи setRGBнапрямую?
Брэд Мейс,
3
@bemace: Потому что, судя по моему профилю, эти методы работают больше, чем можно подумать. Доступ к массиву кажется намного быстрее.
ryyst
15
@bemace: Это действительно очень интенсивно: использование массива более чем на 800% быстрее, чем использование getRGBи setRGBнапрямую.
ryyst

Ответы:

179

Я просто играл с тем же предметом, это самый быстрый способ получить доступ к пикселям. В настоящее время я знаю два способа сделать это:

  1. Использование getRGB()метода BufferedImage, как описано в ответе @ tskuzzy.
  2. Получая доступ к массиву пикселей напрямую, используя:

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

Если вы работаете с большими изображениями и производительность является проблемой, первый метод - совершенно не лучший вариант. Этот getRGB()метод объединяет значения альфа, красного, зеленого и синего в одно целое, а затем возвращает результат, который в большинстве случаев вы делаете наоборот, чтобы вернуть эти значения.

Второй метод будет возвращать значения красного, зеленого и синего цветов непосредственно для каждого пикселя, и если есть альфа-канал, он добавит альфа-значение. Использовать этот метод сложнее с точки зрения расчета индексов, но он намного быстрее, чем первый подход.

В моем приложении я смог сократить время обработки пикселей более чем на 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)
Motasim
источник
10
Для тех, кому лень читать код, есть два теста convertTo2DUsingGetRGBи convertTo2DWithoutUsingGetRGB. Первый тест в среднем занимает 16 секунд. Второй тест в среднем занимает 1,5 секунды. Сначала я подумал, что «s» и «ms» - это два разных столбца. @Mota, отличная ссылка.
Джейсон
1
@Reddy Я попробовал, и я вижу разницу в размере файла, и я не уверен, почему! Однако мне удалось воспроизвести точные значения пикселей с помощью этого кода (с использованием альфа-канала): pastebin.com/zukCK2tu. Возможно, вам потребуется изменить третий аргумент конструктора BufferedImage, в зависимости от изображения, с которым вы имеете дело. , Надеюсь, что это помогает немного!
Motasim 01
4
@Mota В convertTo2DUsingGetRGB почему вы берете результат [row] [col] = image.getRGB (col, row); вместо результата [row] [col] = image.getRGB (row, col);
Кайлаш
6
Люди, замечающие разницу в цвете и / или неправильный порядок байтов: код @ Mota предполагает порядок BGR . Вы должны проверить входящие BufferedImage, 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 );
tskuzzy
источник
11
Разве это не невероятно неэффективно? Я бы все BufferedImageравно сохранил пиксели, используя массив 2D int?
ryyst
1
Я почти уверен, что изображение хранится внутри как одномерная структура данных. Таким образом, операция займет O (W * H), как бы вы ее ни делали. Вы можете избежать накладных расходов на вызов метода, предварительно сохранив его в одномерном массиве и преобразовав одномерный массив в 2D-массив.
tskuzzy
4
@ryyst, если вам нужны все пиксели в массиве, это настолько эффективно, насколько это возможно,
Шон Патрик Флойд
1
+1, я не думаю, что это обращается к Rasterбуферу данных, что определенно хорошо, поскольку это приводит к ускорению.
mre
2
@tskuzzy Этот метод более медленный. Проверьте метод Mota, он быстрее, чем этот традиционный метод.
h4ck3d 09
20

Я обнаружил, что ответ Моты дал мне 10-кратное увеличение скорости - так что спасибо Моте.

Я заключил код в удобный класс, который принимает 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 работает медленно в вашей любимой поисковой системе.
Стефан
10

Ответ Mota великолепен, если ваш BufferedImage не был получен из монохромного растрового изображения. Монохромное растровое изображение имеет только 2 возможных значения пикселей (например, 0 = черный и 1 = белый). Когда используется монохромное растровое изображение,

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

call возвращает необработанные данные Pixel Array таким образом, что каждый байт содержит более одного пикселя.

Поэтому, когда вы используете монохромное растровое изображение для создания объекта 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;
}
CatGuardian
источник
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 значительно ускоряет чтение пикселей, это может привести к высокому использованию памяти, так как просто сохраняет копию изображения. Итак, если у вас есть 4 МБ BufferedImage в памяти, после создания экземпляра FastRGB использование памяти станет 8 МБ. Однако вы можете повторно использовать экземпляр BufferedImage после создания FastRGB.

Будьте осторожны, чтобы не попасть в OutOfMemoryException при использовании на таких устройствах, как телефоны Android, где оперативная память является узким местом.

Stephan
источник
-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?
Николас
это итератор для данных
Cjen1