effizient mehrere BufferedImage in eines schreiben

mr.kernel

Mitglied
Hallo liebe Gemeinde 🙂

Ich verzweifle langsam dran:
Ein Bild soll möglich schnell, also Multithreaded und später auch mit SIMD optimiert gefiltert werden.
Dabei sollen mehrere BufferedImage in eines geschrieben werden, wobei jedes der Einzelbilder nur einen Teil des gesamten Bildes enthält.
Sprich Teil1 - Zeile 0 bis 5, Teil2 - Zeile 5 bis 10 ...
Beim effizienten zusammensetzen hapert es aber.

In einer Schleife habe ich nun:

Java:
        BufferedImage dest = new BufferedImage(img.getWidth(), img.getHeight(),
                BufferedImage.TYPE_3BYTE_BGR);
        int anzThreads = 4;

        for (int x = 0; x < anzThreads; x++) {
            BufferedImage tmp = new BufferedImage(img.getWidth(), img.getHeight(),
                    BufferedImage.TYPE_3BYTE_BGR);
            tmp = Threaded(x, anzThreads, img, tresholds);
        dest.getGraphics().drawImage(tmp, 0, startwert, tmp.getTileWidth(), endwert, null);
}

dabei wird aber im Gesamtbild nur

dargestellt.

Mit folgender Variante klappt es korrekt, ist aber unglaublich langsam:
Java:
             for (ya = startwert; ya < endwert; ya++) {
                    for (xa = 0; xa < img.getTileWidth(); xa++) {
                        dest.setRGB(xa, ya, tmp.getRGB(xa, ya));
                        }
                 }

Kann mir jemand helfen?
 
Ich verstehe den ersten Ansatz von Dir nicht. Du schreibst in mehreren Threads jeweils ein Bild. Dieses hat aber die komplette Größe ... um dann am Ende in die temporären Bilder in das Ziel zu übertragen? Dabei überträgst Du aber das ganze Bild, also alle Bereiche und nicht nur den zuständigen Bereich, oder habe ich da auf die Schnelle etwas übersehen?

Generell kannst Du auf ein BufferedImage mit mehreren Threads zugreifen. Wenn jeder Thread einen eigenen Bereich hat und die Bereiche sich nicht überlappen, solltest Du kein Problem haben.

Was genau machst Du denn in dem "Threaded"? Du willst da ja sofort ein Ergebnis haben zum weiter arbeiten. Also entweder Du hast da keinen Thread oder Du arbeitest mit etwas weiter, obwohl der Thread im Hintergrund noch etwas macht.

Und ganz nebenbei: In der Schleife erzeugst Du in tmp erst ein BufferedImage nur um es sofort wieder wegzuwerfen (Du überschreibst die Referenz mit der Rückgabe vom Threaded Aufruf.
 
Ich verstehe den ersten Ansatz von Dir nicht. Du schreibst in mehreren Threads jeweils ein Bild. Dieses hat aber die komplette Größe ... um dann am Ende in die temporären Bilder in das Ziel zu übertragen? Dabei überträgst Du aber das ganze Bild, also alle Bereiche und nicht nur den zuständigen Bereich, oder habe ich da auf die Schnelle etwas übersehen?
Aus den temporären Bildern soll nur der zuständige Bereich übertragen werden.
Mit der zweiten Variante funktioniert auch das zusammenführen, allerdings Pixel für Pixel.

Was genau machst Du denn in dem "Threaded"? Du willst da ja sofort ein Ergebnis haben zum weiter arbeiten. Also entweder Du hast da keinen Thread oder Du arbeitest mit etwas weiter, obwohl der Thread im Hintergrund noch etwas macht.

In Threaded wird der Filter auf den zuständige Bereich angewendet.
Auf das Ergebnis der Threads wird mit join gewartet und dann erst das Bild zusammengesetzt.

Und ganz nebenbei: In der Schleife erzeugst Du in tmp erst ein BufferedImage nur um es sofort wieder wegzuwerfen (Du überschreibst die Referenz mit der Rückgabe vom Threaded Aufruf.
Ich dachte ich Initialisiere erstmal ein leeres BufferedImage um es dann zu beschreiben.
Habs nun angepasst.
 
In Threaded wird der Filter auf den zuständige Bereich angewendet.
Auf das Ergebnis der Threads wird mit join gewartet und dann erst das Bild zusammengesetzt.
Das macht aber kein Sinn. Du startest einen Thread und wartest dann darauf, dass er sich beendet. Dann kannst Du das, was der Thread macht, auch direkt erledigen und sparst Dir den Aufwand.

Was Du machen müsstest, wäre die Threads alle zu starten und wenn alle Threads laufen, dann kannst Du auf alle warten und du hast die Parallelität, die Du willst.

Was Du aber z.B. machen kannst, ist einfach direkt mit dem BufferedImage in allen Threads zu arbeiten. Du hast also etwas wie:
Java:
BufferedImage target = ...; // Das Ziel-BufferedImage
int lineCount = ...; // Anzahl der Zeilen, die ein Thread bearbeiten soll
int threadCount = 4; // Anzahl der Threads

Thread[] threads = new Thread[threadCount];

for (int i = 0; i < threadCount; i++) {
    final int threadIndex = i;
    threads[i] = new Thread(() -> {
        int startLine = threadIndex * lineCount;
        int endLine = Math.min((threadIndex + 1) * lineCount - 1, target.getHeight());
        writeToBufferedImage(target, startLine, endLine);
    });
    threads[i].start();
}

for (int i = 0; i < threadCount; i++) {
    try {
        threads[i].join(); // Warten, bis alle Threads beendet sind
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // Unterbrechung setzen, falls der Thread unterbrochen wurde
        e.printStackTrace(); // Optional: Stack-Trace ausgeben
    }
}

Das aber nur als kurze Skizzierung - ist halt im Forum entstanden und ist ungetestet und evtl. habe ich auf die Schnelle etwas übersehen ...

Da definierte Bereiche verwendet werden, kannst Du in writeToBufferedImage alles machen. Also auch gerne ein Graphics Objekt erzeugen um damit in das Bild zu malen. Wenn der Index wichtig ist, dann kannst Du den auch mitgeben. damit das richtige Bild gemalt wird.
 
Ha, zufaellig hatte ich mich erst mit BufferedImage beschaeftigt.

Ein Bild soll möglich schnell, also Multithreaded und später auch mit SIMD optimiert gefiltert werden.
Bist du dann mit BufferedImage nicht ohnehin falsch? Weil ich glaube nicht dass man auf BufferedImage so ein paar CPU-Instruktionen anwenden kann.

Dabei sollen mehrere BufferedImage in eines geschrieben werden, wobei jedes der Einzelbilder nur einen Teil des gesamten Bildes enthält.
Sprich Teil1 - Zeile 0 bis 5, Teil2 - Zeile 5 bis 10 ...
Das waere dann das Beispiel fuer BufferedImage.getRGB und setRGB. Du ziehst die Pixel als int[] und setzt diese wieder auf dem finalen Bild. Einfacher wird es nicht mehr wirklich.

Java:
int[] partRgb = partImage.getRGB(
        0,
        0,
        partWidth,
        partHeight,
        null,
        0,
        partWidth;

finalImage.setRGB(
        offsetXOfPartImage,
        offsetYOfPartImage,
        partWidth,
        partHeight,
        partRgb,
        0,
        partWidth);

Wenn du das "parallelisieren" willst, wuerde das vermutlich so aussehen:

Java:
BufferedImage finalImage = createFinalImage();

for (int threadCounter = 0; threadCounter < 4; threadCounter++) {
    new Thread(() -> {
       BufferedImage partImage = getPartImageFromSomewhere();
    
       int[] partRgb = partImage.getRGB(
               0,
               0,
               partWidth,
               partHeight,
               null,
               0,
               partWidth;
  
       finalImage.setRGB(
               offsetXOfPartImage,
               offsetYOfPartImage,
               partWidth,
               partHeight,
               partRgb,
               0,
               partWidth);
      }).start();
}

Aber das so zu machen ist garantiert langsamer als wenn du es direkt machst (es sei denn du hast Bilder mit einer Groesze jenseits von Gut und Boese). Einen Thread zu starten ist teuer (mal abgesehen davon dass du nicht weiszt wann der endlich startet), sowas macht erst Sinn wenn du den Thread behalten kannst und dem immer wieder Arbeit zuschiebst, meiner Meinung nach. Oder wenn das holen der Einzelbilder irgendwie pro Aufruf teuer ist.

Falls du hier versuchst eine Videoverarbeitung zu bauen, waerst du vielleicht besser dran kein BufferedImage im ersten Schritt zu verwenden, sondern die "rohen" Daten selbst irgendwie zusammen zu stoeppeln (so fern moeglich, weil Bitmaps oder so).
 
Zuletzt bearbeitet:
Dank dem Hinweis von @KonradN habe ich nun den Fehler gefunden, durch das überschreiben des neuen Bildes mit allen Pixeln aus dem Teilbildern (auch der nicht zuständigen Bereiche mit getGraphics().drawImage wurde mir das Bild immer unvollständig angezeigt. 😎

Ha, zufaellig hatte ich mich erst mit BufferedImage beschaeftigt.


Bist du dann mit BufferedImage nicht ohnehin falsch? Weil ich glaube nicht dass man auf BufferedImage so ein paar CPU-Instruktionen anwenden kann.


Das waere dann das Beispiel fuer BufferedImage.getRGB und setRGB. Du ziehst die Pixel als int[] und setzt diese wieder auf dem finalen Bild. Einfacher wird es nicht mehr wirklich.

Java:
int[] partRgb = partImage.getRGB(
        0,
        0,
        partWidth,
        partHeight,
        null,
        partWidth;

finalImage.setRGB(
        offsetXOfPartImage,
        offsetYOfPartImage,
        partWidth,
        partHeight,
        partRgb,
        partWidth);

Wenn du das "parallelisieren" willst, wuerde das vermutlich so aussehen:

Java:
BufferedImage finalImage = createFinalImage();

for (int threadCounter = 0; threadCounter < 4; threadCounter++) {
    new Thread(() -> {
       BufferedImage partImage = getPartImageFromSomewhere();
     
       int[] partRgb = partImage.getRGB(
               0,
               0,
               partWidth,
               partHeight,
               null,
               partWidth;
   
       finalImage.setRGB(
               offsetXOfPartImage,
               offsetYOfPartImage,
               partWidth,
               partHeight,
               partRgb,
               partWidth);
      }).start();
}

Aber das so zu machen ist garantiert langsamer als wenn du es direkt machst (es sei denn du hast Bilder mit einer Groesze jenseits von Gut und Boese). Einen Thread zu starten ist teuer (mal abgesehen davon dass du nicht weiszt wann der endlich startet), sowas macht erst Sinn wenn du den Thread behalten kannst und dem immer wieder Arbeit zuschiebst, meiner Meinung nach.

Falls du hier versuchst eine Videoverarbeitung zu bauen, waerst du vielleicht besser dran kein BufferedImage im ersten Schritt zu verwenden, sondern die "rohen" Daten selbst irgendwie zusammen zu stoeppeln (so fern moeglich, weil Bitmaps oder so).

Das SIMD möchte ich auf den eigentlichen Filter(ungsprozess) anwenden, nicht auf das BufferedImage direkt.

Ich habe eine simple Bildbearbeitung und wollte diese jetzt parallelisieren und halt mit SIMD Befehlen nutzen.
 
Das macht aber kein Sinn. Du startest einen Thread und wartest dann darauf, dass er sich beendet. Dann kannst Du das, was der Thread macht, auch direkt erledigen und sparst Dir den Aufwand.

Was Du machen müsstest, wäre die Threads alle zu starten und wenn alle Threads laufen, dann kannst Du auf alle warten und du hast die Parallelität, die Du willst.

Was Du aber z.B. machen kannst, ist einfach direkt mit dem BufferedImage in allen Threads zu arbeiten. Du hast also etwas wie:
Java:
BufferedImage target = ...; // Das Ziel-BufferedImage
int lineCount = ...; // Anzahl der Zeilen, die ein Thread bearbeiten soll
int threadCount = 4; // Anzahl der Threads

Thread[] threads = new Thread[threadCount];

for (int i = 0; i < threadCount; i++) {
    final int threadIndex = i;
    threads[i] = new Thread(() -> {
        int startLine = threadIndex * lineCount;
        int endLine = Math.min((threadIndex + 1) * lineCount - 1, target.getHeight());
        writeToBufferedImage(target, startLine, endLine);
    });
    threads[i].start();
}

for (int i = 0; i < threadCount; i++) {
    try {
        threads[i].join(); // Warten, bis alle Threads beendet sind
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // Unterbrechung setzen, falls der Thread unterbrochen wurde
        e.printStackTrace(); // Optional: Stack-Trace ausgeben
    }
}

Das aber nur als kurze Skizzierung - ist halt im Forum entstanden und ist ungetestet und evtl. habe ich auf die Schnelle etwas übersehen ...

Da definierte Bereiche verwendet werden, kannst Du in writeToBufferedImage alles machen. Also auch gerne ein Graphics Objekt erzeugen um damit in das Bild zu malen. Wenn der Index wichtig ist, dann kannst Du den auch mitgeben. damit das richtige Bild gemalt wird.

Nein du hast nix übersehen und funktioniert besser als mein erster Versuch.
Ich bin fälschlicherweise davon ausgegangen dass das join erst nach dem letzten Thread wartet...

Mein erster Versuch war in etwa so schnell wie die Variante komplett ohne Threads.
Nun mit deinem Vorschlag und sinnvollen Threads etwa ein drittel schneller. 👍
 

Neue Themen


Zurück
Oben