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


118

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


Якщо ви переживаєте за швидкість, чому ви хочете скопіювати все зображення в масив, а не просто getRGBта setRGBбезпосередньо?
Бред Мейс

3
@bemace: Оскільки ці методи, як видається, виконують більше роботи, ніж можна було б подумати, згідно мого профілювання. Доступ до масиву здається набагато швидшим.
ryyst

15
@bemace: Насправді це дуже інтенсивно: використання масиву швидше на 800% швидше, ніж безпосередньо getRGBта setRGBбезпосередньо.
рист

Відповіді:


179

Я просто грав із цим самим предметом, який є найшвидшим способом доступу до пікселів. В даний час я знаю два способи зробити це:

  1. Використання BufferedImage getRGB() методу як описано у відповіді @ 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)

10
Для тих, хто лінивий читати код, є два тести convertTo2DUsingGetRGBі convertTo2DWithoutUsingGetRGB. Перший тест в середньому займає 16 секунд. Другий тест в середньому займає 1,5 секунди. Спочатку я подумав, що "s" і "ms" - це дві різні колонки. @Mota, чудова довідка.
Джейсон

1
@Reddy Я спробував це, і я бачу різницю у розмірі файлу, і я не знаю чому! Однак мені вдалося відтворити точні значення пікселів за допомогою цього коду (за допомогою альфа-каналу): pastebin.com/zukCK2tu Можливо, вам буде потрібно змінити третій аргумент конструктора BufferedImage, залежно від зображення, з яким ви маєте справу. . Сподіваюсь, це трохи допомагає!
Мотасім

4
@Mota In convertTo2DUsingGetRGB чому ви отримуєте результат [рядок] [col] = image.getRGB (col, рядок); замість результату [рядок] [col] = image.getRGB (рядок, стовпчик);
Кайлаш

6
Люди, які помічають різницю кольорів та / або неправильне впорядкування байтів: @ код Mota передбачає впорядкування BGR . Ви повинні перевірити вхідні дані BufferedImage, typeнаприклад, TYPE_INT_RGBабо TYPE_3BYTE_BGRобробляти їх належним чином. Це одна з речей, яка getRGB()робить для вас те, що робить це повільніше :-(
млинний будинок

2
Виправте мене, якщо я помиляюся, але чи не було б ефективнішим використовувати |=замість +=поєднання значень у методі 2?
Онтонатор

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?
рист

1
Я майже впевнений, що зображення зберігається внутрішньо як одновимірна структура даних. Тож операція займе O (W * H) незалежно від того, як ви це зробите. Ви можете уникнути накладних викликів методу, зберігаючи його спочатку в одновимірний масив і отримуючи перетворення одновимірного масиву в 2D-масив.
tskuzzy

4
@ryyst, якщо ви хочете, щоб усі пікселі в масиві, це приблизно настільки ефективно, наскільки це виходить
Шон Патрік Флойд

1
+1, я не думаю, що це звертається до Rasterбуфера даних, що, безумовно, є хорошою справою, оскільки це призводить до прискореного сканування.
мереж

2
@tskuzzy Цей метод повільніший. Перевірте метод Мотою, що швидше, ніж цей звичайний метод.
h4ck3d

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 кольорового 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 таким чином, що кожен байт містить більше одного пікселя.

Отже, коли ви використовуєте монохромне зображення растрового зображення для створення об'єкта 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 4 Мб, коли ви створюєте екземпляр FastRGB, використання пам'яті стане 8 МБ. Однак ви можете переробити екземпляр BufferedImage після створення FastRGB.

Будьте обережні, щоб не потрапити в OutOfMemoryException, використовуючи його на таких пристроях, як телефони Android, де оперативна пам'ять є вузьким місцем


-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.