Code Richtlinien

Da im Praktikum Bildbearbeitung vereinzelt auf Funktionalität aus vorherigen Aufgaben zurückgegriffen wird, bauen wir grundsätzlich auf dem Bearbeitungsstand der vorherigen Aufgabe auf. Sofern euch eine geforderte Funktionalität fehlen sollte wendet euch bitte umgehend an den jeweiligen Betreuer. Ihr erhaltet dann eine statische Bibliothek aus der ihr die benötigten Funktionen dazulinken könnt. Die folgenden Richtlinien sind für jede der geforderten Aufgaben einzuhalten. Explizit implementiert werden müssen sie selbstverständlich nur einmalig bei der ersten Aufgabe.

Die folgenden Seiten der OpenCV Dokumentation sollten euch bei euren ersten Schritten gut unterstützen können:

Makefile und Umgebung

Wir arbeiten im Praktikum Bildbearbeitung grundsätzlich mit der vorinstallierten Linux-Softwareumgebung der Rechenzentrum-PCs. Aktuell sind dort die folgenden Versionen installiert:

  • gcc 4.8.2
  • make 3.81
  • OpenCV 2.4.8.0

Verwendet für die Abgabe das von uns vorgegebene Makefile. Es setzt automatisch die korrekten Compileroptionen, um OpenCV einzubinden und sorgt dafür, dass die C++ Quellen nur dann übersetzt werden wenn es auch tatsächlich eine Änderung in relevanten Headerdateien gab.

Argumente über die Kommandozeile

Die Kommunikation der vorzunehmenden Operationen erfolgt grundsätzlich über Kommandozeilenparameter. Diese sind stets nach dem folgenden Schema aufgebaut:

./bba <input image> <operation> [operation-params] [global-options]

Dabei stehen Argumente in <spitzen Klammern> für zwingend erforderliche Werte, Argumente in [eckigen Klammern] stehen für komplexere Werte die im Rahmen der konkreten Aufganbestellung erläutert werden. Das erste Argument ist also immer der Dateiname eines zu lesenden Bildes, das zweite Argument immer die anzuwendende Operation. Je nach Operation folgt dann eine Variable Anzahl von Parametern. Aus Gründen der Lesbarkeit wird die Operation nicht mit einem Präfix versehen. Globale Argumente werden immer mit -- als Präfix eingeleitet und tauchen aus Gründen der vereinfachten Verarbeitung nur ganz am Ende auf.

Konkret denkbare Aufrufe von dem zu erstellenden Programm wären daher zum Beispiel:

  • ./bba testbild.png invert               # Operation ohne Parameter
  • ./bba testbild.png brighten 10          # Operation mit einem Parameter
  • ./bba testbild.png gaussian --edge tile # Angabe eines globalen Parameters

Macht euch dementsprechend Gedanken, wie ihr die in diesem Schema angegebenen Parameter sinnvoll verarbeiten könnt. Ein sinnvoller erster Schritt wäre dabei sicherlich die Konvertierung der Parameter argc und argv in einen std::vector<std::string>. Das spart bei der Übergabe an diverse parse Funktionen einen Parameter und sorgt gleichzeitig für sicherere Zugriffe und Vergleiche.

Lesen von Bildern

Grundsätzlich können Bilder mit cv::imread eingelesen werden. Überprüft aber bitte für jede Aufgabenstellung ob das Bild nach dem Einlesen noch in spezielles internes Format oder einen spezfischen Farbraum konvertiert werden muss. Die von OpenCV gewählten Standards entsprechen nicht zwingend den Vorgaben der Übung!

Aktuell leider etwas in der Doku versteckt ist die Existenz der Option CV_LOAD_IMAGE_UNCHANGED, welche im Rahmen dieser Veranstaltung beim Laden von Bildern zu verwenden ist. Wenn man in euer Programm ein Graustufenbild hereingibt soll, sofern vom gewählten Bildformat unterstützt, auch ein Graustufenbild wieder herauskommt.

Parameter: Schreiben von Bildern

Für jedes Kommandozeilenprogramm ist immer die Kommandozeilenoption --out zu implementieren.  Diese beschreibt den Dateinamen unter dem das entstehende Bild zu speichern ist. Das zu schreibende Dateiformat ergibt sich dabei aus der Dateiendung, unterstützt werden müssen die von cv::imwrite vorgesehenen Formate PNG, JPG und PPM.

Wenn diese Option nicht angegeben wird soll das transformierte Bild als Portable Pixmap (für Farbbilder, Magic Number P3) oder als Portable Graymap (für Graustufenbilder, Magic Number P2) auf stdout ausgegeben werden. Diese Ausgabe auf stdout soll einigermaßen menschlich lesbar sein und ist zur direkten Kontrolle von kleinen Bildern gedacht. Fügt daher bitte nach jeder Bildzeile einen Zeilenumbruch ein und richtet die einzelnen Zahlenwerte rechts- oder linksbündig aus.

Packt diese Funktionalität in ein util.{c,h}pp Modul und gebt ihr die folgende Signatur:

void writeTextImage(const cv::Mat& imgOut);

Parameter: Anzeigen von Bildern

Für jedes Kommandozeilenprogramm ist immer die Kommandozeilenoption --imshow zu implementieren. Diese soll das ursprüngliche und das transformierte Bild Seite an Seite anzeigen. Konkret müssen also beide Bilder Seite an Seite in eine cv::Mat Instanz kopiert werden. Am einfachsten geht das vermutlich mit der cv::Mat::copyTo Methode. Bilder lassen sich mittels cv::imshow anzeigen. Packt diese Funktionalität in ein util.{c,h}pp Modul und gebt ihr die folgende Signatur:

void showSideBySide(const cv::Mat& imgIn, const cv::Mat& imgOut)

Damit man auch Zeit hat die Bilder zu betrachten soll das Programm nach Anzeige der Bilder auf einen Druck der Taste Q warten. Implementiert auch hierfür eine eigene Funktion unter Verwendung von cv::waitKey:

void waitForKey(char key)

Eine Funktion je geforderter Funktionalität

Für die zu implementierenden Funktionalitäten werden in der Aufgabenstellung typischerweise Kommandozeilenparameter beschrieben. Trotzdem soll für jede zu implementierende Operation eine eigene Funktion mit sinnvollem Interface entstehen. Solltet ihr beim Parsen ein struct verwenden um die Parameter irgendwie zu bündeln darf dieses struct nicht an diese Funktionen übergeben werden.

  • Sofern der Funktion Listen von Werten übergeben werden sollen, sind diese als konstante Referenz auf einen std::vector<T> zu übergeben. Konkret für eine Liste an Integern wäre der Typ also const std::vector<int>& intList.
  • Wenn die Aufgabenstellung standardmäßig zu verwendende Werte für Parameter definiert, muss sich eine überladende Variante der Funktion ohne explizite Angabe dieser Standardwerte aufrufen lassen.

Ausnahmebehandlung

Ihr dürft das Programm im Falle von fehlenden oder ungültigen Parametern durch werfen einer Instanz von std::runtime_error beenden. Gebt dabei eine aussagekräftige Fehlermeldung an! Solltet ihr Ausnahmen behandeln wollen, müssen diese im catch-Block als konstante Referenz gefangen werden. Ein allgemeines behandeln aller Fehler würde also mit catch(const std::exception& ex) eingeleitet werden. Die für die Ausnahmebehandlung notwendigen Typen sind im Header stdexcept definiert.