Bildverarbeitung in Python

Rastergrafiken in Python

Wir wollen nun Python-Funktionen schreiben, mit denen Histogramme berechnet und Bilder manipuliert werden können. Dazu verwenden wir das Paket pillow, das einen einfachen Zugriff auf Bilder in verschiedenen Dateiformaten implementiert. Nach der Installation über die Paketverwaltung können wir das Paket mit

from PIL import Image

in eigene Python-Programme einbinden. Danach können wir über das Modul Image Funktionen und Klassen zum Zugriff auf Bilder verwenden. Die Klasse Image dieses Moduls repräsentiert ein Bild.

Objekte dieser Klasse haben Attribute width und height zum Zugriff auf ihre Größe. Außerdem ist es möglich mit

color = image.getpixel((x, y))

auf den Farbwert des Pixels an Position (x, y) zuzugreifen und diesen mit

image.putpixel((x, y), color)

zu verändern, wenn image ein Image-Objekt ist. Die Pixelkoordinaten werden hierbei als Paar in runden Klammern angegeben. Paare sind (wie Tripel und größere Tupel) in Python Datenstrukturen, auf die ähnlich wie auf Arrays zugegriffen werden kann, die aber nicht mutierbar sind.

Für Grauwertbilder sind die Farbwerte einfache Zahlen von 0 bis 255, die Graustufen angeben. Für Farbbilder sind die Farbwerte dagegen Tripel aus Zahlen, jeweils von 0 bis 255, die Rot-, Grün- und Blauwerte der Farbe. Wir können also

white = (255, 255, 255)

schreiben, um einen weißen Farbwert zu erzeugen. Ist color ein Farbwert, so speichern die folgenden Zuweisungen den Rot-, Grün- bzw. Blau-Wert (zwischen 0 und 255) des Farbbildes image an der Position (x, y) in einer entsprechenden Variablen.

red = image.getpixel((x, y))[0]
green = image.getpixel((x, y))[1]
blue = image.getpixel((x, y))[2]

Neue Objekte der Klasse Image können wir erzeugen, indem wir sie mit der Funktion Image.open aus einer Datei einlesen.

image = Image.open('filename.png')

Alternativ können wir ein Bild mit der Kontruktor-Funktion Image.new durch Angabe seiner Größe und eines Farbwertes erzeugen, der für alle Pixel verwendet wird.

image = Image.new('RGB', (width, height), (255, 255, 255))

Der erste Parameter ist ein String, der das Farbformat des neuen Bildes angibt: 'RGB' für ein RGB-Farbbild oder 'L' für ein Grauwertbild (“L” steht hier für luminosity, also Helligkeit).

Schließlich können wir Bilder auch abspeichern, indem wir die Methode save verwenden.

image.save('filename.png')

Als erstes Beispiel für Bildverarbeitung in Python definieren wir eine Prozedur zur Umwandlung eines übergebenen RGB-Bildes in Graustufen.

def desaturate(image):
    for y in range(0,image.height):
        for x in range(0,image.width):
            gray = average(image.getpixel((x, y)))
            image.putpixel((x, y), (gray, gray, gray))

Sie durchläuft alle Pixel des Bildes, berechnet den Grauwert mit Hilfe einer noch zu definierenden Funktion average und überschreibt dann den aktuellen Pixel mit seinem Grauwert. Die Funktion average berechnet zunächst die Rot-, Grün- und Blauwerte des übergebenen Farbwerts (das Tupel color mit drei Werten) und gibt dann deren Mittelwert zurück.

def average(color):
    return round((color[0] + color[1] + color[2]) // 3)

Die folgende Prozedur liest ein Bild aus einer Datei ein, wandelt es in Graustufen um und speichert es mit einem anderen Namen ab.

def save_desaturated(base_name):
    image = Image.open(base_name + '.png')
    desaturate(image)
    image.save(base_name + '_gray.png')

Dazu wird der Teil des Dateinamens vor der Dateiendung .png übergeben. Wenn die eingelesene Datei den Namen filename.png hat, muss also 'filename' übergeben werden. Das Graustufenbild wird dann in einer Datei mit dem Namen filename_gray.png abgespeichert.

Grauwert-Histogramme

Als Nächstes definieren eine Funktion zur Berechnung eines Histogramms der Grauwerte eines Bildes.

def gray_histogram(image):
    histogram = [0] * 256
    for y in range(0,image.height):
        for x in range(0,image.width):
            gray = average(image.getpixel((x, y)))
            histogram[gray] = histogram[gray] + 1
    return histogram

Dazu erzeugen wir ein Array aus 256 Zahlen, einer für jeden Grauwert. Dieses Array füllen wir dann, indem wir alle Pixel durchlaufen, den Grauwert jedes Pixels berechnen und jedesmal die entsprechende Anzahl erhöhen.

Helligkeit analysieren

Aus dem Histogramm lassen sich, ohne Kenntnis des Bildes, interessante Eigenschaften berechnen. Als Beispiel definieren wir eine Funktion, die die mittlere Helligkeit eines Bildes nur anhand seines Histogramms berechnet. Dazu berechnen wir gleichzeitig die Anzahl der Pixel und die Summe der Grauwerte aller Pixel. Letztere berechnen wir, indem wir jeden Grauwert mit der Anzahl der Pixel mit diesem Grauwert multiplizieren und die Ergebnisse addieren.

def mean_brightness(histogram):
    total_gray = 0
    pixel_count = 0
    for gray in range(0,256):
        count = histogram[gray]
        total_gray = total_gray + gray * count
        pixel_count = pixel_count + count
    return round(total_gray / pixel_count)

Für das dunkle Bild vom Anfang dieses Abschnitts ergibt sich eine mittlere Helligkeit von 63, für das helle Bild eine von 189.

Pixel-weise Manipulation

Zur Manipulation von Bildern in Graustufen können wir, wie in GIMP gesehen, Abbildungen von Grauwerten in Grauwerte verwenden. Diese stellen wir in Python als Listen der Länge 256 dar, deren Einträge Zahlen zwischen 0 und 255 sind. So dargestellte Abbildungen können wir mit der folgenden Prozedur auf Bilder anwenden.

def change_pixels(image, gray_map):
    for y in range(image.height):
        for x in range(image.width):
            gray = gray_map[average(image.getpixel((x, y)))]
            image.putpixel((x, y), (gray, gray, gray))

Diese Prozedur durchläuft alle Pixel, berechnet den neuen Grauwert anhand des alten Grauwertes und der übergebenen Abbildung und überschreibt den alten Grauwert durch den neuen.

Helligkeit bearbeiten

Als Beispiel für eine Abbildung von Grauwerten berechnen wir eine Abbildung zum Aufhellen (oder Verdunkeln) eines Bildes durch Addition (oder Subtraktion) einer Konstanten.

def brightness_adjustment(diff):
    gray_map = [0] * 256
    for gray in range(0,256):
        new_gray = gray + diff
        new_gray = max(0, new_gray)
        new_gray = min(new_gray, 255)
        gray_map[gray] = new_gray
    return gray_map

Diese Funktion erzeugt eine Abbildung als Array und weist dann jedem Grauwert den Grauwert zu, auf den er abgebildet werden soll. Dazu wird die übergebene Konstante auf den aktuellen Grauwert addiert. Subtraktionen werden durch negative Parameter erreicht. Bevor ein Grauwert gespeichert wird, wird er durch Vergleich mit 0 und 255 auf den Zahlenbereich für Grauwerte eingeschränkt.

Die Prozedur save_with_new_brightness ändert die mittlere Helligkeit eines mit dem gegebenen Namen gespeicherten Bildes auf den übergebenen Wert und speichert es unter einem neuen Namen ab.

def save_with_new_brightness(base_name, new_mean):
    image = Image.open(base_name + '.png')
    gray_hist = gray_histogram(image)
    old_mean = mean_brightness(gray_hist)
    gray_map = brightness_adjustment(new_mean - old_mean)
    change_pixels(image, gray_map)
    image.save(base_name + '_luma' + str(new_mean) + '.png')

Die Prozedur berechnet zunächst ein Histogram und daraus dann die mittlere Helligkeit des Bildes. Aus der Differenz der aktuellen und der übergebenen Helligkeit wird eine Abbildung von Grauwerten berechnet, die die Helligkeit entsprechend anpasst. Diese wird schließlich auf das eingelesene Bild angewendet, bevor es unter einem neuen Namen gespeichert wird.

Auf ähnliche Weise können wir beliebige Abbildungen von Grauwerten berechnen und zur Manipulation von Bildern auf diese anwenden. Zum Beispiel könnten wir die Helligkeit mit Hilfe von Geraden durch einen Eckpunkt verändern oder sogar kurvige Kurven als gray_map darstellen.