42.10 Bildverarbeitung – Pillow 

Die Drittanbieterbibliothek Pillow ermöglicht die Verarbeitung von Rastergrafiken in Python.
Die Python-Distribution Anaconda installiert Pillow standardmäßig mit. Ansonsten lässt Pillow sich folgendermaßen nachinstallieren, wie in Abschnitt 38.4 beschrieben:
$ conda install pillow
Pillow ist außerdem im PyPI enthalten und kann über den Paketmanager pip installiert werden:
$ pip install pillow
Bei Pillow handelt es sich um einen Fork[ 234 ](Unter einem Fork versteht man ein Projekt, das durch Abspaltung aus einem anderen Projekt entstanden ist. ) der Python Imaging Library (PIL) für Python 2, die inzwischen kaum noch weiterentwickelt wird. Ein Ziel von Pillow ist es, möglichst kompatibel zu PIL zu bleiben.
An dieser Stelle finden Sie eine beispielorientierte Einführung in die Arbeit mit Pillow. Dabei werden das Laden und Speichern von Bilddateien, das Ausschneiden von Teilbereichen eines Bildes, geometrische Transformationen sowie das Anwenden und Schreiben von Filtern behandelt.
42.10.1 Bilddateien laden und speichern 

Die Basisfunktionalität einer Bibliothek zur Bildbearbeitung ist das Laden und Speichern von Bilddateien. Dies funktioniert in Pillow über die Methode open der Klasse Image:
>>> from PIL import Image
>>> lena = Image.open("lena.png")
Die Eigenschaften des geladenen Bildes sind über Attribute der resultierenden Image-Instanz verfügbar:
>>> lena.format
'PNG'
>>> lena.size
(512, 512)
>>> lena.mode
'RGBA'
Über die Methode show kann ein Bild angezeigt werden:
>>> lena.show()
Dazu speichert Pillow die Grafik in ein temporäres Verzeichnis und verwendet die Methoden des Betriebssystems zur Anzeige von Bilddateien.
Analog zu open existiert die Methode save, um geladene Bilddateien zu speichern. Dabei wird das Dateiformat anhand der angegebenen Dateiendung bestimmt. In diesem Fall speichern wir das geöffnete PNG-Bild als JPEG ab:
>>> lena.save("lena.jpg")
Pillow unterstützt die Formate BMP, EPS, GIF, IM, JPEG, JPEG 2000, MSP, PCX, PNG, PPM, SPIDER, TIFF, WebP und XBM. Darüber hinaus gibt es eine Teilunterstützung für eine Reihe weiterer Formate. Diese können gelesen, aber nicht geschrieben werden.
Abbildung 42.4 Pillow kann geladene Bilder direkt anzeigen.[ 235 ](Das hier verwendete Bild stammt aus einer Playboy-Ausgabe aus dem Jahre 1972 und hat sich kurz danach zu einem Standardbeispiel in der Bildverarbeitung entwickelt. Das abgebildete schwedische Playmate Lena Söderberg erfuhr erst viel später von seiner Popularität in der Wissenschaft. )
42.10.2 Zugriff auf einzelne Pixel 

Über die Methode load einer geöffneten Image-Instanz kann auf die dem Bild zugrunde liegende Pixelmatrix zugegriffen werden:
>>> px = lena.load()
>>> px[100, 100]
(178, 68, 78, 255)
>>> px[100, 100] = (255, 255, 255, 255)
Um auf ein bestimmtes Pixel zuzugreifen, werden seine Koordinaten[ 236 ](Die Angaben beziehen sich auf das lokale Bild-Koordinatensystem, dessen Ursprung in der oberen linken Ecke des Bildes liegt. ) in eckige Klammern hinter die Pixelmatrix-Instanz px geschrieben. In diesem Fall ist der Farbwert des Pixels mit den Koordinaten (100, 100) gleich (178, 68, 78) bei voller Opazität. Der Farbwert kann verändert werden, was sich auf das zugrunde liegende Bild lena auswirkt.
42.10.3 Teilbereiche eines Bildes ausschneiden 

Eine Image-Instanz bietet die Methode crop an, mit deren Hilfe sich Teilbereiche eines Bildes ausschneiden lassen:
>>> gesicht = lena.crop((150, 150, 400, 400))
Der gewünschte Teilbereich des Bildes wird in Form eines Tupels mit vier Werten spezifiziert. Die vorderen beiden Werte legen die Koordinaten der oberen linken Ecke und die hinteren beiden Werte die Koordinaten der unteren rechten Ecke in Pixeln fest. Der in diesem Beispiel ausgeschnittene Bereich beginnt damit an der Stelle (150, 150) und ist 250 Pixel breit bzw. hoch.
Das Ergebnis eines crop-Aufrufs wird als Image-Instanz zurückgegeben und kann ebenfalls mittels show dargestellt werden:
Abbildung 42.5 Ein ausgeschnittener Teilbereich des Bildes
42.10.4 Bilder zusammenfügen 

Eine Image-Instanz, unabhängig davon, ob sie aus einem Bild ausgeschnitten oder eigenständig geladen wurde, lässt sich in eine zweite Image-Instanz einfügen. Im folgenden Beispiel erzeugen wir eine neue Image-Instanz in der passenden Größe und verwenden dann die Methode paste, um den im vorangegangenen Abschnitt ausgeschnittenen Teilbereich gesicht zweimal einzufügen:
>>> ergebnis = Image.new("RGBA", (500, 250))
>>> ergebnis.paste(gesicht, (0, 0, 250, 250))
>>> ergebnis.paste(gesicht, (250, 0, 500, 250))
>>> ergebnis.show()
Die statische Methode new der Klasse Image erzeugt ein neues Bild der angegebenen Größe, in diesem Fall 500 Pixel breit und 250 Pixel hoch. Zusätzlich wird der Farbraum des Bildes angegeben, in diesem Fall RGBA[ 237 ](Das bedeutet, dass sich der Farbwert eines Pixels aus seinen Rot-, Grün- und Blauanteilen zusammensetzt. Zusätzlich gibt es eine Alpha-Komponente, die den Transparenzwert eines Pixels angibt. Pillow unterstützt neben RGBA eine Reihe weiterer Farbräume. ). Das Ergebnisbild der paste-Operationen sieht folgendermaßen aus:
Abbildung 42.6 Zusammenfügen von Bildern
42.10.5 Geometrische Bildtransformationen 

Pillow unterstützt eine Reihe von Operationen zur geometrischen Transformation von Image-Instanzen. Dazu zählt beispielsweise das Skalieren eines Bildes:
>>> klein = lena.resize((100, 100))
In diesem Fall wird das Beispielbild lena auf eine Größe von 100 * 100 Pixeln verkleinert und als neue Image-Instanz zurückgegeben.
Eine zweite Klasse von Bildtransformationen sind Spiegelungen und Rotationen um ganzzahlige Vielfache von 90°. Solche Transformationen ändern die Bildgeometrie nicht und werden in Pillow als Transpositionen bezeichnet. Eine Transposition kann über die Methode transpose einer Image-Instanz durchgeführt werden:
>>> trans1 = lena.transpose(Image.FLIP_LEFT_RIGHT)
>>> trans2 = lena.transpose(Image.FLIP_TOP_BOTTOM)
>>> trans3 = lena.transpose(Image.ROTATE_90)
>>> trans4 = lena.transpose(Image.ROTATE_180)
>>> trans5 = lena.transpose(Image.ROTATE_270)
Abbildung 42.7 zeigt die Ergebnisse der einzelnen Transpositionsoperationen in einem gemeinsamen Bild.
Abbildung 42.7 Geometrische Transpositionen: vertikale bzw. horizontale Spiegelung sowie Rotationen um 90°, 180° und 270°
Rotationen um ganzzahlige Vielfache von 90° sind angenehm, weil sie die Bildgeometrie unverändert lassen. Selbstverständlich können Sie Bilder mit Pillow auch beliebig rotieren. Dazu können Sie die Methode rotate verwenden:
>>> lena.rotate(30).show()
Auch in diesem Fall wird die Geometrie des Bildes beibehalten. Durch die Rotation entstandene Freiflächen werden mit der Standardhintergrundfarbe des Bildes, in diesem Fall Schwarz, gefüllt (siehe Abbildung 42.8).
Abbildung 42.8 Eine Rotation um 30°
Alternativ kann über den Schlüsselwortparameter expand beim Aufruf von rotate angegeben werden, dass die Bilddimensionen an das rotierte Bild angepasst werden sollen:
>>> lena.rotate(30, expand=True).show()
42.10.6 Vordefinierte Bildfilter 

Das Modul ImageFilter enthält eine Reihe vordefinierter Filterfunktionen, die über die Methode filter einer Image-Instanz angewendet werden können. Auf diese Weise lässt sich beispielsweise ein GaussianBlur-Filter über ein Bild legen:
>>> from PIL import ImageFilter
>>> lena.filter(ImageFilter.GaussianBlur(10)).show()
Dieser Filter berechnet den Farbwert eines jeden Pixels anhand der Farbwerte der Pixel in seiner Umgebung. Der Grad der entstehenden Unschärfe lässt sich über die Umgebungsgröße variieren, in diesem Fall beträgt die Umgebungsgröße 10 Pixel. Das Ergebnis ist in Abbildung 42.9 dargestellt.
Abbildung 42.9 Ein GaussianBlur-Filter
Neben dem hier gezeigten GaussianBlur-Filter enthält das Modul ImageFilter die Filterklassen UnsharpMask für eine Unschärfemaske, Kernel für eine Faltungsoperation sowie RankFilter, MedianFilter, MinFilter und MaxFilter für einfache umgebungsbasierte Filter.
Darüber hinaus sind die vorkonfigurierten und daher parameterlosen Filter BLUR, CONTOUR, DETAIL, EDGE_ENHANCE, EDGE_ENHANCE_MORE, EMBOSS, FIND_EDGES, SMOOTH, SMOOTH_MORE und SHARPEN in ImageFilter enthalten, die ebenfalls über filter angewendet werden können:
>>> lena.filter(ImageFilter.FIND_EDGES).show()
42.10.7 Eigene Pixeloperationen 

Wie im vorangegangenen Abschnitt geschildert, enthält Pillow eine Reihe vordefinierter Filter. Über die Methode point einer Image-Instanz ist es möglich, einfache Filterfunktionen selbst zu schreiben. Dabei dürfen sich diese Filter nur auf das betreffende Pixel selbst beziehen und nicht auf seine Umgebung. Der Methode point wird ein Funktionsobjekt übergeben, das für jede Farbkomponente jedes Pixels aufgerufen wird und diese verändern kann:
>>> lena.point(lambda i: 0 if i < 125 else 255).show()
In diesem Beispiel wird ein Schwellwertfilter angewandt, der zwischen voller Intensität (Wert 255) und keiner Intensität (Wert 0) entscheidet. Diese Entscheidung wird für jede Farbkomponente unabhängig getroffen. Das Ergebnis entspricht einer maximalen Kontrasteinstellung und ist künstlerisch wertvoll, wie Abbildung 42.10 zeigt.
Abbildung 42.10 Eine selbst definierte Pixeloperation
42.10.8 Bildverbesserungen 

Neben den bereits angesprochenen Filtern im Modul ImageFilter gibt es das Modul ImageEnhance, das eine Reihe von Klassen enthält, die verschiedene Aspekte eines Bildes, beispielsweise Helligkeit oder Kontrast, verbessern können:
>>> from PIL import ImageEnhance
>>> enhancer = ImageEnhance.Contrast(lena)
>>> enhancer.enhance(0.5).show()
Zunächst wird eine Instanz der Klasse Contrast mit dem zu bearbeitenden Bild erzeugt. Diese Instanz kann dann verwendet werden, um verschiedene Kontrasteinstellungen anzuwenden. Dazu wird die Methode enhance der Enhancer-Instanz mit dem Kontrastwert aufgerufen. Ein Wert kleiner als 1 verringert den Kontrast des Ausgangsbildes, während ein Wert größer als 1 den Kontrast erhöht.
Auf analoge Art und Weise können die Verbesserungsfilter Color für die Farbbalance, Brightness für die Helligkeit und Sharpness für die Schärfe verwendet werden.
42.10.9 Zeichenoperationen 

Pillow unterstützt grundlegende Zeichenoperationen, mit deren Hilfe einfache geometrische Formen zu einem Bild hinzugefügt werden können. Außerdem kann Text in Bilder geschrieben werden. Dazu werden die beiden Pillow-Module ImageDraw und ImageFont benötigt.
>>> from PIL import ImageDraw
>>> draw = ImageDraw.Draw(lena)
>>> draw.rectangle((150, 150, 400, 400), outline=(255,255,255,255))
Zunächst muss eine Instanz der Klasse ImageDraw erzeugt werden, die daraufhin zum Zeichnen in das bei der Instanziierung angegebene Bild verwendet werden kann, zum Beispiel zum Zeichnen eines weißen Rechtecks. Neben der Methode rectangle gibt es die in Tabelle 42.3 zusammengefassten Zeichenmethoden.
Methode | Beschreibung |
---|---|
arc | Zeichnet einen offenen Bogen. |
chord | Zeichnet einen geschlossenen Bogen. |
ellipse | Zeichnet eine Ellipse. |
line | Zeichnet eine Linie. |
pieslice | Zeichnet ein »Tortenstück«. |
point | Zeichnet einen Punkt bzw. ein Pixel. |
polygon | Zeichnet ein Polygon. |
rectangle | Zeichnet ein Rechteck. |
text | Zeichnet einen Text. |
Tabelle 42.3 Zeichenmethoden von ImageDraw
Die Klasse ImageFont kann verwendet werden, um Text in ein geöffnetes Bild zu schreiben. Dazu wird über die Methode truetype eine Schriftart geladen und in einer Schriftgröße, in diesem Fall 40 Punkt, instanziiert. Danach kann über die Methode text einer ImageDraw-Instanz ein Text in das Bild geschrieben werden.
>>> from PIL import ImageFont
>>> font = ImageFont.truetype("arial.ttf", 40)
>>> draw.text((230, 410), "Lena", font=font, fill=(255,255,255,255))
Das kombinierte Ergebnis der beiden vorangegangenen Beispiele sehen Sie in Abbildung 42.11.
Abbildung 42.11 Zeichenfunktionalität in Pillow
42.10.10 Interoperabilität 

Pillow ermöglicht es, Bilddateien komfortabel zu bearbeiten und nach der Bearbeitung entweder zu speichern oder mithilfe der Methoden des Betriebssystems darzustellen. Darüber hinaus kann Pillow mit verschiedenen GUI-Toolkits interagieren, um eine möglichst einfache Darstellung einer Image-Instanz auf einer grafischen Oberfläche zu erreichen. Dazu gibt es die Klassen ImageQt für die Interoperabilität mit PyQt und ImageTk für die Interoperabilität mit TkInter:
>>> from PIL import ImageQt
>>> lenaQt = ImageQt.ImageQt(lena)
bzw.
>>> from PIL import ImageTk
>>> from tkinter import Tk
>>> root = Tk()
>>> lenaTk = ImageTk.PhotoImage(lena)
Die angesprochenen Klassen erben von den entsprechenden Gegenstücken QtGui. QImage bzw. tkinter.PhotoImage und lassen sich daher direkt im Kontext von PyQt bzw. TkInter verwenden.