deutsch     english     français     Drucken

 

3.11 BILDBEARBEITUNG

 

 

EINFÜHRUNG

 

Wir fassen ein Bild als ein ebene, rechteckförmige Fläche auf, auf der sich farbige Formen befinden. In der Druck- und Computertechnik beschreibt man ein Bild durch eine gitterartige Anordnung von farbigen Bildpunkten, auch Pixels genannt. Die Anzahl Bildpunkte pro Flächeneinheit wird Bildauflösung genannt und oft in dots per inch (dpi) angegeben.

Um ein Bild auf dem Computer zu speichern und zu verarbeiten, muss die Farbe als Zahl definiert werden. Es gibt dazu mehrere Möglichkeiten, die man Farbmetriken oder Farbmodelle nennt. Eines der bekanntesten Modelle ist das RGB-Farbmodell, wo die Intensitäten der drei Farbkomponenten für Rot, Grün und Blau als Zahlen zwischen 0 (dunkel) und 255 (hell) angegeben werden [mehr... Dies entspricht auch dem Farbwahrnehmung des menschlichen Auges,
wo drei verschiedene Farbrezeptoren, die L-, M- und S-Zapfen die Rot,
Grün- und Blauanteile messen
]. Im ARGB-Modell wird in einer weiteren Zahl zwischen 0 und 255  ein Mass für die Transparenz (Alpha-Wert) mitgeliefert [mehr... Diese 4 Zahlen lassen sich in eine Dualzahl (32 bit)
mit folgender Anordnung verpacken: bit 0..7: blau,
bit 8..15: grün, bit 16..23: rot, bit 24..31: alpha
].

Zusammengefasst: Ein Computerbild besteht aus einer rechteckigen Anordnung von Pixels, die als Farben codiert sind. Oft spricht man von einer Bitmap.

PROGRAMMIERKONZEPTE: Bilddigitalisierung, Bildauflösung, Farbmodell, Bitmap, Bildformat

 

 

FARBMISCHUNG IM RGB-MODELL

 

TigerJython stellt dir Objekte vom Typ GBitmap zur Verfügung, um dir die Arbeit mit Bitmaps zu erleichtern. Du erzeugst mit bm = GBitmap(width, height) eine Bitmap mit der gewünschten Anzahl horizontaler und vertikaler Bildpunkten. Nachher kannst du mit den Methoden setPixelColor(x, y, color) die Farbe einzelner Pixels setzen und sie mit getPixelColor(x, y) lesen. Mit der Methode image() stellst du schliesslich deine Bitmap in einem GPanel dar. Dein Programm zeichnet die berühmten 3 Farbkreise der additiven Farbmischung, indem du mit einer verschachtelten for-Schleife die Bitmap durchläufst.

 

 
from gpanel import *

xRed = 200
yRed = 200
xGreen = 300
yGreen = 200
xBlue = 250
yBlue = 300

makeGPanel(Size(501, 501))
window(0, 501, 501, 0)    # y axis downwards
bm = GBitmap(500, 500)
for x in range(500):
  for y in range(500):
      red = green = blue = 0
      if (x - xRed) * (x - xRed) + (y - yRed) * (y - yRed) < 16000:
         red = 255
      if (x - xGreen) * (x - xGreen) + (y - yGreen) * (y - yGreen) < 16000:
         green = 255
      if (x - xBlue) * (x - xBlue) + (y - yBlue) * (y - yBlue) < 16000:
         blue = 255
      bm.setPixelColor(x, y, makeColor(red, green, blue))

image(bm, 0, 500)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Farben werden mit ihrem Rot-, Grün- und Blauanteil definiert. makeColor(red, green, blue) setzt diese Farbanteile zu einer Farbe (einem Farbobjekt) zusammen.

Für Bilder wird meist ein Integer-Koordinatensystem mit dem Ursprung in der oberen linke Ecke und nach unten zeigender positiver y-Achse verwendet [mehr... Da das GPanel Float-Koordinaten verwendet, können sich Rundungsfehler ergeben].

 

 

GRAUSTUFENBILD SELBST GEMACHT

 

Du hast dich vielleicht manchmal gefragt, wie deine Bildverarbeitungs-Software (wie Photoshop, o.ä.) funktioniert. Hier lernst du einige einfache Verfahren kennen. Dein Programm macht aus einem Farbbild ein Graustufenbild, indem du den Mittelwert des Rot-, Grün- und Blauanteils bestimmst und diesen zur Definition des Grauwerts verwendest.

from gpanel import *

size = 300

makeGPanel(Size(2 * size, size))
window(0, 2 * size, size, 0)    # y axis downwards
img = getImage("sprites/colorfrog.png")
w = img.getWidth()
h = img.getHeight()
image(img, 0, size)
for x in range(w):
    for y in range(h):
        color = img.getPixelColor(x, y)
        red = color.getRed()
        green = color.getGreen()
        blue = color.getBlue()
        intensity = (red + green + blue) // 3
        gray = makeColor(intensity, intensity, intensity)
        img.setPixelColor(x, y, gray)
image(img, size, size)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Aus einem Farbobjekt kannst du mit den Methoden getRed(), getGreen(), getBlue() die Farbwerte als Integer bestimmen.

Der Hintergrund muss weiss und nicht etwa transparent sein. Willst du die Transparenz berücksichtigen, so kannst du mit alpha = getAlpha() den Transparenzwert bestimmen und diesen in makeColor(red, green, blue, alpha) verwenden.

 

 

WIEDERVERWENDBARKEIT

 

Bei vielen Bildbearbeitungen muss der Benutzer einen Teilbereich des Bildes auswählen. Dazu zieht er mit der Maus ein temporäres Rechteck ("Gummiband-Rechteck"). Beim Loslassen der Maustaste wird der rechteckige Bereich definitiv auswählt. Es ist ratsam, zuerst dieses Teilproblem zu lösen, da dieser Code später in vielen Bildbearbeitungsprogrammen wieder verwendet werden kann. Wiederverwendbarkeit ist ein Gütezeichen für alle Software-Entwicklungen.

Wie du früher gesehen hast, kann man das Zeichnen von Gummiband-Linien als Animation auffassen. Dabei muss aber bei jeder Bewegung das ganze Bild immer wieder neu aufgebaut werden. Ein eleganter Trick, um dies zu vermeiden, ist der XOR-Zeichnungsmodus. In diesem Modus wird eine neue Figur mit der darunter liegenden so verschmolzen, dass durch erneutes Darüberzeichnen die Figur wieder gelöscht wird, ohne dass sich das darunterliegende Bild verändert. Der Nachteil besteht darin, dass beim Zeichnen der Figur die Farben verändert werden. Dies kann man aber im Zusammenhang mit Gummiband-Rechtecken meist in Kauf nehmen.

Das Programmgerüst soll nach der Rechteckwahl lediglich die Funktion doIt() aufrufen und die Koordinaten der oberen linken Ecke ulx (upper left x), uly (upper left y) und der unteren rechten Ecke lrx (lower right  x), lry (lower right y) ausschreiben. Später wirst du deinen Code zur Bildbearbeitung in doIt() einklinken.

Du verstehst den Code auf Grund deiner Kenntnisse im Kapitel Maus-Events ohne grössere Probleme.

 

 
from gpanel import *

size = 300

def onMousePressed(e):
    global x1, y1
    global x2, y2
    setColor("blue")
    setXORMode(Color.white) # set XOR paint mode
    x1 = x2 = e.getX()
    y1 = y2 = e.getY()

def onMouseDragged(e):
    global x2, y2
    rectangle(x1, y1, x2, y2) # erase old
    x2 = e.getX()
    y2 = e.getY()
    rectangle(x1, y1, x2, y2) # draw new

def onMouseReleased(e):
    rectangle(x1, y1, x2, y2) # erase old
    setPaintMode() # establish normal paint mode
    ulx = min(x1, x2)
    lrx = max(x1, x2)
    uly = min(y1, y2)
    lry = max(y1, y2)
    doIt(ulx, uly, lrx, lry)

def doIt(ulx, uly, lrx, lry):
    print "ulx = ", ulx, "uly = ", uly
    print "lrx = ", lrx, "lry = ", lry
    
x1 = y1 = 0
x2 = y2 = 0

makeGPanel(Size(size, size), 
    mousePressed = onMousePressed, 
    mouseDragged = onMouseDragged, 
    mouseReleased = onMouseReleased)
window(0, size, size, 0)    # y axis downwards

img = getImage("sprites/colorfrog.png")
image(img, 0, size)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Du holst dir die Bitmap für eine Bild, das du auf dem Computer gespeichert hast mit getImage(), wobei du den vollständig qualifizierten Dateinamen oder auch nur einen Teil des Pfades relativ zum Verzeichnis, in dem sich dein Programm befindet, angeben musst. Für Bilder, die sich in der Distribution befinden, verwendest du den Verzeichnisnamen sprites.

Beim Press-Event setzst du das System in den XOR-Modus, damit du beim Drag-Event mit zweimaligem Zeichnen zuerst das alte Rechteck löschen und dann das neue zeichnen kannst. Dazu musst du die Eckpunkte in den globalen Werten x1, y1, x2, y2 abspeichern. Wenn du beim Release-Event das Gummiband-Rechteck nochmals zeichnest, bevor du in den Paint-Mode wechselst, so wird das Rechteck verschwinden. Wenn du aber zuerst in den Paint-Mode wechselt, bleibt es erhalten.

Das Programm funktioniert unabhängig davon, wie du das Rechteck ziehst. Es liefert immer die richtigen Werte für ulx,uly und lrx, lry (immer ulx < lrx, uly < lry). Beachte, dass du die Mauskoordinaten nicht auf Window-Koordinaten umrechnen musst, da beide gleich gross sind, wenn du bei der Fenstergrösse mit Size() und dem Koordinatensystem mit window() dieselbe Werte verwendest.

Du kriegst auch Drag-Events, wenn du die Maus aus dem Fenster ziehst. Du musst aufpassen, was du mit solchen Koordinaten machst, sonst kann das Programm unerwartet crashen.

 

 

ROTAUGEN-EFFEKT

 

Die Bildbearbeitung spielt für die Nachbearbeitung von digitalen Fotos eine zentrale Rolle. Es gibt im Internet zahlreiche Nachbearbeitungsprogramme. Du brauchst aber keinen allzu grossen Respekt vor dieser Software zu haben, denn du kannst nun mit Python und einem gesunden Mass an Fantasie und Durchhaltewillen eigene Programme schreiben, die deinen Bedürfnissen besser angepasst sind. Deine Aufgabe besteht nachfolgend darin, ein Programm zu schreiben, dass den Rotaugen-Effekt  beheben kann. Dieser tritt auf, wenn bei der Aufnahme das Blitzlicht am Augenhintergrund (fundus) reflektiert wird. Als Bild verwendest du hier ein Froschbild, da dieses auch noch andere rote Stellen aufweist.

 
from gpanel import *

size = 300

def onMousePressed(e):
    global x1, y1
    global x2, y2
    setColor("blue")
    setXORMode("white")
    x1 = x2 = e.getX()
    y1 = y2 = e.getY()

def onMouseDragged(e):
    global x2, y2
    rectangle(x1, y1, x2, y2) # erase old
    x2 = e.getX()
    y2 = e.getY()
    rectangle(x1, y1, x2, y2) # draw new

def onMouseReleased(e):
    rectangle(x1, y1, x2, y2) # erase old
    setPaintMode()
    ulx = min(x1, x2)
    lrx = max(x1, x2)
    uly = min(y1, y2)
    lry = max(y1, y2)

    doIt(ulx, uly, lrx, lry)    

def doIt(ulx, uly, lrx, lry):
    for x in range(ulx, lrx):
        for y in range(uly, lry):
            col = img.getPixelColor(x, y)
            red = col.getRed()
            green = col.getGreen()
            blue = col. getBlue()
            col1 = makeColor(3 * red // 4, green, blue)
            img.setPixelColor(x, y, col1)
    image(img, 0, size)
        
x1 = y1 = 0
x2 = y2 = 0

makeGPanel(Size(size, size), 
    mousePressed = onMousePressed, 
    mouseDragged = onMouseDragged, 
    mouseReleased = onMouseReleased)
window(0, size, size, 0)    # y axis downwards

img = getImage("sprites/colorfrog.png")
image(img, 0, size)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Der Code zur Bildbearbeitung wird in die Funktion doIt() eingeklinkt. Alles andere übernimmst du unverändert vom vorhergehenden Programm. Du kannst den Abschwächungsgrad für die rote Farbe anpassen. Hier wird die Rotintensität auf 75% hinuntergesetzt. Beachte den doppelten Bruchstrich, der eine Integer-Division durchführt (der Divisionsrest wird vernachlässigt). Das Resultat ist wieder ein Integer, wie es auch sein muss.

Das Programm zeigt noch einige Fehlerhalten, die du leicht beheben kannst. Zum einen verfärbt es auch nicht rote Gebiete, zum anderen schmiert es ab, wenn du das Gummiband-Rechteck aus dem Fenster ziehst.

Es wäre natürlich nett, wenn das Programm die roten Augen selbst finden würde. Dazu müsste es aber das Bild analysieren und Bildinhalte automatisch erkennen, was ein besonders schwieriges Problem der Informatik ist [mehr... Bilderkennung ist ein Teilgebiet der hochaktuellen Mustererkennung].

 

 

BILDER AUSSCHNEIDEN UND ABSPEICHERN

 

Das Ausschneiden von Bildteilen gehört ebenfalls zu den Grundfunktionen von Bildverarbeitungs- programmen. Dein Programm kann nicht nur einen Teil, den du mit einem Gummiband-Rechteck auswählst, in ein anderes Fenster kopieren, sondern dieses Bild auch als JPEG-Datei zur späteren Wiederverwendung abspeichern.


 

from gpanel import *

size = 300

def onMousePressed(x, y):
    global x1, y1
    global x2, y2
    setColor("blue")
    setXORMode("white")
    x1 = x2 = int(x)
    y1 = y2 = int(y)

def onMouseDragged(x, y):
    global x2, y2
    rectangle(x1, y1, x2, y2) # erase old
    x2 = int(x)
    y2 = int(y)
    rectangle(x1, y1, x2, y2) # draw new

def onMouseReleased(x, y):
    rectangle(x1, y1, x2, y2) # erase old
    setPaintMode()
    ulx = min(x1, x2)
    lrx = max(x1, x2)
    uly = min(y1, y2)
    lry = max(y1, y2)
    doIt(ulx, uly, lrx, lry)    

def doIt(ulx, uly, lrx, lry):
    width = lrx - ulx
    height = lry - uly
    if ulx < 0 or uly < 0 or lrx > size or lry > size:
        return
    if width < 20 or height < 20:
        return
    
    cropped = GBitmap.crop(img, ulx, uly, lrx, lry)
    p = GPanel(Size(width, height))  # another GPanel
    p.window(0, width, 0, height)
    p.image(cropped, 0, 0)
    rc = save(cropped, "mypict.jpg", "jpg") 
    if rc:
        p.title("Saving OK")
    else:
        p.title("Saving Failed")

    
x1 = y1 = 0
x2 = y2 = 0

makeGPanel(Size(size, size), 
    mousePressed = onMousePressed, 
    mouseDragged = onMouseDragged, 
    mouseReleased = onMouseReleased)
window(0, size, size, 0)    # y axis downwards

img = getImage("sprites/colorfrog.png")
image(img, 0, size)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Du kannst bei Bedarf mehrere GPanel-Fenster anzeigen, indem du GPanel-Objekte erzeugst. Zum Zeichnen verwendest du die Grafikbefehle, die du mit dem Punktoperator aufrufst.

Falls der gewählte Ausschnitt zu klein ist (insbesondere wenn man ohne zu Ziehen mit der Maus klickt), so kehrt doIt() mit einem return unverrichteter Dinge zurück, ebenfalls wenn die Eckpunkte nicht im Bildbereich liegen.

Zum Abspeichern wird die Methode save() verwendet, die als letzten Parameter das Bildformat festlegt. Erlaubt sind die Werte:  "bmp", "gif", "jpg", "png".

 

 

AUFGABEN

 

1.


Schreibe ein Programm, das die Rot- und Grünanteile des Bildes colorfrog.png vertauscht.

 


2.


Schreibe ein Programm, wo du durch Ziehen mit der Maus das Bild drehen kannst. Verwende die Funktion atan2(y, x), die dir den Winkel α zum Punkt P(x, y) liefert. Du musst diesen noch mit math.degrees() in Grad umwandeln, bevor du mit GBitmap.scale() das Bild drehst.

 

 

Als Testbild kannst du wieder colorfrog.png nehmen.

 



3.


Schreibe ein Retouchier-Programm, dass bei einem Mausklick die Farbe des Pixels speichert (color pick). Nachfolgendes Ziehen soll mit so gefüllten Farbkreisen in das Bild hineinzeichnen. Du musst hier den Press-, Drag- und Click-Event verwenden. Als Testbild kannst du wieder colorfrog.png verwenden. Schreibe die 3 Farbkomponenten der "gepickten" Farbe in die Titelzeile des Fensters.

 

 

 

ZUSATZSTOFF: BILDFILTERUNG DURCH FALTUNG

 

Dir ist sicher bekannt, dass man in den bekannten Bildverarbeitungsprogrammen ein Bild durch verschiedenartige Filter verändern kann. Es gibt beispielsweise Glättungsfilter, Schärfungsfilter, Weichzeichnungsfilter, usw. Dabei kommt das wichtige Prinzip der Faltung zur Anwendung, das du hier kennen lernst [mehr... Faltung (Convolution) ist ein Prinzip, dass vielfach in der Mathematik
und in den Natur- und Ingenieurwissenschaften zur Anwendung kommt
]. Man verändert dabei die Farbwerte jedes Bildpixels, indem man gemäss einer Filterregel einen neuen Wert aus ihm und seinen nächsten 8 Nachbarn berechnet.

Im Detail funktioniert dies wie folgt: Gehe der Einfachheit halber von einem Graustufenbild aus, wo jeder Pixel im RGB-Farbmodell einen Grauwert v zwischen 0 und 255 besitzt.  Die Filterregel wird durch 9 Zahlen festgelegt, die sich in einem Quadrat anordnen lassen:

m00   m01   m02
m10   m11   m12
m20   m21   m22

Diese Darstellung nennt sich Faltungsmatrix (auch Maske genannt). In Python implementieren wir sie zeilenweise in einer Liste:
mask = [[0, -1, 0], [-1, 5, 1], [0, -1, 0]]

Mit dieser Datenstruktur kannst du leicht mit Doppel- indizes auf die einzelnen Werte zugreifen, beispielsweise ist m12 = mask[1][2] = 1. Diese neun Zahlen sind Gewichtsfaktoren für einen Bildpunkt und seine 8 Nachbarn. Man berechnet nun den neuen Grauwert vnew eines Bildpunkts an der Stelle x, y aus den vorhandenen 9 Werten v(x, y) wie folgt:

 

vnew(x, y) = m00 * v(x - 1, y -1) + m01 * v(x, y - 1) + m02 * v(x + 1, y - 1) +
  m10 * v(x - 1, y) + m11 * v(x, y) + m12 * v(x + 1, y) +
  m20 * v(x - 1, y + 1) + m21 * v(x , y + 1) + m22 * v(x + 1, y + 1)

Anschaulich könnte man sagen, dass man die Faltungsmatrix für die Neuberechnung über den Bildpunkt legt, ihre Werte mit den darunter liegenden Grauwerten multipliziert und aufsummiert.

Das Programm führt diese Faltungsoperation auf alle Bildpunkte (ausser den Randpunkten) aus und speichert die neuen Grauwerte in einer neuen Bitmap, die dann dargestellt wird. Dazu bewegst du sozusagen mit einer for-Struktur die Faltungsmatrix zeilenweise von links nach rechts und von oben nach unten über das Bild. Du verwendest  hier die Faltungsmatrixwerte eines Scharfzeichnungsfilters und das Graustufenbild frogbw.png des Frosches.

from gpanel import *

size = 300

makeGPanel(Size(2 * size, size))
window(0, size, size, 0)    # y axis downwards

bmIn = getImage("sprites/frogbw.png")
image(bmIn, 0, size)
w = bmIn.getWidth()
h = bmIn.getHeight()
bmOut = GBitmap(w, h)

#mask = [[1/9, 1/9, 1/9], [1/9, 1/9, 1/9], [1/9, 1/9, 1/9]]  # smoothing
mask = [[ 0, -1,  0], [-1,  5, -1], [0,  -1,  0]] #sharpening
#mask = [[-1, -2, -1], [ 0,  0,  0], [ 1,  2,  1]] #horizontal edge extraction
#mask = [[-1,  0,  1], [-2,  0,  2], [-1,  0,  1]] #vertical edge extraction

for x in range(0, w):
    for y in range(0, h):
        if x > 0 and x < w - 1 and y > 0 and y < h - 1:
            vnew = 0
            for k in range(3):
                for i in range(3):
                    c = bmIn.getPixelColor(x - 1 + i, y - 1 + k)
                    v = c.getRed()
                    vnew +=  v * mask[k][i]
            # Make int in 0..255        
            vnew = int(vnew)
            vnew = max(vnew, 0)
            vnew = min(vnew, 255)
            gray = Color(vnew, vnew, vnew)
        else:
            c = bmIn.getPixelColor(x, y)
            v = c.getRed()
            gray = Color(v, v, v)
        
        bmOut.setPixelColor(x, y, gray)

image(bmOut, size / 2, size)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Bei der Faltung wird jeder Bildpunkt durch ein gewichtetes Mittel aus sich und den Nachbarpunkten ersetzt. Die Filterart wird durch die Faltungsmatrix festgelegt.

 

Du kannst mit den folgenden bekannten Faltungsmatrizen experimentieren, aber auch eigene erfinden.

Filterart

Faltungsmatrix

Scharfzeichnungsfilter

Weichzeichnungsfilter

Kantenfilter (horizontal)

Kantenfilter (vertikal)