2014-11-07 5 views
7

Man könnte vorschlagen, dass BufferedImage die beste Option ist, um ein Bild in Java zu verarbeiten. Während es bequem ist, wenn große Bilder zu lesen endet es oft bis in:Wie liest man ein Bild aus dem Stream?

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 

Erhöhung VM Größe ist keine Lösung in meinem Fall, da einige Eingabedateien wirklich riesig sind.

Also ich bin auf der Suche nach der Art und Weise, wie ein Bild progressiv aus einem Stream gelesen werden kann.

Ich vermute, dass ImageIO.createImageInputStream() von der ImageIO könnte die Rechnung passen, aber ich bin mir nicht sicher, wie man es verwendet, um die chunks progressiv zu lesen. Es gibt auch die Klassen PNGMetadata & PNGImageReader, die auf den JDKs verfügbar sind, die nützlich scheinen, aber ich fand keine einfachen Beispiele ihrer Verwendung.

Ist das der richtige Weg, oder gibt es bessere Alternativen?

+0

Bitte hinterlassen Sie einen Kommentar, wenn Sie eine Abstimmung ablehnen oder eine Stimme zum Schließen abgeben. – blackSmith

+2

Keine Notwendigkeit, der Tooltip für den Downvote-Pfeil sagt Ihnen bereits alles, was Sie wissen müssen – Clive

+0

@Clive: Danke für den Tipp. Wenn ich jedoch wissen könnte, wie man mit java auf den Header zugreifen kann, würde ich es nicht veröffentlichen. Zur besseren Übersicht kann ich die Frage bearbeiten. Schließlich kann ich nicht akzeptieren, dass es "nicht sinnvoll" ist, da SO zu viele 'java.lang.OutOfMemoryError: Java-Heap-Space 'hat, wenn' BufferedImage' Art von Fragen verwendet wird. – blackSmith

Antwort

11

Die Java-APIs zum Lesen und Bearbeiten von Bildern sind nicht wirklich stream-basiert, wie Sie denken. Die ImageInputStream ist nur ein Convenience Wrapper, der das Lesen von byte s und anderen primitiven Typen von verschiedenen Eingängen erlaubt (RandomAccessFile s, InputStream s usw.).

Ich habe über das Erstellen einer API zum Lesen von "Pixelströmen" nachgedacht, um die Verkettung von Verarbeitungsfiltern ohne viel Speicher zu ermöglichen. Aber die Notwendigkeit war nie ernst genug, um die Mühe wert zu sein. Fühlen Sie sich frei, mich anzustellen, wenn Sie weitere Ideen hören möchten oder eine funktionierende Implementierung haben.;-)

Doch wie ich es sehe, Sie mehrere Möglichkeiten haben Ihr Ziel zu erreichen, große Bilder verarbeiten zu können:

  1. Verwendung BufferedImage wie es ist, und die ImageIO API zu lesen ein Bild in kleineren Teilen, um Speicher zu sparen. Dies wird für einige Formate ziemlich effizient sein, wegen der Implementierung weniger effizient für andere Formate (d. H. Der Standard JPEGImageReader liest das gesamte Bild im nativen Speicher, bevor kleinere Bereiche an den Java-Heap übergeben werden, aber PNGImageReader könnte in Ordnung sein).

    Etwas entlang der Linien von:

    ImageInputStream stream = ImageIO.createImageInputStream(input); 
    ImageReader reader = ImageIO.getImageReaders(stream).next(); // TODO: Test hasNext() 
    reader.setInput(stream); 
    
    int width = reader.getWidth(0); 
    int height = reader.getHeight(0); 
    
    ImageReadParam param = reader.getDefaultReadParam(); 
    
    for (int y = 0; y < height; y += 100) { 
        for (int x = 0; x < width; x += 100) { 
          param.setSourceRegion(new Rectangle(x, y, 100, 100)); // TODO: Bounds check 
    
          // Read a 100 x 100 tile from the image 
          BufferedImage region = reader.read(0, param); 
    
          // ...process region as needed... 
        } 
    } 
    
  2. das gesamte Bild auf einmal lesen, in einen Speicher abgebildet Puffer. Fühlen Sie sich frei, einige experimental classes zu versuchen, die ich für diesen Zweck gemacht habe (unter Verwendung nio). Das Lesen wird langsamer als das Lesen eines reinen Speicherabbilds und die Verarbeitung wird ebenfalls langsamer. Wenn Sie jedoch gleichzeitig Berechnungen an kleineren Bildbereichen durchführen, kann es bei einigen Optimierungen etwa so schnell wie im Arbeitsspeicher sein. Ich habe> 1 GB Bilder in einer 32 MB JVM mit diesen Klassen gelesen (der reale Speicherverbrauch ist natürlich viel größer).

    Auch hier ein Beispiel:

    ImageInputStream stream = ImageIO.createImageInputStream(input); 
    ImageReader reader = ImageIO.getImageReaders(stream).next(); // TODO: Test hasNext() 
    reader.setInput(stream); 
    
    int width = reader.getWidth(0); 
    int height = reader.getHeight(0); 
    ImageTypeSpecifier spec = reader.getImageTypes(0).next(); // TODO: Test hasNext(); 
    
    BufferedImage image = MappedImageFactory.createCompatibleMappedImage(width, height, spec) 
    
    ImageReadParam param = reader.getDefaultReadParam(); 
    param.setDestination(image); 
    
    image = reader.read(0, param); // Will return same image as created above 
    
    // ...process image as needed... 
    
  3. Einige Formate wie unkomprimiertes TIFF, BMP, PPM usw., hält die Pixel in der Datei in einer Weise, dass es möglich wäre, den Speicher-Karte direkt um sie zu manipulieren. Erfordert etwas Arbeit, sollte aber möglich sein. TIFF unterstützt auch Kacheln, die hilfreich sein könnten. Ich werde diese Option als eine Übung verlassen, fühlen Sie sich frei, die Klassen, die ich oben verlinkt habe, als Ausgangspunkt oder Inspiration zu verwenden. ;-)

  4. JAI kann etwas haben, das dir helfen kann. Ich bin kein großer Fan, aufgrund der vielen ungelösten Bugs und des Mangels an Liebe und Entwicklung von Oracle. Aber es lohnt sich, es auszuprobieren. I denken sie haben Unterstützung für tileable und Festplatte-basierte RenderedImage s ebenso. Auch hier werde ich diese Option für Sie weiter erkunden.

+0

Scheinbar überschaubar, ich war eigentlich nirgends vor den beiden Antworten. Mal sehen, wie weit ich mit meiner '(inspi | explor) Ration gehen kann. Wird definitiv Unterstützung von euch suchen. Übrigens +1 für das Mietangebot, obwohl ich nur ein – blackSmith

4

Das Speicherproblem hängt sicherlich nicht mit dem Decodierprozess selbst zusammen, sondern mit dem Speichern des gesamten Bildes im Speicher als BufferedImage. Es ist möglich, schrittweise ein PNG-Bild zu lesen, aber:

  • Dies ist nur geringfügig in „Brocken“ auf die Organisation bezogen, um so mehr mit der Tatsache, dass PNG-Dateien Zeile-für-Zeile kodiert werden, und so Sie können im Prinzip Zeile für Zeile gelesen werden.

  • Die obige Annahme bricht im Fall von interlaced PNGs - aber man sollte nicht erwarten, dass große PNG-Bilder im Interlaced-Format

  • einige PNG-Bibliotheken für progressive (line-by-line) Dekodierung ermöglichen gespeichert haben, während (zB: libpng), die Standard Java API gibt Ihnen das nicht.

Ich war mit diesem Problem konfrontiert, und ich beenden meine eigene Java-Bibliothek bis Codierung: PNGJ. Es ist ziemlich ausgereift, es erlaubt PNG-Bilder Zeile für Zeile zu lesen, Speicherverbrauch zu minimieren und sie auf die gleiche Weise zu schreiben (Sogar verschachtelte PNGs können gelesen werden, aber in diesem Fall wird das Speicherproblem nicht verschwinden.) Wenn Sie nur brauchen Um eine "lokale" Bildverarbeitung durchzuführen (jeden Pixelwert abhängig vom aktuellen Wert und den Nachbarn zu modifizieren und zurückzuschreiben), sollte dies helfen.

Verwandte Themen