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.
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')
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 Listen 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]
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.
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 eine Liste aus 256 Zahlen, einer für jeden Grauwert. Diese Liste füllen wir dann, indem wir alle Pixel durchlaufen, den Grauwert jedes Pixels berechnen und jedesmal die entsprechende Anzahl erhöhen.
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.
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.
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 Liste 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 beliebige Funktionen als gray_map
darstellen.