Bildbearbeitung#
Was sind Bilder#
Bilder bestehen aus winzig kleinen Pixeln, die alle eine bestimmte Farbe haben:
Bildbearbeitung besteht darin, diese Pixel nach bestimmten Kriterien zu verändern und zu manipulieren.
Dazu benötigen wir Arrays
die eine spezielle Form von Listen sind (Arrays haben begrenze Größen).
Laden des Hintergrunds#
Im miniworlds können wir den Hintergrund
mit der Funktion
arr = background.to_colors_array()
laden.
Wenn wir z.B. dieses minimale Programm schreiben:
from miniworlds import *
world = World()
arr = world.background.to_colors_array()
print(arr)
world.run()
, dann bearbeiten wir im folgenden den Standard-Hintergrund:
Du erhälst ein verschachteltes, zweidimensionales Array, das folgendermaßen aussieht:
[[[150 150 150]
[150 150 150]
[150 150 150]
...
[150 150 150]
[150 150 150]
[150 150 150]]
[[150 150 150]
[150 150 150]
[150 150 150]
...
[150 150 150]
[150 150 150]
[150 150 150]]
[[150 150 150]
[150 150 150]
[150 150 150]
...
[150 150 150]
[150 150 150]
[150 150 150]]
...
[[150 150 150]
[150 150 150]
[150 150 150]
...
[150 150 150]
[150 150 150]
[150 150 150]]
[[150 150 150]
[150 150 150]
[150 150 150]
...
[150 150 150]
[150 150 150]
[150 150 150]]
[[150 150 150]
[150 150 150]
[150 150 150]
...
[150 150 150]
[150 150 150]
[150 150 150]]]
Die innerste Liste steht jeweils für ein 3-Tupel an Farben. [150, 150, 150].
Diese beschreiben jeweils den rot, grün und blau-Anteil des jeweiligen Pixels. Jede Farbe wird durch “Mischen” dieser 3 Grundfarben erzeugt:
Der Minimalwert für jede Farbe ist 0, der Maximalwert ist 255.
Das Bild-Array besteht:
Aus einer Liste von Spalten
und jede dieser Spalten enthält für jede Zeile einen Farbwert (der selbst wiederrum eine Liste mit 3 Werten ist)
Verändern des Hintergrunds#
Man kann über dieses Array folgendermaßen iterieren:
from miniworlds import *
grey
world = World()
arr = world.background.to_colors_array()
for x in range(len(arr)): # iterate over all rows
for y in range(len(arr[0])): # iterate over all columns of row
arr[x][y][0] = 0
print(arr)
world.run()
Die Zählervariable x iteriert über die Spalten und wählt jeweils eine Spalte aus. Über jede ausgewählte Spalte wird nun mit der Zählervariablen y iteriert:
Mit der Anweisung arr[i][j][0] = 0
wird jeweils die erste Farbe, also der rot-Anteil auf 0 gesetzt.
Das Array sieht folgendermaßen aus:
[[[ 0 150 150]
[ 0 150 150]
[ 0 150 150]
...
[ 0 150 150]
[ 0 150 150]
[ 0 150 150]]
[[ 0 150 150]
[ 0 150 150]
[ 0 150 150]
...
[ 0 150 150]
[ 0 150 150]
[ 0 150 150]]
...
[[ 0 150 150]
[ 0 150 150]
[ 0 150 150]
...
[ 0 150 150]
[ 0 150 150]
[ 0 150 150]]]
Dieses Array können wir nun wieder mit dem Befehl background.from_array(arr)
als Hintergrund laden, so sieht das vollständige Programm aus:
from miniworlds import *
world = World()
arr = world.background.to_colors_array()
for x in range(len(arr)):
for y in range(len(arr[0])):
arr[x][y][0] = 0
world.background.from_array(arr)
world.run()
…und so sieht das Ergebnis aus. Die Farbe Grau verliert ihre rot-Anteile und wird dadurch grün-bläulich:
Hier haben wir einfach jedem Bildpunkt den rot-Wert 0 zugewiesen. Wir können aber auch jedem Bildpunkt einen Wert zwischen 0 und 255 zuweisen.
Man kann auch anders über diese Liste iterieren und z.B. nur jede zweite Zeile färben:
from miniworlds import *
world = World()
arr = world.background.to_colors_array()
for x in range(0, len(arr),2 ):
for y in range(len(arr[0])):
arr[x][y][0] = 0
print(arr)
world.background.from_array(arr)
world.run()
Genauso ist es möglich, den Wert Abhängig von der Zählervariablen i zu verwenden - Auf diese Weise kann man Farbübergänge erzeugen, z.B. so :
from miniworlds import *
world = World()
arr = world.background.to_colors_array()
print(arr)
for x in range(len(arr)):
for y in range(len(arr[0])):
arr[x][y][0] = ((x +1 ) / world.width) * 255
world.background.from_array(arr)
world.run()
Mit zunehmenden x-Wert steigt der x-Wert an. (x+1) / world.width ergibt einen Wert zwischen 0 und 1. Multipliziert man diesen mit 255 erhält man einen Wert zwischen 0 und 255. Ist ein Wert ganz links, ist sein rot-Wert minimal. Ist es ganz rechts, ist der rot-Wert maximal.
Man erhält folgenden Farbübergang.
Dies kann man auch mit der y-Variable machen, und das Programm wie folgt erweitern:
from miniworlds import *
world = World()
arr = world.background.to_colors_array()
for x in range(len(arr)):
for y in range(len(arr[0])):
arr[x][y][1] = ((y +1 ) /world.width) * 255
world.background.from_array(arr)
world.run()
Oben ist der grün-Wert minimal, unten ist er maximal:
Man kann dies nun zusammensetzen:
from miniworlds import *
world = World()
arr = world.background.to_colors_array()
for x in range(len(arr)):
for y in range(len(arr[0])):
arr[x][y][0] = ((x +1 ) / world.width) * 255
arr[x][y][1] = ((y +1 ) /world.width) * 255
world.background.from_array(arr)
world.run()
Man erhält diesen Farbübergang:
Bildbearbeitung#
Bisher haben wir nun einen einfarbigen Hintergrund bearbeitet, aber genau dies geht natürlich auch mit einem Bild als Hintergrund.
Auf diese Weise können wir verschiedene Filter über das Bild legen.
Wir laden z.B. die Sonnenblume von oben als Hintergrundbild:
from miniworlds import *
world = World(600,400)
world.add_background("images/sunflower.jpg")
arr = world.background.to_colors_array()
for x in range(len(arr)):
for y in range(len(arr[0])):
arr[x][y][0] = 0
world.background.from_array(arr)
world.run()
Da die rot-Werte aus dem Bild entfernt werden, erhält das Bild einen gründlichen Farbton. Wir haben hier also einen ersten Farbfilter geschrieben. So sieht das Ergebnis aus:
Als nächstes manipulieren wir die Helligkeit. Dazu können wir sowohl den rot, grün als blau-Wert mit einer Konstante multiplizieren.
from miniworlds import *
world = World(600,400)
world.add_background("images/sunflower.jpg")
arr = world.background.to_colors_array()
constant = 2
for x in range(len(arr)):
for y in range(len(arr[0])):
arr[x][y][0] = arr[x][y][0] * constant
arr[x][y][1] = arr[x][y][1] * constant
arr[x][y][2] = arr[x][y][2] * constant
world.background.from_array(arr)
world.run()
Der erste Versuch sieht allerdings so aus!
Wie ist dies passiert?
Jeder Farbton hat einen Wert zwischen 0 und 255, beim Multiplizieren wurden allerdings einige unserer Werte größer als 255 und sind daher “übergelaufen”. Du erkennst dies an besonders dunklen Bereichen, die eigentlich hell sein sollten.
Wir müssen also sicherstellen, dass das Ergebnis kleiner als 255 ist, z.B. so:
from miniworlds import *
world = World(600,400)
world.add_background("images/sunflower.jpg")
arr = world.background.to_colors_array()
constant = 2
for x in range(len(arr)):
for y in range(len(arr[0])):
arr[x][y][0] = min(arr[x][y][0] * constant, 255)
arr[x][y][1] = min(arr[x][y][1] * constant, 255)
arr[x][y][2] = min(arr[x][y][2] * constant, 255)
world.background.from_array(arr)
world.run()
Bildbearbeitung II (mit Funktionen)#
Helligkeit#
Oft benötigen wir die Helligkeit eines Pixels. Die “einfachste Methode dies zu berechnen ist den Durchschnitt der r,g und b-Werte zu berechnen:
from miniworlds import *
world = World(600,400)
world.add_background("images/sunflower.jpg")
arr = world.background.to_colors_array()
def brightness(r, g, b):
return (int(r) + int(g) + int(b)) / 3
print(brightness(arr[10][20]))
world.background.from_array(arr)
world.run()
In der Funktion brightness müssen die Werte r, g und b zunächst umgewandelt werden:
Es handelt sich um uint8
-Werte, so dass das Ergebnis niemals größer als 255 werden darf (Ansonsten ensteht ein “Overflow”). Deshalb müssen die Variablen in den Datentyp int
umgewandelt werden, damit das Ergebnis der Addition auch ein int
-Wert und damit beliebig groß ist.
Dies können wir nutzen, um jeden Pixel grau zu färben, abhängig von seiner Helligkeit:
from miniworlds import *
world = World(600,400)
world.add_background("images/sunflower.jpg")
arr = world.background.to_colors_array()
def brightness(r, g, b):
return (int(r) + int(g) + int(b)) / 3
for x in range(len(arr)):
for y in range(len(arr[0])):
arr[x][y] = brightness(arr[x][y][0], arr[x][y][1], arr[x][y][2])
world.background.from_array(arr)
world.run()
Farbunterschied und wahrgenommener Farbunterschied#
Beim Arbeiten mit Bildern und Tönen ist es wichtig zu wissen, wie wir Menschen diese wahrnehmen.
Die wahrgenommene Helligkeit entspricht nicht der hier berechneten Helligkeit. Für die folgenden Zwecke ist die hier verwendete Form zur Berechnung der Helligkeit aber ausreichend.
Kantenerkennung#
Eine wichtige Funktion in der Bildbearbeitung ist das Erkennen von Kanten. Auch in der künstlichen Intelligenz ist dies wichtig, weil Kantenerkennung ein erster Schritt ist, um Objekte in einem Bild zu erkennen.
Wie funktioniert Kantenerkennung?#
Helferfunktionen#
Um uns auf den eigentlichen Algorithmus zu konzentrieren, verwenden wir einige Helfer-Funktionen:
Ist ein Bildpunkt im Bild?:
def in_array(arr, x, y):
if x >= 0 and x < len(arr):
if y >= 0 and y < len(arr[0]):
return True
return False
Gebe alle Nachbarzellen eines Bildpunktes zurück:
def neighbour_cells(arr, x, y):
neighbours = []
for x0 in range(x-1, x+1):
for y0 in range(y-1, y+1):
if in_array(arr, x0, y0):
neighbours.append(arr[x0][y0])
return neighbours
Wir bereiten das Bild vor. Mit `arr.copy()`` können wir eine Kopie des Bildes erstellen, in der wir nur die Helligkeitswerte speichern.
Auf diese Weise müssen wir später nicht mit allen drei Farbwerten rechnen.
So sieht das Grundgerüst aus:
from miniworlds import *
world = World(600,400)
world.add_background("images/sunflower.jpg")
arr = world.background.to_colors_array()
grey_arr = arr.copy()
def brightness(r, g, b):
return (int(r) + int(g) + int(b)) / 3
def in_array(arr, x, y):
if x >= 0 and x < len(arr):
if y >= 0 and y < len(arr[0]):
return True
return False
def neighbour_cells(arr, x, y):
neighbours = []
for x0 in range(x-1, x+1):
for y0 in range(y-1, y+1):
if in_array(arr, x0, y0):
neighbours.append(arr[x0][y0])
return neighbours
for x in range(len(arr)):
for y in range(len(arr[0])):
grey_arr[x][y] = brightness(arr[x][y][0], arr[x][y][1], arr[x][y][2])
world.background.from_array(arr)
world.run()
und jetzt ergänzen wir noch den Algorithmus zur Kantenerkennung:
Der Algorithmus funktioniert so: Wir berechnen mit Hilfe der neighbour_cells-Funktion den durchschnittlichen Helligkeitswert aller Nachbarzellen und färben das Bild entsprechend ein.
from miniworlds import *
world = World(600,400)
world.add_background("images/sunflower.jpg")
arr = world.background.to_colors_array()
grey_arr = arr.copy()
def brightness(r, g, b):
return (int(r) + int(g) + int(b)) / 3
def in_array(arr, x, y):
if x >= 0 and x < len(arr):
if y >= 0 and y < len(arr[0]):
return True
return False
def neighbour_cells(arr, x, y):
neighbours = []
for x0 in range(x-1, x+1):
for y0 in range(y-1, y+1):
if in_array(arr, x0, y0):
neighbours.append(arr[x0][y0])
return neighbours
for x in range(len(arr)):
for y in range(len(arr[0])):
grey_arr[x][y] = brightness(arr[x][y][0], arr[x][y][1], arr[x][y][2])
for x in range(len(arr)):
for y in range(len(arr[0])):
neighbours = neighbour_cells(grey_arr, x, y)
sum_neighbours = 0
for neighbour in neighbour_cells(grey_arr, x, y):
sum_neighbours += neighbour[0]
mean_neighbours = sum_neighbours / len(neighbours)
diff = grey_arr[x][y][0] - mean_neighbours
arr[x][y] = (diff, diff, diff)
world.background.from_array(arr)
world.run()
Die Farben sind noch invertiert, d.h. wir brauchen eine Funktion, die die Farben erneut invertiert, so dass der schwarze Hintergrund weiß wird und die Kanten statt weiß schwarz werden.
Dies geht so:
for x in range(len(arr)):
for y in range(len(arr[0])):
arr[x][y][0] = 255 - arr[x][y][0]
arr[x][y][1] = 255 - arr[x][y][1]
arr[x][y][2] = 255 - arr[x][y][2]
Diesen Algorithmus können wir noch auf verschiedene Art verändern verbessern. Eine Möglichkeit ist es, nur Zellen weiß zu färben, die einen gewissen Schwellenwert überschreiten und alle anderen Zellen schwarz, man könnte auch das Feld der Nachbarzellen größer gestalten, …
Probiere es aus!