Bilder gleichzeitig bearbeiten (Multiprocessing)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Gabelmensch
User
Beiträge: 79
Registriert: Montag 12. Oktober 2009, 11:50

Hallo,

ich habe einen Haufen Bilder in einer Liste "liste", diese moechte ich so bearbeiten:

Code: Alles auswählen

def bilderskalieren(datei):

	name = os.path.basename(datei)
	ausgabe = os.path.join(ziel, name)
	im = Image.open(datei)
	im.thumbnail(abmessungen, Image.ANTIALIAS)
	im.save(ausgabe + '.py.jpg', 'JPEG')

for i in liste:

	bilderskalieren(i)
Nun moechte ich immer vier Bilder gleichzeitig verarbeiten, jedoch steige ich bei der Doku von "threading", "multiprocessing"... nicht wirklich durch. Mir fehlt ein Einstieg wo ich suchen sollte. Hat einer soetwas mal gemacht?
Gabelmensch
User
Beiträge: 79
Registriert: Montag 12. Oktober 2009, 11:50

Das lese ich bereits seit einer Stunde durch.
Gabelmensch
User
Beiträge: 79
Registriert: Montag 12. Oktober 2009, 11:50

Ok, so geht es schoneinmal:

Code: Alles auswählen

from threading import Thread

*listebauen*

def bilderskalieren(datei):

	name = os.path.basename(datei)
	ausgabe = os.path.join(ziel, name)
	im = Image.open(datei)
	im.thumbnail(abmessungen, Image.ANTIALIAS)
	im.save(ausgabe + '.py.jpg', 'JPEG')

for datei in liste:

	prozess = Thread(target = bilderskalieren, args = (datei,))
	prozess.start()

Es funktioniert auch, ich bekomme aber ca. 10 Fehlermeldungen:

Code: Alles auswählen

Exception in thread Thread-15:
Traceback (most recent call last):
  File "/usr/lib64/python2.6/threading.py", line 522, in __bootstrap_inner
    self.run()                                                            
  File "/usr/lib64/python2.6/threading.py", line 477, in run              
    self.__target(*self.__args, **self.__kwargs)
  File "z.py", line 60, in bilderskalieren
    im = Image.open(datei)
  File "/usr/lib64/python2.6/site-packages/PIL/Image.py", line 1916, in open
    raise IOError("cannot identify image file")
IOError: cannot identify image file
Aber am Ende werden alle 150 Bilder korrekt generiert. Kann ich die Fehlermeldung ignorieren?

Edit:

OK, die Fehlermeldung resultiert aus der Liste.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Funktioniert es denn ohne Threads? Und werden die 150 Bilder wirklich korrekt angezeigt oder sind das noch Reste von einem voherigen Test? Mit der Fehlermeldung so dürfte das dann nämlich nicht funktionieren. Du betreibst da auch ziemliches Kamikaze mit deinen 150 Threads die du da startest.

Zu deinem eigentlichen Problem, das ist nicht so trivial, vermutlich musst du dir einen eigenen Threadpool basteln, der die Aufgaben dann abarbeitet, aber auch da muss man aufpassen(man muss den Quellcode nicht unbedingt verstehen, der Text reicht).
Benutzeravatar
DatenMetzgerX
User
Beiträge: 398
Registriert: Freitag 28. April 2006, 06:28
Wohnort: Zürich Seebach (CH)

Naja suche dir mal einige Hilfen zu queue heraus, das ist wohl das, was du benötigst.
Das mit den Threads wäre ja schon mal ganz nett, nur würde ich nicht pro Datei einen starten sondern genau 4 stuck die sich dann die Daten selbst aus der Queue (liste).

Achte hierbei unbedingt noch auf die Synchronisierung (ist die Python queue synchronisiert?)
Gabelmensch
User
Beiträge: 79
Registriert: Montag 12. Oktober 2009, 11:50

Die Fehlermeldung ruehrte daher, dass einige Eintraege in der Liste keine Bilder waren. Die Seite ist Interessant, einen Queue schaue ich mir noch an.

Ich habe es jetzt ganz dreckig geloest:

Code: Alles auswählen

from threading import Thread 
 
*listebauen* 
 
def bilderskalieren(datei): 
 
    name = os.path.basename(datei) 
    ausgabe = os.path.join(ziel, name) 
    im = Image.open(datei) 
    im.thumbnail(abmessungen, Image.ANTIALIAS) 
    im.save(ausgabe + '.py.jpg', 'JPEG') 
 
z = 0
for datei in liste: 
 
    prozess = Thread(target = bilderskalieren, args = (datei,)) 
    prozess.start()

	z = z + 1
	if z == 10:

		prozess.join()
		z = 0

So wird bei jedem 10. Thread gewartet, bis der 10. Fertig ist, bevor es weitergeht.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Gabelmensch hat geschrieben:So wird bei jedem 10. Thread gewartet, bis der 10. Fertig ist, bevor es weitergeht.
Das ist genau der Weg, wie man es nicht machen würde. Überlege dir was passiert, wenn die ersten 9 Threads fertig sind, der 10te aber noch (beliebig lang) arbeitet. Wie schon vorher von jemandem vorgeschlagen, solltest du 10 Worker-Threads erzeugen, welche sich ihre Aufgaben aus einer Queue holen oder auf die die Arbeit schon vorher verteilt wird.

Threads laufen in Python übrigens nicht wirklich parallel, da spielt der GIL nicht mit.
Das Leben ist wie ein Tennisball.
Gabelmensch
User
Beiträge: 79
Registriert: Montag 12. Oktober 2009, 11:50

So, meine neue Version.

Code: Alles auswählen

import threading

def bilderskalieren(liste):

	for datei in liste:
		
		name = os.path.basename(datei)
		ausgabe = os.path.join(ziel, name)
		im = Image.open(datei)
		im.thumbnail(abmessungen, Image.ANTIALIAS)
		im.save(ausgabe + '.py.jpg', 'JPEG', quality=95)



laenge = len(liste)
anzahljobs = 10


if laenge > anzahljobs:
	teiler = laenge / anzahljobs
	for i in range(0, laenge - 1, teiler):
		jobliste = []
		for j in range(0, teiler):
			stelle = j + i
			if stelle < laenge:
				jobliste.append(liste[stelle])
		prozess = threading.Thread(target = bilderskalieren, args = (jobliste,))
		prozess.start()
Das reicht mir fuer Heute.
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Etwas vereinfacht (ungetestet)

Code: Alles auswählen

anzahljobs = min(laenge, 10)
for i in xrange(anzahljobs):
    thread = threading.Thread(target=bilderskalieren,
                              args=(liste[i:laenge:anzahljobs],))
    thread.start()
Wobei das Holen der Bilder aus einem Queue sicher effektiver wäre. Bei Deiner Variante wäre es durchaus möglich, dass bis auf einen alle Threads fertig sind, der letzte aber noch einige Bilder umrechnen muss. Diese Arbeit könnten ihm die fertigen Threads abnehmen.
Evtl. soltest Du auch einmal das multiprocessing-Module ausprobieren. Damit kannst Du mehrere Kerne eines Multi-Core-Prozessors gleichzeitig verwenden. Wahrscheinlich ist das schneller.
MfG
HWK
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ein Tip: Slicing ;-)

Edit: Da war HWK wohl etwas schneller.
Das Leben ist wie ein Tennisball.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

HWK hat geschrieben:Evtl. soltest Du auch einmal das multiprocessing-Module ausprobieren. Damit kannst Du mehrere Kerne eines Multi-Core-Prozessors gleichzeitig verwenden. Wahrscheinlich ist das schneller.
Nicht unbedingt, das Hauptproblem ist das I/O. Vermutlich ist der GIL das einzige, was einem bei der jetzigen Lösung sogar den Hintern rettet(die I/O-Aktivität wird durch den GIL automatisch synchronisiert). Siehe mein Link.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Hi

Hier noch eine Version mit dem Multiprocessingmodul. Funktioniert bei mir auf 4 Kernel problemlos.

Code: Alles auswählen

from multiprocessing import Process, Queue
from glob import glob
import os
import Image

def rescale(q, size, target):
    while not q.empty():
        filename = q.get()
        print 'Process %d: create thumbnail of %s' % (os.getpid(), filename)

        output = 'thumb_%s' % filename
        ausgabe = os.path.join(target, output)
        
        im = Image.open(filename)
        im.thumbnail(size, Image.ANTIALIAS)
        im.save(output, 'JPEG') 

if __name__ == '__main__':
    q = Queue()
    worker = 4
    
    for x in glob('*.JPG'):
        q.put(x)
        
    for x in range(worker):
        p = Process(target=rescale, args=(q, (128,128), '.'))
        p.start()
    
    for x in range(worker):
        p.join()
Gruss
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@rayo: Auch wenn es für die Ausführung des Programms keine dramatischen Folgen hat (außer, dass es sich mal nicht beendet), denke mal über deine Zeilen

Code: Alles auswählen

    while not q.empty():
        filename = q.get() 
nach.
Das Leben ist wie ein Tennisball.
BlackJack

@rayo: Gibt's einen Grund warum Du keinen `Pool` benutzt?
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

BlackJack hat geschrieben:@rayo: Gibt's einen Grund warum Du keinen `Pool` benutzt?
Nö, war einfach das Beispiel was mir gerade am geläufigsten war.
Gabelmensch
User
Beiträge: 79
Registriert: Montag 12. Oktober 2009, 11:50

Vielen Dank fuer eure Antworten. :)
Antworten