lange Laufzeiten bei PIL (Imaging) + Speicherprobleme

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
axon
User
Beiträge: 15
Registriert: Donnerstag 23. Juni 2011, 19:21

Hallo,

ich benutzt die PIL für wissenschaftliche Datenverarbeitung. Dabei stehe ich momentan vor zwei wesentlichen Problemen, die mir das Arbeiten ein wenig erschweren:
1. Die Laufzeiten sind extrem lange:

Code: Alles auswählen

import Image
img=Image.open(newfile)
try:
     for m in range(0,1100):
         print m # nur um zu sehen, dass das Programm noch arbeitet
         img.seek(m) # blättern innerhalb des Stacks
         for i in range(1,img.size[0]): # "Scanen" der einzelnen Bilder
              for j in range(1,img.size[1]):
                  if img.getpixel((i,j)) == 255: # falls ein weisser (255) Pixel gefunden wird, dann...
                      img.show()
except EOFError: 
     print  m,"Ende der Sequenz \n"
Das Script soll innerhalb eines Stacks mit i.d.R. 1000 einzeln Bildern weiße Pixel (255) aufspüren. (Zur Vereinfachung habe ich die mathematisch folgenden Operationen durch "show()" ersetzt) - Das dauert aber extrem lange…
Gegenüber Java habe ich schon den Vorteil gemerkt, dass python sehr sparsam für den Arbeitsspeicher ist und nicht gleich der komplette Stack geladen wird, sondern teilweise nur 3 MB bei Daten die z.T. 400 MB pro Stack ausmachen.
Es wäre vielleicht für die Laufzeit hilfreich den gesamten Stack mit einem mal in den Speicher zu laden - wie würde das gehen?
Gibt es sonst Hinweise, wie ich mich schneller durch einen Stack bewegen kann, als oben angegeben?

2.Das zweite Problem ist folgendes.
Das, was im obigen Code durch das "show()" ersetzt wurde, enthält teilweise

Code: Alles auswählen

img.putpixel((x,y),100)
nun habe ich das Problem, dass diese speziell markierten Pixel (100) nicht gespeichert werden, sobald ich über "bild.seek(m)" zum nächsten Bild wechsle. Das ist ärgerlich - Gibt es eine Möglichkeit die Änderung eines Bildes innerhalb des Stacks an der selben Stelle zu speichern, wo auch vorher das Bild war?

Vielen Dank bereits im Vorhinein.
Viele Grüße
Axon
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Schwierig. Erstens ist PIL generell lahm und es gibt unter Python leider keine Alternative. Dazu kommt, dass deine Methode dann noch besonders langsam ist. Schneller funktioniert es vermutlich, wenn die einzelnen Unterbilder komplett als numpy.array in den Speicher lädst und dann verarbeitest. Baucht dann halt mehr Speicher, aber 400 MB sind ja noch händelbar.

Code: Alles auswählen

for i in ...:
   img.seek(1)
   ar = numpy.array(img)
   ar[ar==255] = 100
Speichern musst du vermutlich zu fuß machen, wenn es nicht einfach funktioniert, also selbst das Bild aus Einzelbildern neu erstellen. Entweder das geht per PIL oder du musst ein externes Script nehmen(imagemagick oder so).
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Numpy heißt die Lösung! Es ist *die* Bibliothek für wissenschaftliches Rechnen mit Python. PIL ist eine Library um verschiedenste Bildformate laden und speichern zu können. Zur Auswertung in Sinne von Bildverarbeitung eignet sie sich nur bedingt. Mit numpy wirst du sicherlich eine performante Lösung hinkriegen.
Du kannst ein mit der PIL geladenes Bild einfach mit

Code: Alles auswählen

numpy.asarray(im)
in ein numpy array umwandeln.
numpy bietet die Möglichkeit mit 3-dimensionalen arrays zu arbeiten. Auf diese Weiße kannst du deinen Bilder-Stack in ein Array packen. Das funktioniert dann so oder so ähnlich:

Code: Alles auswählen

>>> stack = numpy.zeros([3,2,2], dtype="uint8")
>>> stack
3: array([[[0, 0],
        [0, 0]],

       [[0, 0],
        [0, 0]],

       [[0, 0],
        [0, 0]]], dtype=uint8)
>>> stack[0]
4: array([[0, 0],
       [0, 0]], dtype=uint8)
>>> stack[0] = numpy.array([[1,2],[3,4]])
>>> stack
5: array([[[1, 2],
        [3, 4]],

       [[0, 0],
        [0, 0]],

       [[0, 0],
        [0, 0]]], dtype=uint8)
In dem Array kannst du dann effizient verschiedene Operationen durchführen, wie bspw. bestimmte Felder auf einen Wert setzen.

Code: Alles auswählen

stack[stack==255] = 100
Evtl. findest du in der numpy example list genau die Operation die du suchst:
http://www.scipy.org/Numpy_Example_List_With_Doc

MFG HerrHagen
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Hoi,

ich möchte noch die scipy.ndimage-Methoden ins Rennen werfen und

Code: Alles auswählen

from scipy.misc import toimage, fromimage
import Image

x = fromimage(Image.open(file name))
image = toimage(x)
image.save(file name, image type)
Was IMHO auch einen ziemlich klaren und leicht nachvollziehbaren Code ergeben kann. Ein paar Beispiele hier (ggf. veraltet, weil ich den Code nicht mehr pflege).

HTH
Christian
deets

Last but not least: OpenCV hat Python-Bindings.
axon
User
Beiträge: 15
Registriert: Donnerstag 23. Juni 2011, 19:21

Vielen Dank für die Antworten!
Leider kann ich momentan nur noch keinen richtigen Vorteil dieser Methode entdecken…

Zunächst:
Ich habe meinen Code wie folgt abgeändert:

Code: Alles auswählen

import Image
import numpy as np

ar = []
img=Image.open(newfile)
VariableA = 0
VariableB = 0

try:
     for k in range(0,1100):
         img.seek(k)
         ar.append(np.array(img))
except EOFError:
     print "Ende des Stacks erreicht..."

ysize=img.size[1]
xsize=mg.size[0]

#2. Schleife:
try:
     for m in range(0,k):
         print m # nur um zu sehen, dass das Programm noch arbeitet
         for i in range(1,ysize): # "Scanen" der einzelnen Bilder
              for j in range(1,xsize):
                  if ar[m][i][j] == 255: # falls ein weisser (255) Pixel gefunden wird, dann...
                      if ar[m+1][i][j] == 255:
                         VariableA = VariableA + 1
                      if ar[m+1][i][j] == 0:
                         VariableB = VariableB + 1
except IndexError: 
     print  m,"unerwarteter Fehler \n"
print "Ende der Sequenz"


CAVE: in diesem Code sind nun i und j in ihrer Zuordnung zu x und y Koordinate anders als im vorhergehenden Beispiel, (i ist nun y zugeordnet) da sich der array als ar[slide][y][x] darstellt.
Außerdem habe ich img.show() durch eine einfache Operation ersetzt, da ich innerhalb der zweiten Schleife das Image Objekt nun gar nicht mehr aufrufe…

Ich habe das Problem, dass ich in dieser Art der Berechnung dreimal so lange Laufzeiten habe. (In einem für diese Problematik unwesentlichen Teil des Programms lasse ich immer bestimmen, wie viele Bilder über 10 Sekunden bearbeitet wurden. Die Zahl der Bilder hat sich (übertragen auf den array) von 156 Bilder / 10sec mit der alten Methode auf ca. 59 Bilder / 10sec mit dieser Methode verlangsamt. Aber auch insgesamt läuft dieses Skript ca. 3 mal so lange wie das vorhergehende … das finde ich komisch…

Weiß jemand Rat? Woran könnte das liegen ... sollte es nicht eigentlich schneller gehen nur mit einem array anstatt mit einem Bild?
Ich habe erstmal ein kleines Testbild mit 15 MB genommen - und tatsächlich steigt nun auch der Arbeitsspeichergebrauch um ca 15 MB mit dem Start des Programms…
Zuletzt geändert von axon am Sonntag 26. Juni 2011, 10:13, insgesamt 5-mal geändert.
axon
User
Beiträge: 15
Registriert: Donnerstag 23. Juni 2011, 19:21

Ich habe noch ein bisschen weitergedacht … ließe sich nicht eventl. die eigentliche Berechnung auch an C/C++ übergeben? Oder kann ich da diesen multidimensional array gar nicht händeln? :K (als ich C++ gelernt habe waren arrays für mich noch Tabellen… :roll: )

Macht das Sinn oder nicht? - Oder wird dann alles nur noch langsamer?

- Kann mir jemand vielleicht einen Tipp geben, wie das gehen würde - hab ich selbst noch nie gemacht… :oops:
axon
User
Beiträge: 15
Registriert: Donnerstag 23. Juni 2011, 19:21

Ich habe gerade eben noch einmal die genauen Zeiten gestoppt:

Für mein vollständiges Skript (nicht hier dargestellt) läuft

die Variante_1 (ohne numpy):
716.86 Sekunden

die Variante_2 (mit numpy und array):
1883.24 Sekunden

1883/716 = 2.6

allein das erstellen des Arrays dauert ungefähr 4 Sekunden … wenn er dann wenigstens einen Vorteil bringen würde

Ich werde das Gefühl nicht los etwas elementares falsch gemacht zu haben. Das ist höchst unbefriedigend…
Ich bin für jeden Ratschlag dankbar.
BlackJack

@axon: Du benutzt die Arrays in der Tat falsch. Du verwendest im Grunde nichts was irgendwie einen Vorteil gegenüber Listen hat. Schau Dir mal an was `numpy`-Arrays für Operationen bieten und drücke dann Deine ”manuellen” Schleifen in Operationen aus die auf ganze Arrays/Slices wirken.

Du kannst Dir zum Beispiel eine Liste aller Indices geben lassen an denen ein Bild den Wert 255 hat. Und dann kannst Du diese Liste mit Indizes verwenden um alle Werte aus einem anderen Bild zu holen und dort dann die heraus filtern die den Wert 255 oder den Wert 0 haben und die Zählen. Dann passieren eine Menge Operationen intern in kompiliertem C-, C++-, oder Fortran-Code.

Edit: Ungetestet:

Code: Alles auswählen

#2. Schleife:
try:
    for m in xrange(k):
        print m
        frame, next_frame = ar[m], ar[m + 1]
        # 
        # Werte aus `next_frame` bei denen das Element in `frame` den
        # Wert 255 hat.
        # 
        values = next_frame[frame == 255]
        VariableA += (values == 255).sum()
        VariableB += (values == 0).sum()
except IndexError: 
    print m, 'unerwarteter Fehler \n'
print 'Ende der Sequenz'
axon
User
Beiträge: 15
Registriert: Donnerstag 23. Juni 2011, 19:21

@ BlackJack:
Entschuldigung bezüglich der vielleicht doofen Nachfrage: Könnte ich einen Tipp haben welcher Befehl mir für einen Gegebenenwert alle Indices heraus sucht? Im Grunde beschreibt das BlackJack-Beispiel schon ziemlich gut, was ich machen will. Nur wie?

Numpy ist vollkommen neu für mich, da werde ich mich wieder ein bisschen einlesen müssen.

Edit: Ich hab den Code noch nicht gelesen … der kam erst gerade…

Edit2:
@ Black Jack : VIelen Dank!
… der ode hilft schon ziemlich viel … für eine andere Sache bräuchte ich dann allerdings auch mal die genauen Koordinaten bzw. Indices … die kann man doch bestimmt auch per Liste ausgeben - sprich: wenn der Wert = 255: dann gib die Koordinaten aus…
Könnte ich da auch noch einen Hinweis bekommen?
Gibt es die Möglichkeit Werte des Arrays unter einer Bedingung zu ändern?
Zuletzt geändert von axon am Sonntag 26. Juni 2011, 13:34, insgesamt 2-mal geändert.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

axon hat geschrieben:Numpy ist vollkommen neu für mich, da werde ich mich wieder ein bisschen einlesen müssen. Ein kleiner Code, oder eine Korrektur in meinem Beispiel würde mir ersteinmal zum besseren Verständnis helfen … :oops: :K
Das gibts schon in meinem Beitrag und wird dann noch in weiteren Beiträgen wiederholt. Dass das erstellen des Arrays 4 Sekunden dauert liegt an PIL, ist einfach saulahm…
axon
User
Beiträge: 15
Registriert: Donnerstag 23. Juni 2011, 19:21

Darii hat geschrieben:Das gibts schon in meinem Beitrag und wird dann noch in weiteren Beiträgen wiederholt. Dass das erstellen des Arrays 4 Sekunden dauert liegt an PIL, ist einfach saulahm…
Das erstellen des Arrays kann nicht nur an PIL liegen, oder?! Wenn ich nur PIL verwende beginnen die Berechnungen bereits nach 0.79 Sekunden Wenn ich Vorher das ganze in eine Liste überführe dauert es 4.53 Sekunden … außerdem habe ich die Listen ja scheinbar falsch verwendet…

@ Darii
Danke für Deine Code… den habe ich ja wie Du siehst versucht bei mir einzuarbeiten … habe dabei nur kein suffizienertes Ergebnis erzielen können...
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

axon hat geschrieben:Das erstellen des Arrays kann nicht nur an PIL liegen, oder?! Wenn ich nur PIL verwende beginnen die Berechnungen bereits nach 0.79 Sekunden Wenn ich Vorher das ganze in eine Liste überführe dauert es 4.53 Sekunden
Du verlagerst einfach nur die Arbeit des Array erstellens von nebenher auf vorher. Wenn man ein PIL-Bild in ein numpy.array umwandelt werden die ganzen Bilddaten erst umständlich in einen String geschrieben, der dann von numpy weiterverarbeitet werden muss. Da das ganze auch noch komplett in Python geschieht, ist das einfach langsam.
axon
User
Beiträge: 15
Registriert: Donnerstag 23. Juni 2011, 19:21

Also für das aktuelle Problem ersteinmal vielen Dank!
Das Skript läuft mittlerweile in 54.87 Sekunden einmal durch
Allerdings wird es hintenraus um einiges langsamer … bei 1000 Bildern braucht das Skript für die ersten 100: 2.18 Sek
bis Bild 500 läuft das Skript dann schon 17.14 Sekunden und bis Bild 1000 dann eben 54.87 Sek.

Hat jemand eine Ahnung woran das liegen könnte?
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Speicher voll?
axon
User
Beiträge: 15
Registriert: Donnerstag 23. Juni 2011, 19:21

@ Darii ... nein, der Speicher ist nicht voll …sieht jedenfalls nicht so aus …

Ich habe das Programm heute mal in voller Ausführung mit allend Tricks die ich nun übers WE gelernt habe und noch ein bisschen eigenen Optimierungsstrategien laufen lassen und bin am Ende auf eine Laufzeitoptimierung von Faktor 60 auf unserem Uniserver gekommen - im Vergleich zu dem was ich vor dem WE habe laufen lassen … also ein großes Dankeschön in die Runde … es fühlt sich schon mächtig an, wenn man anstatt 10 h nur 10 Minuten rechnet …

Muss ich einen neunes Thema anfangen für folgende Frage - oder kann mir da jemand vielleicht noch einen Befehl zu flüstern?
Ich möche einen Array haben, der alle Werte Paare x,y enthält für die ar==255 - gibt es da auch eine schnelle Variante?

Mfg
axon
Benutzeravatar
gkuhl
User
Beiträge: 600
Registriert: Dienstag 25. November 2008, 18:03
Wohnort: Hong Kong

@axon: Man könnte das wie folgt machen.

Code: Alles auswählen

In [43]: import numpy as np

In [44]: img = np.random.randint(0,255,(10,10))

In [45]: img
Out[45]: 
array([[218, 182,  68, 169, 162, 120, 210, 136, 252, 116],
       [ 62, 238,  61, 190,  30, 145,  77, 137,  59, 163],
       [110, 211,  80,  55, 175, 234,  42,   3, 114, 169],
       [106, 118,  82, 113,  33, 251, 192, 221, 185, 121],
       [119,  88, 191,  88,  38, 250, 116, 204, 130,  10],
       [ 97,  19, 135, 129,  53, 250, 237, 197,  10, 240],
       [ 67,  90,  13, 191, 199,  86, 253, 184, 225,  50],
       [ 34, 192, 108,  88, 138,  90, 247, 125, 174, 158],
       [170,  27,  43,  98,  87,  45,  85, 190, 129, 210],
       [ 40, 110,  88, 108, 150, 180,  94, 230, 236, 116]])

In [46]: x,y = np.indices(img.shape)

In [47]: ind = x[img>200], y[img>200]

In [48]: ind
Out[48]: 
(array([0, 0, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 6, 6, 7, 8, 9, 9]),
 array([0, 6, 8, 1, 1, 5, 5, 7, 5, 7, 5, 6, 9, 6, 8, 6, 9, 7, 8]))

In [49]: img[ind]
Out[49]: 
array([218, 210, 252, 238, 211, 234, 251, 221, 250, 204, 250, 237, 240,
       253, 225, 247, 210, 230, 236])
Häufig reicht aber auf folgendes:

Code: Alles auswählen

In [56]: mask = img>200

In [57]: img[mask]
Out[57]: 
array([218, 210, 252, 238, 211, 234, 251, 221, 250, 204, 250, 237, 240,
       253, 225, 247, 210, 230, 236])
Grüße
Gerrit
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Noch ein Tip zum Thema Speicher: Mit numpy.who() kannst du dir den Speichplatz anschauen den die Arrays belegen:

Code: Alles auswählen

>>> import numpy
>>> test1 = numpy.random.randint(0, 10, [1000, 1000])
>>> test2 = numpy.arange(100000, dtype="uint8")
>>> numpy.who()
Name            Shape             Bytes            Type
============================================================

test1           1000 x 1000       4000000          int32
test2           100000            100000           uint8

Upper bound on total bytes  =       4100000
Antworten