Seite 1 von 1

Bilder gleichzeitig bearbeiten (Multiprocessing)

Verfasst: Sonntag 6. Dezember 2009, 11:35
von Gabelmensch
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?

Verfasst: Sonntag 6. Dezember 2009, 11:52
von nemomuk

Verfasst: Sonntag 6. Dezember 2009, 12:04
von Gabelmensch
Das lese ich bereits seit einer Stunde durch.

Verfasst: Sonntag 6. Dezember 2009, 12:23
von Gabelmensch
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.

Verfasst: Sonntag 6. Dezember 2009, 14:07
von Darii
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).

Verfasst: Sonntag 6. Dezember 2009, 14:28
von DatenMetzgerX
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?)

Verfasst: Sonntag 6. Dezember 2009, 15:04
von Gabelmensch
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.

Verfasst: Sonntag 6. Dezember 2009, 15:20
von EyDu
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.

Verfasst: Sonntag 6. Dezember 2009, 17:10
von Gabelmensch
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.

Verfasst: Sonntag 6. Dezember 2009, 19:37
von HWK
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

Verfasst: Sonntag 6. Dezember 2009, 19:38
von EyDu
Ein Tip: Slicing ;-)

Edit: Da war HWK wohl etwas schneller.

Verfasst: Sonntag 6. Dezember 2009, 19:52
von Darii
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.

Verfasst: Sonntag 6. Dezember 2009, 20:37
von rayo
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

Verfasst: Sonntag 6. Dezember 2009, 21:01
von EyDu
@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.

Verfasst: Sonntag 6. Dezember 2009, 22:43
von BlackJack
@rayo: Gibt's einen Grund warum Du keinen `Pool` benutzt?

Verfasst: Montag 7. Dezember 2009, 07:35
von rayo
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.

Verfasst: Montag 7. Dezember 2009, 15:13
von Gabelmensch
Vielen Dank fuer eure Antworten. :)