[gelöst] PILAnfaenger: Pixelwerte aendern und Bild speichern

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
merlin_emrys
User
Beiträge: 110
Registriert: Freitag 3. März 2006, 09:47

Ich versuche gerade, mich mit der Python Image Library (PIL) vertraut zu machen, aber ich bin ziemlich bald an Anfaengerproblemen haengengeblieben. Ich verwende Python 2.5, die dazugehörige Version der PIL und Windows XP (schlagt mich nicht, ich konnte nix anderes kriegen...)

Das erste Problem ist der Versuch, Bilder unter variablen Namen abzuspeichern. Ich kann speichern, wenn ich den Bildnamen fest angebe (siehe die letzen Zeilen meines bisherigen Quellcodes), aber wenn ich den Namen "zusammensetzen" lasse (Zeilen 15 bis 21, derzeit auskommentiert), bekomme ich eine Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:\Pfad\Bildverarbeitung.py", line 19, in <module>
    Kopie.save(Speicherbezeichnung, "BMP")
  File "C:\Programme\Python25\Lib\site-packages\PIL\Image.py", line 1399, in save
    fp = __builtin__.open(fp, "wb")
IOError: [Errno 2] No such file or directory: '"TestF3.bmp"'
Das zweite Problem ist, dass ich gerne wissen wuerde, welche Pixel in meinem Bild welchen Wert haben. Ich würde das Testbild auch hier hereinstellen, aber ich fuerchte, das ist nicht vorgesehen. Es ist aber jedenfalls ein Graustufen-Bitmap mit relativ wenig verschiedenen Grauwerten.

Das Histogramm sagt mir, dass überhaupt nur 15 Werte auftreten. Das kann ganz gut sein; es sind in jedem Fall mehr als 5 Werte, aber sehr viel mehr müssen es nicht sein. Um zu sehen, welches Pixel welchen Wert hat, wollte ich jetzt jeweils die Pixel eines Grauwerts auf irgendeinen bisher nicht verwendeten Grauwert (willkürlich: 240) setzen und das entstehende Bild abspeichern.

Da ich derzeit das Bild nicht neu einlese, müssten zunehmend Bereiche den Grauwert 240 annehmen - war so meine Theorie. Die Praxis ist, dass ich viermal das Originalbild speichere.

Mein Code:

Code: Alles auswählen

# -*- coding: cp1252 -*-
import os, sys, Image

im = Image.open("Testbild.bmp")

# Ausgabe von Informationen über das Bild:
print "Bildgroesse: ", im.size
Bildbreite, Bildhoehe = im.size
print "Bildbreite = ", Bildbreite, ", Bildhoehe = ", Bildhoehe
print "Bildmodus: ", im.mode
histogra = im.histogram()
print histogra

# Versuch, eine Kopie des Bildes unter variablem Namen zu speichern:
# Kopie = im.copy()
# NeuerName = raw_input("Name für die erste Bildkopie, ohne Erweiterung: ")
# Speicherbezeichnung = "\"" + str(NeuerName) + ".bmp\""
# print Speicherbezeichnung
# Kopie.save(Speicherbezeichnung, "BMP")
# im.close()

# Versuch, Pixelwerte gezielt zu ändern:
for n in range(10,14):
    # fuer jedes n:
    for a in range(300):                       
        for b in range(300):                   
            aktuPix = im.getpixel((a,b))
            if aktuPix == n:
                aktuPix = ((a,b),240)
            b += 1
        a+= 1
    # abhängig vom n unter folgenden Namen speichern:
    if n == 10:
        im.save("Pixelaustausch0.bmp", "BMP")
    elif n == 11:
        im.save("Pixelaustausch1.bmp", "BMP")
    elif n == 12:
        im.save("Pixelaustausch2.bmp", "BMP")
    elif n == 13:
        im.save("Pixelaustausch3.bmp", "BMP")
    else:
        pass
Die Ausgabe sieht so aus:

Code: Alles auswählen

Bildgroesse:  (603, 643)
Bildbreite =  603 , Bildhoehe =  643
Bildmodus:  P
[4379, 936, 564, 1053, 548, 6012, 2033, 810, 1132, 30941, 558, 164847, 179, 170165, 2003, 1569, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Ist meine Interpretation des Bildmodus "P" als Grauwerttabelle falsch?
(Dafür spricht, daß die Grauwerte meines Erachtens weiter streuen müssten, ich hatte erwartet, dass hin und wieder ein Wert von 0 abweicht, aber der niedrigste Grauwert um die 30 und der hoechste um die 230 liegen müsste.)
Wenn das der Fall sein sollte - kann ich die Palette gezielt manipulieren?

Meinen besten Dank für die Hilfe schonmal!
Zuletzt geändert von merlin_emrys am Freitag 23. Februar 2007, 15:20, insgesamt 1-mal geändert.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

merlin_emrys hat geschrieben:...Das erste Problem ist der Versuch, Bilder unter variablen Namen abzuspeichern. Ich kann speichern, wenn ich den Bildnamen fest angebe (siehe die letzen Zeilen meines bisherigen Quellcodes), aber wenn ich den Namen "zusammensetzen" lasse (Zeilen 15 bis 21, derzeit auskommentiert), bekomme ich eine Fehlermeldung: ...
Benutz am besten die Methode os.path.join(<string1>, <string2>, ...) um einen Pfad zusammen zusetzen.

Bsp.:

Code: Alles auswählen

>>> import os.path
>>> root = 'c:\\work'
>>> sub = 'test'
>>> a_name = 'blup.txt'
>>> os.path.join(root, sub, a_name)
'c:\\work\\test\\blup.txt'
merlin_emrys
User
Beiträge: 110
Registriert: Freitag 3. März 2006, 09:47

Masaru hat geschrieben:Benutz am besten die Methode os.path.join(<string1>, <string2>, ...) um einen Pfad zusammen zusetzen.
Damit bekomme ich aber das ".bmp" nicht an meinen NeuerName? Das ist aber ja das einzige, was ich dringend brauche; der Rest vom Pfad ist "relativ", d.h. ich gebe ihn nicht an.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Code: Alles auswählen

...
>>> base = 'c:\\work'
>>> bilder = 'bilder'
>>> dateiname = 'bild1'
>>> dateityp = '.bmp'
>>> pathname = os.path.join(base, bilder, dateiname) + dateityp
>>> pathname
'c:\\work\\bilder\\bild1.bmp'
Fuchs mich zudem gerade ein wenig in die PIL ein, und was mir dabei schonmal aufgefallen ist ... Dir fehlt mindestens irgendwo ein im.putpixel(<(x, y)>, <value>), ohne welches sich dein Bild nicht ändern würde.

Du ließt zwar immer schön einen Pixelwert aus ... aber im BildObjekt änderst Du ihn vor dem speichern nicht.
merlin_emrys
User
Beiträge: 110
Registriert: Freitag 3. März 2006, 09:47

Okay, jetzt hab ich's! Danke!! :-)

Der Code sieht jetzt so aus:

Code: Alles auswählen

        Vorname = "Pixelaustausch"
        Bildnummer = str(n)
        Dateityp = ".bmp"
        Bildname = os.path.join(Vorname) + Bildnummer + Dateityp
        print Bildname
        image.save(Bildname, "BMP")
Meine Vermutung mit der Palette hat auch gestimmt. Inzwischen habe ich ImageOps.grayscale(image) gefunden, und damit bekomme ich auch die volle Palette und kann Werte ueber image.putpixel((a,b),240) auch gezielt ändern.

Woran ich jetzt festhänge, ist das Schliessen von Bildern. Wenn ich eine Zeile

Code: Alles auswählen

image.close()
in meinen Code einbaue, bekomme ich:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:/Pfad/Speichertest02c.py", line 54, in <module>
    image.close()
  File "C:\Programme\Python25\Lib\site-packages\PIL\Image.py", line 493, in __getattr__
    raise AttributeError(name)
AttributeError: close
Wie kriege ich meine Bilder also wieder "zu"?
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Code: Alles auswählen

del(image)
... sollte auf jeden Fall funktionieren.

Warum keine .close Methode für ein "Image" Objekt vom Author mit eingebaut wurde ... *zuckt mi den Schultern* ... keine Ahnung ;).

Gruß,
>>Masaru<<
merlin_emrys
User
Beiträge: 110
Registriert: Freitag 3. März 2006, 09:47

Masaru hat geschrieben:

Code: Alles auswählen

del(image)
... sollte auf jeden Fall funktionieren.
Jau, tut es, tadellos!

Nochmal ganz herzlichen Dank! :-)
(Und alle weiteren Fragen müssen bis morgen warten ;-) .)
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Dann einen schönen Feierabend *g*.
BlackJack

Das ``del image`` sollte überflüssig sein. Bilder müssen nicht explizit geschlossen werden.

Zur allerersten Fehlermeldung:

Code: Alles auswählen

IOError: [Errno 2] No such file or directory: '"TestF3.bmp"'
Schau mal den Namen ganz genau an, da sind Anführungszeichen drin, die da so wohl nicht hingehören. Das mag Windows anscheinend nicht.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

BlackJack hat geschrieben:Das ``del image`` sollte überflüssig sein. Bilder müssen nicht explizit geschlossen werden.
... nicht unbedingz explizit (endet die Python Ausführung, so endet auch der Handle) ... es sei denn, man möchte die Datei dahinter, die man mit der "Image.open()" Methode addressiert hatte: löschen oder umbenennen.

Auf einer Windows Kiste wohlgemerkt. Unix Systeme können in der Regel damit besser umgehen.

Windows mag nämlich die oben genannten Datei-Operationen auf Dateien mit offenen Filehandles so ganz und gar nicht. Benutzt man den Windows Dateiexplorer, so wird die hässliche Meldung herraus gegeben, dass die Datei von einer anderen Person, bzw. einem anderen Programm verwendet wird ;). Mit Python gäbe es einen: OSError: [Errno 13] Permission denied:...

Offene Filehandles sind wie Schnürsenkel ... sind sie geöffnet, kann man damit mal auch leicht auf die Schnautze fallen.
Benutzeravatar
mq
User
Beiträge: 124
Registriert: Samstag 1. Januar 2005, 19:14

"del image " zum Schliessen halte ich uebrigens fuer eine schlechte API - del ruft ja nicht in jedem Fall __del__() auf, man kann gar nicht sicher stellen, dass diese Methode aufgerufen wird. Von daher sollte man nach Moeglichkeit immer .close() implementieren (dass das hier nicht geht, ist natuerlich klar)
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

lumax hat geschrieben:"del image " zum Schliessen halte ich uebrigens fuer eine schlechte API - del ruft ja nicht in jedem Fall __del__() auf, man kann gar nicht sicher stellen, dass diese Methode aufgerufen wird. Von daher sollte man nach Moeglichkeit immer .close() implementieren (dass das hier nicht geht, ist natuerlich klar)
Ganz genau. "del" ist fast so eine schlimme Falle wie "global", da sollte man auch mal eine Wikiseite drüber schreiben...
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
BlackJack

Wenn man ein Bild geöffnet hat und auf die Pixeldaten zugreift, dann wird die Datei komplett gelesen *und geschlossen*. Man braucht dann folglich auch kein `close()` oder ``del`` für das Bild-Objekt.

Ums Schliessen muss man sich nur selber kümmern, wenn man die Bilder nur "antestet", zum Beispiel um Grösse und Modus herauszufinden.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

lumax hat geschrieben:"del image " zum Schliessen halte ich uebrigens fuer eine schlechte API ...
Korrekt .... aber der Author von PIL hat nun einmal ein "close" oder komplettes Einlesen mit nachfolgendem "close" mittels der Methode Image.open() ist leider nicht implementiert BlackJack.

Was also tun? Das löschen des ImageObjektes (del) sorgt indirekt dafür, dass der FileHandle mit freigegeben wird.

Nicht gerade die saubere Art .. aber leider die einzige für den Thread-Ersteller (es sei denn, er schreibt die PIL Module um).

Oder hat jemand eine andere Idee? Nur herraus damit *g*.
Benutzeravatar
mq
User
Beiträge: 124
Registriert: Samstag 1. Januar 2005, 19:14

Masaru hat geschrieben:Was also tun? Das löschen des ImageObjektes (del) sorgt indirekt dafür, dass der FileHandle mit freigegeben wird.
Nein, tut es nicht. del loescht nicht das Objekt, es loescht nur einen Namen (und damit eine Referenz auf das Objekt). Wenn keine Referenzen mehr existieren, kann der Garbage Collector das Objekt loeschen (Betonung auf kann - er kann sich auch Zeit damit lassen, bis der Interpreter geschlossen wird, und in dem Fall sollte das Betriebssystem eh alle Filehandles freigeben), und genau in dem Moment, in dem er das tut, wird __del__() aufgerufen. Und genau das ist der Grund, warum __del__() als Destruktor nicht geeignet ist.
BlackJack

Masaru hat geschrieben:
lumax hat geschrieben:"del image " zum Schliessen halte ich uebrigens fuer eine schlechte API ...
Korrekt .... aber der Author von PIL hat nun einmal ein "close" oder komplettes Einlesen mit nachfolgendem "close" mittels der Methode Image.open() ist leider nicht implementiert BlackJack.
Doch genau das ist implementiert Masaru. Wenn das Bild in den Speicher geladen wurde, dann wird die Datei geschlossen.

Code: Alles auswählen

In [24]: a = Image.open('_scan0011.png')

In [25]: print a.fp
<open file '_scan0011.png', mode 'rb' at 0xb77b90f8>

In [26]: a.getpixel((10, 10))
Out[26]: 254

In [27]: print a.fp
None
Also wird hier über ein Problem diskutiert das es für das gegebene Programm gar nicht gibt.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Code: Alles auswählen

>>> import Image
>>> i = Image.open(r'd:\work\Masaru.gif')
>>> i.getpixel((0,0))
0
>>> import os
>>> os.remove(r'd:\work\Masaru.gif')
Traceback (most recent call last):
  File "<interactive input>", line 1, in ?
OSError: [Errno 13] Permission denied: 'd:\\work\\Masaru.gif'
Diskussionen sind einfach doch etwas herliches :roll:

Achja ...

Code: Alles auswählen

...
>>> del(i)
>>> os.remove(r'd:\work\Masaru.gif')
>>> # keine Fehlermeldung geraised
merlin_emrys
User
Beiträge: 110
Registriert: Freitag 3. März 2006, 09:47

BlackJack hat geschrieben:Doch genau das ist implementiert Masaru. Wenn das Bild in den Speicher geladen wurde, dann wird die Datei geschlossen.
...
Also wird hier über ein Problem diskutiert das es für das gegebene Programm gar nicht gibt.
Okay, vielen Dank für die Erklärung :-) !
Ich hab' das Manual vielleicht doch arg "selektiv" gelesen, der Hinweis ist mir irgendwie entgangen. Aber gut, das vereinfacht die Sache schon - zumindest insofern, daß das Original immer unverändert bleibt, bis man es explizit überspeichert. Wenn allerdings das Programm zwischendurch abbricht, kann man sich nicht mal im Bild schauen, wie weit die Änderungen schon fortgeschritten waren... oder hat PIL für sowas eine Funktion :-o ? Ich bin auf Hinweise zu "show" gestossen, aber da ich keinen Unix-Rechner zur Hand habe, kann ich damit nicht arbeiten.
BlackJack

Masaru hat geschrieben:

Code: Alles auswählen

>>> import Image
>>> i = Image.open(r'd:\work\Masaru.gif')
>>> i.getpixel((0,0))
0
>>> import os
>>> os.remove(r'd:\work\Masaru.gif')
Traceback (most recent call last):
  File "<interactive input>", line 1, in ?
OSError: [Errno 13] Permission denied: 'd:\\work\\Masaru.gif'
Das scheint vom Dateityp abhängig zu sein. Bei PNG ist bei mir die Datei nach dem einlesen geschlossen bei GIF nicht.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

BlackJack hat geschrieben:... Das scheint vom Dateityp abhängig zu sein. Bei PNG ist bei mir die Datei nach dem einlesen geschlossen bei GIF nicht.
Ist dann wirklich ein interessantes und zudem nicht einheitliches Verhalten der PIL. Entsprechend sollten wir dieses Thema ein wenig tiefer doch weiter diskutieren.

Ich schaue mir einmal die Dateityp-Klassen in der PIL die Tage genaue an.
Antworten