Zum Inhalt springen
Vorschau auf das nächste Semester, noch nicht veröffentlicht.
Zur aktuellen Seite

Filter

Harvard Hof in Graustufen

Aufgabe

So funktionieren die vier Filter

filter.c liest das Bild in ein zweidimensionales Array image mit height Zeilen und width Spalten und übergibt es Ihren Funktionen. Jede Funktion ändert die Pixel direkt in image.

  1. Pixel: image[i][j] ist der Pixel in Zeile i und Spalte j; image[0][0] ist die Ecke oben links. Jeder Pixel ist ein RGBTRIPLE mit drei Farbwerten rgbtRed, rgbtGreen und rgbtBlue, jeweils eine ganze Zahl von 0 bis 255.
  2. Farben: Alle drei Werte 0 ergeben Schwarz, alle drei 255 Weiß. Sind alle drei Werte gleich, ist der Pixel grau – je höher der Wert, desto heller.
  3. Graustufen (grayscale): Alle drei Farbwerte werden durch den Mittelwert der ursprünglichen drei Werte ersetzt, gerundet auf die nächste ganze Zahl. So bleibt jeder Pixel etwa so hell wie vorher.
  4. Sepia (sepia): Die neuen Werte berechnen sich aus den ursprünglichen Werten RR, GG, BB des Pixels: Rot=0.393⋅R+0.769⋅G+0.189⋅BGru¨n=0.349⋅R+0.686⋅G+0.168⋅BBlau=0.272⋅R+0.534⋅G+0.131⋅B \begin{aligned} \text{Rot} &= 0.393 \cdot R + 0.769 \cdot G + 0.189 \cdot B \\ \text{Grün} &= 0.349 \cdot R + 0.686 \cdot G + 0.168 \cdot B \\ \text{Blau} &= 0.272 \cdot R + 0.534 \cdot G + 0.131 \cdot B \end{aligned} Jedes Ergebnis wird auf die nächste ganze Zahl gerundet; liegt es über 255, wird es auf 255 gesetzt.
  5. Spiegeln (reflect): In jeder Zeile tauschen die Pixel links und rechts die Plätze: Spalte 0 mit Spalte width - 1, Spalte 1 mit Spalte width - 2 und so weiter. Bei ungerader Breite bleibt der mittlere Pixel stehen.
  6. Weichzeichnen (blur): Jeder Farbwert wird durch den Mittelwert desselben Farbwerts aller Pixel im 3×3-Kasten um den Pixel ersetzt, der Pixel selbst eingeschlossen, gerundet auf die nächste ganze Zahl. Für Pixel 6 im Raster unten sind das die Pixel 1, 2, 3, 5, 6, 7, 9, 10 und 11.
  7. Rand beim Weichzeichnen: Am Rand und in den Ecken zählen nur die Pixel des Kastens, die im Bild liegen: am Rand 6, in einer Ecke 4. Für Pixel 15 sind das 10, 11, 12, 14, 15 und 16.
  8. Originalwerte beim Weichzeichnen: Gemittelt wird immer über die ursprünglichen Farbwerte, nicht über Werte, die Ihr Programm schon weichgezeichnet hat.

Raster aus 16 nummerierten Pixeln, 3×3-Kasten um Pixel 6

Beispiel Graustufen und Sepia für einen Pixel mit Rot 220, Grün 190, Blau 140:

FilterRechnungErgebnis
Graustufen(220+190+140)/3=183.33(220 + 190 + 140) / 3 = 183.33alle drei Werte 183
Sepia, Rot0.393⋅220+0.769⋅190+0.189⋅140=259.030.393 \cdot 220 + 0.769 \cdot 190 + 0.189 \cdot 140 = 259.03259, gedeckelt: 255
Sepia, Grün0.349⋅220+0.686⋅190+0.168⋅140=230.640.349 \cdot 220 + 0.686 \cdot 190 + 0.168 \cdot 140 = 230.64231
Sepia, Blau0.272⋅220+0.534⋅190+0.131⋅140=179.640.272 \cdot 220 + 0.534 \cdot 190 + 0.131 \cdot 140 = 179.64180

Beispiel Weichzeichnen für Pixel 2 am oberen Rand. Sein Kasten umfasst nur die Pixel 1, 2, 3, 5, 6 und 7. Deren Rotwerte:

ZeileSpalte 0Spalte 1Spalte 2
0Pixel 1: 100Pixel 2: 120Pixel 3: 90
1Pixel 5: 80Pixel 6: 110Pixel 7: 105

Neuer Rotwert von Pixel 2: (100+120+90+80+110+105)/6=605/6=100.83(100 + 120 + 90 + 80 + 110 + 105) / 6 = 605 / 6 = 100.83, gerundet 101. Grün und Blau rechnen Sie genauso.

Den Umgang mit image üben Warm-up 3 (Pixel lesen und ändern) und Warm-up 4 (Nachbarpixel ohne Griff über den Bildrand). Wie eine BMP-Datei aufgebaut ist, steht am Ende der Seite unter „Zum Weiterlesen“.

Demo

Aufgabenmaterial

Für diese Aufgabe werden Sie ein von uns zur Verfügung gestelltes Codegerüst vervollständigen.

Aufgabenmaterial herunterladen

Öffnen Sie VS CodeVisual Studio Code, der kostenlose Code-Editor von Microsoft, in dem Sie im Kurs programmieren. Glossar → entsprechend Ihrem Setup.

Öffnen Sie Ihr TerminalfensterFenster, in dem Sie dem Computer Befehle als Text eintippen statt zu klicken. In VS Code liegt es im unteren Bereich des Fensters. Glossar → und führen Sie dann cdWechselt das Verzeichnis: cd me geht in den Ordner me, cd .. eine Ebene nach oben, cd allein ins Homeverzeichnis. Glossar → aus. Die EingabeaufforderungDas Zeichen am Anfang der Zeile im Terminal, etwa $ oder me/ $. Es zeigt, dass das Terminal auf Ihren nächsten Befehl wartet. Steht davor ein Ordnername wie me/, befinden Sie sich gerade in diesem Ordner. Glossar → Ihres Terminalfensters sollte wie folgt aussehen:

$

Geben Sie dann

wget https://dev.inf.zone/download/exercises/06/filter.zip

ein und führen Sie den Befehl mit der Eingabetaste aus, um eine ZIP-Datei namens filter.zip in den aktuellen Ordner herunterzuladen. Achten Sie darauf, dass Sie das Leerzeichen zwischen wgetLädt eine Datei aus dem Internet in das aktuelle Verzeichnis herunter, im Kurs zum Beispiel das Aufgabenmaterial einer Übung. Glossar → und der folgenden URL nicht übersehen, und auch kein anderes Zeichen!

Führen Sie jetzt

unzip filter.zip

aus, um das ZIP-Archiv in einen Ordner namens filter zu extrahieren. Sie brauchen die ZIP-Datei nicht mehr, also können Sie

rm filter.zip

ausführen. Antworten Sie mit “y”, gefolgt von der Eingabetaste, um die heruntergeladene ZIP-Datei zu entfernen.

Führen Sie dann

cd filter

aus, um in dieses Verzeichnis zu wechseln. Ihre Eingabeaufforderung sollte nun wie folgt aussehen:

filter/ $

Wenn alles wie beschrieben funktioniert hat, sollten Sie nach dem Ausführen von

ls

mehrere Dateien sehen: bmp.h, filter.c, helpers.h, helpers.c, und Makefile. Sie sollten auch einen Ordner images/ mit vier BMP-Dateien sehen. Wenn diese Dateien nicht angezeigt werden, führen Sie die gleichen Schritte noch einmal durch und schauen Sie, ob Sie herausfinden können, wo Sie einen Fehler gemacht haben!

Überblick über das Codegerüst

Sie müssen filter.c nicht vollständig verstehen. Wichtig ist, was Ihre Funktionen übergeben bekommen.

DateiInhalt
bmp.hDas struct RGBTRIPLE mit den Feldern rgbtBlue, rgbtGreen, rgbtRed (in dieser Reihenfolge) sowie die beiden Header-structs der BMP-Datei. Der Typ BYTE ist nur ein anderer Name für uint8_t, also ein Byte (0 bis 255).
filter.cFertig. Liest den Filter-Buchstaben (die erlaubten stehen in Zeile 10: b, g, r, s), öffnet die Bilddatei, prüft, ob es eine 24-Bit-BMP ist, und liest alle Pixel in das 2D-Array image. Die switch-Anweisung ab Zeile 101 ruft je nach Buchstabe blur, grayscale, reflect oder sepia auf. Danach schreibt das Programm image in die Ausgabedatei.
helpers.hDie Prototypen der vier Funktionen. Jede bekommt height, width und RGBTRIPLE image[height][width]: ein Array aus height Zeilen, jede Zeile ein Array aus width Pixeln.
helpers.cDie vier Funktionen, noch leer. Hier schreiben Sie Ihren Code.
MakefileSagt make, wie aus den beiden Dateien filter.c und helpers.c ein Programm wird. Deshalb kompilieren Sie mit make filter.

Kompilieren und starten Sie das Gerüst einmal:

make filter
./filter -g images/yard.bmp out.bmp

Da grayscale noch nichts tut, sieht out.bmp genauso aus wie images/yard.bmp.

Spezifikation

Implementieren Sie die vier Funktionen in helpers.c:

  • grayscale wandelt das Bild in Graustufen um (Regel 3).
  • sepia wandelt das Bild in Sepia um (Regel 4). Jeder Farbwert ist danach eine ganze Zahl von 0 bis 255.
  • reflect spiegelt das Bild horizontal, also links ↔ rechts (Regel 5).
  • blur zeichnet das Bild mit dem 3×3-Kasten weich, auch am Rand und in den Ecken (Regeln 6 bis 8).
  • Die Filter sind voneinander unabhängig. Am einfachsten beginnen Sie in dieser Reihenfolge.

Ändern Sie keine der Funktionssignaturen und keine andere Datei als helpers.c. Wenn Ihr Vorgehen nur mit einer Änderung an anderer Stelle funktioniert, überdenken Sie es.

Hilfestellung

Klicken Sie auf die folgenden Tipps, um einige Ratschläge zu erhalten. Versuchen Sie aber zunächst, selbst so weit wie möglich zu kommen.

Implementierung von grayscale

Wandeln Sie das Vorgehen zuerst in Pseudocode um:

void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels

        // Take average of red, green, and blue

        // Update pixel values
}

Um über ein zweidimensionales Array zu iterieren, brauchen Sie zwei verschachtelte Schleifen:

void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // Take average of red, green, and blue

            // Update pixel values
        }
    }
}

Nun erreichen Sie mit image[i][j] jeden einzelnen Pixel. Auf die Farbwerte greifen Sie wie bei jedem struct mit dem Punkt zu: image[i][j].rgbtRed, image[i][j].rgbtGreen, image[i][j].rgbtBlue.

Die drei Farbwerte sind ganze Zahlen. Runden Sie den Mittelwert mit round auf die nächste ganze Zahl, bevor Sie ihn zuweisen. Und warum sollten Sie die Summe durch 3.0 und nicht durch 3 teilen?

Implementierung von sepia

Auch hier laufen Sie mit zwei verschachtelten Schleifen über alle Pixel:

void sepia(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // Compute sepia values

            // Update pixel with sepia values
        }
    }
}

Die Formel aus Regel 4 hat zwei Haken. Sie müssen

  • das Ergebnis jeder Berechnung auf die nächste ganze Zahl runden und
  • sicherstellen, dass der Wert nicht größer als 255 ist.

Wie könnte eine Funktion helfen, die von zwei ganzen Zahlen die kleinere zurückgibt? Sie können so eine Hilfsfunktion selbst schreiben, müssen aber nicht.

Achten Sie außerdem darauf, dass alle drei Formeln die ursprünglichen Werte des Pixels brauchen. Was passiert, wenn Sie rgbtRed schon überschrieben haben, bevor Sie den neuen Grünwert berechnen?

Implementierung von reflect

In reflect vertauschen Sie die Werte von Pixeln auf gegenüberliegenden Seiten einer Zeile. Als Pseudocode:

void reflect(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // Swap pixels
        }
    }
}

Erinnern Sie sich daran, wie wir in der Vorlesung zwei Werte mit einer temporären Variablen vertauscht haben. Eine eigene Funktion für das Vertauschen brauchen Sie nicht, dürfen Sie aber schreiben.

Denken Sie noch einmal über die innere Schleife nach: Muss sie wirklich über alle Pixel einer Zeile laufen? Was passiert, wenn jedes Paar zweimal vertauscht wird?

Implementierung von blur

Die Unschärfe eines Pixels hängt von seinen Nachbarn ab. Wenn Sie image direkt überschreiben, rechnen Sie für die folgenden Pixel schon mit weichgezeichneten Werten (Regel 8). Legen Sie deshalb zuerst eine Kopie an, etwa mit RGBTRIPLE copy[height][width];, und kopieren Sie Pixel für Pixel hinein:

void blur(int height, int width, RGBTRIPLE image[height][width])
{
    // Create a copy of image
    RGBTRIPLE copy[height][width];
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            copy[i][j] = image[i][j];
        }
    }
}

Jetzt lesen Sie die Farbwerte aus copy, rechnen und schreiben die Ergebnisse nach image.

Für den Kasten brauchen Sie zwei weitere Schleifen über Zeile i - 1 bis i + 1 und Spalte j - 1 bis j + 1. Zählen Sie nur Nachbarn, die im Bild liegen, und merken Sie sich, wie viele es waren – durch diese Zahl teilen Sie. Genau das übt Warm-up 4.

Testen

Kompilieren Sie filter mit:

make filter

Anschließend starten Sie das Programm zum Beispiel so:

./filter -g images/yard.bmp out.bmp

Das erzeugt aus images/yard.bmp ein Graustufenbild out.bmp. Testen Sie alle vier Filter an allen mitgelieferten Bildern; statt -g verwenden Sie -s, -r oder -b.

Korrektheit

Führen Sie in Ihrem Terminal den folgenden Befehl aus, um die Korrektheit Ihrer Arbeit zu überprüfen:

check50 -l inf-zone/exercises/2026/filter

Style

Führen Sie den folgenden Befehl aus, um den Stil Ihres Codes mit style50Prüfprogramm von CS50, das zeigt, wo Ihr Code von den Formatierungsregeln abweicht. In Grün zeigt es, was Sie ergänzen sollten, in Rot, was wegfallen soll. Glossar → zu analysieren:

style50 helpers.c

Abgeben

Geben Sie im Ordner filter ab:

inf upload filter

Danach sehen Sie, welche Tests Ihr Programm besteht und ob die Übung für die Bonuspunkte zählt. Wie Sie inf installieren und sich anmelden, steht unter Abgeben mit inf.

Zum Weiterlesen: Wie eine BMP-Datei aufgebaut ist

Für die Lösung der Aufgabe nicht nötig; das Einlesen und Schreiben erledigt filter.c.

Die einfachste Art, ein Bild darzustellen, ist ein Raster aus Pixeln (Punkten), von denen jeder eine eigene Farbe haben kann. Für ein Schwarz-Weiß-Bild genügt 1 Bit pro Pixel, zum Beispiel 0 für Schwarz und 1 für Weiß:

eine einfache Bitmap

Ein Bild ist in diesem Sinne ein Raster aus Bits, eine Bitmap. Für Farben braucht man mehr Bits pro Pixel:

  • Farbtiefe: Formate wie BMP, JPEG oder PNG können 24 Bit pro Pixel speichern. BMP kennt 1, 4, 8, 16, 24 und 32 Bit pro Pixel.
  • RGB: Bei 24 Bit stehen je 8 Bit für den Rot-, Grün- und Blauanteil. Rot 0xff (dezimal 255), Grün 0x00, Blau 0x00 ergibt reines Rot.
  • Header: Am Anfang der Datei stehen Metadaten wie Breite und Höhe, in zwei Datenstrukturen („Header“, nicht zu verwechseln mit Header-Dateien in C): BITMAPFILEHEADER mit 14 Byte und BITMAPINFOHEADER mit 40 Byte. filter.c prüft unter anderem, ob die Pixeldaten genau nach diesen 54 Byte beginnen.
  • BGR: Danach folgen die Pixel, je drei Byte – aber in der Reihenfolge Blau, Grün, Rot. Deshalb steht in RGBTRIPLE zuerst rgbtBlue.
  • Zeilenreihenfolge: Viele BMP-Dateien speichern die unterste Bildzeile zuerst. Die Bilder dieser Aufgabe sind umgekehrt gespeichert, oberste Zeile zuerst; im Header steht dafür eine negative Höhe.

Wandelt man den Smiley oben in 24 Bit um und ersetzt Schwarz durch Rot, speichert eine BMP-Datei ihn so; 0000ff ist Rot (in der Reihenfolge Blau, Grün, Rot), ffffff Weiß:

rotes Smiley

Mit etwas Abstand erkennen Sie den Smiley in den Hexziffern. Eine Hexziffer steht für 4 Bit; ffffff sind also 6 · 4 = 24 Bit, binär 111111111111111111111111.

Warum structs wie RGBTRIPLE nützlich sind: Eine Datei ist nur eine Folge von Bytes, und ein Dateiformat legt fest, welche Bytes was bedeuten. Man könnte die Datei als ein großes Byte-Array einlesen und sich merken, dass array[i] das eine und array[j] das andere bedeutet. Ein struct gibt diesen Bytes Namen: Statt einer langen Folge von Bytes liest filter.c eine Folge von structs.

Diese Seite als Markdown: ansehen herunterladen