जावा - छवि से पिक्सेल सरणी प्राप्त करें


118

मैं पिक्सेल डेटा प्राप्त करने का सबसे तेज़ तरीका ढूंढ रहा हूं (int रूप int[][]) a BufferedImage। मेरा लक्ष्य (x, y)छवि का उपयोग करके पिक्सेल को संबोधित करने में सक्षम होना है int[x][y]। मैंने जो भी विधियां पाई हैं, वे ऐसा नहीं करते हैं (उनमें से अधिकांश int[]एस वापस आते हैं )।


यदि आप गति के बारे में चिंतित हैं, तो आप पूरी छवि को केवल उपयोग करने getRGBऔर setRGBसीधे के बजाय एक सरणी में कॉपी करना चाहते हैं?
ब्रैड मेस

3
@bemace: क्योंकि वे तरीके मेरे प्रोफाइलिंग के अनुसार एक से अधिक काम करने के लिए प्रतीत होते हैं। किसी सरणी तक पहुँचना तेज़ तरीका लगता है।
रयिस्ट

15
@ बेमेस: यह वास्तव में वास्तव में तीव्र है: एक सरणी का उपयोग 800% से अधिक तेजी से getRGBऔर setRGBसीधे उपयोग करने से होता है।
रयिस्ट

जवाबों:


179

मैं बस इसी विषय के साथ खेल रहा था, जो पिक्सल तक पहुंचने का सबसे तेज़ तरीका है। मुझे वर्तमान में ऐसा करने के दो तरीके पता हैं:

  1. 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" दो अलग-अलग कॉलम हैं। @ छोटा, महान संदर्भ।
जेसन

1
@ रेड्डी मैंने इसे एक कोशिश दी, और मुझे फ़ाइल आकार में अंतर दिखाई देता है, जो मुझे यकीन नहीं है कि क्यों! हालाँकि, मैं इस कोड (अल्फा चैनल का उपयोग करके) सटीक पिक्सेल मानों को पुन: पेश करने में सक्षम रहा हूं: pastebin.com/zukCK2tu आपको बफ़रडाइमैज कंस्ट्रक्टर के तीसरे तर्क को संशोधित करने की आवश्यकता हो सकती है, यह उस छवि पर निर्भर करता है, जिस पर आप काम कर रहे हैं। । उम्मीद है इससे कुछ मदद मिली होगी!
मोटसिम

4
@Mota ConvertTo2DUsingGetRGB में आप परिणाम क्यों लेते हैं [पंक्ति] [col] = image.getRGB (कॉल, पंक्ति); परिणाम के बजाय [पंक्ति] [कॉल] = image.getRGB (पंक्ति, कॉल);
कैलाश

6
एक रंग अंतर और / या गलत बाइट ऑर्डर को नोटिस करने वाले लोग: @ मोटा कोड एक बीजीआर ऑर्डरिंग मानता है । आपको जांचना चाहिए भेजे 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 सरणी का उपयोग कर पिक्सल की दुकान होगा?
ryyst

1
मुझे पूरा यकीन है कि छवि आंतरिक रूप से एकल-आयामी डेटा संरचना के रूप में संग्रहीत है। तो ऑपरेशन ओ (डब्ल्यू * एच) ले जाएगा चाहे आप इसे कैसे भी करें। आप पहले एकल आयामी सरणी में स्टोर करके विधि कॉल ओवरहेड से बच सकते हैं और एकल आयामी सरणी को 2 डी-सरणी में परिवर्तित कर सकते हैं।
tskuzzy

4
@ फ्रीस्टाइल यदि आप किसी सरणी में सभी पिक्सेल चाहते हैं, तो यह लगभग उतना ही कुशल है जितना इसे प्राप्त होता है
सीन पैट्रिक फ्लोयड

1
+1, मुझे नहीं लगता कि यह Rasterडेटा बफ़र तक पहुँचता है , जो निश्चित रूप से एक अच्छी बात है क्योंकि इसके परिणामस्वरूप त्वरण पंटिंग होता है।
MRE

2
@tskuzzy यह विधि धीमी है। Mota द्वारा विधि की जाँच करें, जो इस पारंपरिक विधि की तुलना में तेज़ है।
h4ck3d

20

मैंने पाया कि मोटा के जवाब ने मुझे 10 गुना गति में वृद्धि दी - इसलिए धन्यवाद मोटा।

मैंने एक सुविधाजनक वर्ग में कोड को लपेटा है जो बफ़रमैडम को कंस्ट्रक्टर में ले जाता है और एक समतुल्य 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;
    }
}

मैं जावा में छवि फ़ाइलों को संसाधित करने के लिए नया हूं। क्या आप बता सकते हैं कि गेटआरजीबी () इस तरह से बनाने से कलर एपीआई के गेटआरजीबी () से तेज / बेहतर / अधिक इष्टतम है? सराहना !
19

@ mk7 कृपया इस उत्तर पर एक नज़र डालें । stackoverflow.com/a/12062932/363573 अधिक जानकारी के लिए java क्यों getrgb आपके पसंदीदा खोज इंजन में धीमा है।
स्टेपहान

10

जब तक आपका बफ़रेडमैज एक मोनोक्रोम बिटमैप से नहीं आया, तब तक मोता का जवाब बहुत अच्छा है। एक मोनोक्रोम बिटमैप के पिक्सल के लिए केवल 2 संभावित मान हैं (उदाहरण के लिए 0 = काला और 1 = सफेद)। जब एक मोनोक्रोम बिटमैप का उपयोग किया जाता है तब

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

कॉल रिटेल पिक्सेल एरे डेटा को इस तरह से लौटाता है कि प्रत्येक बाइट में एक से अधिक पिक्सेल होते हैं।

इसलिए जब आप अपने बफ़रडैमेज ऑब्जेक्ट को बनाने के लिए मोनोक्रोम बिटमैप छवि का उपयोग करते हैं तो यह वह एल्गोरिथम है जिसका आप उपयोग करना चाहते हैं:

/**
 * 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 विधि के माध्यम से पिक्सेल द्वारा एक छवि पिक्सेल पढ़ना काफी धीमा है, यह वर्ग इसके लिए समाधान है।

विचार यह है कि आप ऑब्जेक्ट को बफ़रेडइमेज इंस्टेंस को फीड करके बनाते हैं, और यह एक ही बार में सभी डेटा पढ़ता है और उन्हें एक सरणी में संग्रहीत करता है। एक बार जब आप पिक्सेल प्राप्त करना चाहते हैं, तो आप getRGB को कॉल करते हैं

निर्भरता

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

विचार

हालाँकि FastRGB पठन पिक्सेल को बहुत तेज़ बनाता है, यह उच्च मेमोरी उपयोग को जन्म दे सकता है, क्योंकि यह बस छवि की एक प्रति संग्रहीत करता है। इसलिए यदि आपके पास मेमोरी में 4MB बफर्डइमेज है, तो एक बार FastRGB इंस्टेंस बनाने के बाद, मेमोरी उपयोग 8MB हो जाएगा। हालाँकि, FastRGB बनाने के बाद आप BufferedImage इंस्टेंस को रीसायकल कर सकते हैं।

एंड्रॉइड फोन, जहां रैम एक अड़चन है जैसे उपकरणों पर इसका उपयोग करते समय OutOfMemoryException में नहीं पड़ने के लिए सावधान रहें


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.