Weichzeichnen
Alle bisherigen Filter haben einen Bildpunkt angesehen und daraus einen neuen gemacht. Weichzeichnen geht anders: Der neue Wert entsteht aus dem Bildpunkt und seinen acht Nachbarn. Solche Filter heißen Nachbarschaftsoperatoren, und mit ihnen fängt die eigentliche Bildverarbeitung an – Weichzeichnen, Schärfen, Rauschunterdrückung, Kantensuche funktionieren alle nach diesem Muster.
Neun Werte, ein Durchschnitt
┌────┬────┬────┐
│ 90 │ 95 │ 98 │ Der neue Wert in der Mitte ist
├────┼────┼────┤ der Durchschnitt aller neun:
│ 88 │ 40 │ 97 │
├────┼────┼────┤ (90+95+98+88+40+97+91+94+99) / 9 = 88
│ 91 │ 94 │ 99 │
└────┴────┴────┘
Aus der 40 – einem einzelnen dunklen Ausreißer – wird eine 88. Genau das macht Weichzeichnen: Es zieht jeden Wert zu seiner Umgebung hin.
Dieser Filter braucht spürbar länger als die bisherigen – für jeden der 6912 Bildpunkte werden neun Werte geholt. Dreimal hintereinander angewandt, wie im rechten Bild, sind das über 180 000 Zugriffe. Ein paar Zehntelsekunden sind also normal.
Der Rand
Sieh dir das mittlere Bild genau an: Es hat einen schwarzen Rahmen. Die
Schleifen beginnen bei 1 und enden bei hoehe - 1, der äußerste Ring bleibt
also auf 0.
Jeder Nachbarschaftsfilter hat ein Randproblem. Der Bildpunkt oben links hat keinen Nachbarn über sich und keinen links von sich. Es gibt drei übliche Antworten darauf:
- Den Rand auslassen – wie hier. Ehrlich, aber man sieht es.
- Den Rand kopieren – die ursprünglichen Werte unverändert übernehmen.
- Nach außen fortsetzen – für fehlende Nachbarn den nächstgelegenen gültigen Wert nehmen.
Keine davon ist die richtige. Man muss sich entscheiden und wissen, wofür.
Aufgabe 1: Den Rand behandeln
a) Ändere weich so, dass der Rand unverändert übernommen wird (Antwort 2).
Der schwarze Rahmen verschwindet damit.
b) Was ist am Ergebnis von a) unschön, wenn du den Filter dreimal hintereinander anwendest? Sieh genau hin und erkläre es.
c) Setze Antwort 3 um: Schreibe eine Methode wertBei(int[][] pGrau, int pZeile, int pSpalte), die einen Index außerhalb des Bildes auf den nächsten gültigen
zurechtstutzt, und benutze sie in den Schleifen. Die Schleifen dürfen dann
wieder über das ganze Bild laufen.
Tipp zu a)
Bevor die eigentlichen Schleifen beginnen, kopierst du das Original einmal
vollständig nach neu. Danach überschreibst du nur noch die Mitte.
Tipp zu c)
wertBei ist kurz und macht die Schleifen einfacher:
int wertBei(int[][] pGrau, int pZeile, int pSpalte) {
if (pZeile < 0) { pZeile = 0; }
// … drei weitere Fälle
return pGrau[pZeile][pSpalte];
}
In der Schleife steht dann summe = summe + wertBei(pGrau, zeile + dz, spalte + ds);.
Warum es ohne Kopie schiefgeht
Ein naheliegender Gedanke: Wozu ein zweites Gitter? Man könnte den neuen Wert doch direkt ins alte schreiben und Speicher sparen.
Im folgenden Bereich steht genau das: weichOhneKopie schreibt jeden neuen Wert
sofort ins übergebene Gitter zurück, ein zweites gibt es nicht.
Sage vorher, was dabei herauskommt. Dann starte das Programm.
Auflösung
Das Bild wird viel stärker weichgezeichnet, als ein einziger Durchgang es könnte – und zwar ungleichmäßig: Die linke und die obere Kante der LED bleiben schärfer als die rechte und die untere, an der alles verschmiert.
Der Grund: Wenn die Schleife bei Zeile 5, Spalte 5 ankommt, sind die Nachbarn oben und links schon überschrieben. Der Filter rechnet also teils mit Originalwerten, teils mit eigenen Zwischenergebnissen. Und die Zwischenergebnisse enthalten wieder Zwischenergebnisse – der Effekt schaukelt sich über das Bild hinweg auf, in der Richtung, in der die Schleife läuft.
Ein Nachbarschaftsfilter braucht das unveränderte Original. Lesen und Schreiben müssen in verschiedenen Gittern stattfinden.
Das ist kein Sonderfall des Weichzeichnens, sondern die Regel bei jedem Verfahren, das seine Nachbarn ansieht – auch beim Spiel des Lebens oder bei einer Wärmeausbreitungssimulation.
Und noch eine Falle steckt darin: pGrau ist eine Referenz. Die Methode
bekommt nicht eine Kopie des Bildes, sondern den Verweis darauf. Wer hineinschreibt,
verändert das Gitter, mit dem main weiterarbeitet – auch dann, wenn dort noch
das Original erwartet wird. Warum das so ist, steht auf der Seite
Wert und Referenz.
Aufgabe 2: Schärfen
Schärfen ist das Gegenteil von Weichzeichnen – und lässt sich aus ihm bauen. Die Idee: Ziehe vom Original ab, was das Weichzeichnen ihm weggenommen hätte, und lege es noch einmal obendrauf.
scharf = original + (original - weich)
a) Rechne von Hand nach: Was passiert an einer Stelle, an der das Bild ohnehin
gleichmäßig ist, an der also original und weich denselben Wert haben?
b) Schreibe scharf(int[][] pGrau) und probiere es aus.
c) Probiere original + (original - weich) * 2. Ab wann sieht es nicht mehr
schärfer aus, sondern kaputt? Beschreibe, was du siehst.