Вопрос-ответ

Java - get pixel array from image

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

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

Переведено автоматически
Ответ 1

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


  1. Используя метод BufferedImage getRGB(), как описано в ответе @tskuzzy.

  2. Путем прямого доступа к массиву пикселей с помощью:


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

Если вы работаете с большими изображениями и у вас проблемы с производительностью, первый метод абсолютно не подходит. Метод getRGB() объединяет значения alpha, red, green и blue в один 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)
Ответ 2

Что-то вроде этого?

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 );
Ответ 3

Я обнаружил, что ответ Mota увеличил скорость в 10 раз - так что спасибо Mota.

Я упаковал код в удобный класс, который принимает 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;
}
}
Ответ 4

Ответ 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;
}
2024-02-03 21:35 java