Projekt Luna  1.0.1
Der humanoide Roboter
Erstes Programm

Autor: Joshua Brauns
Letzte Änderung: 18.01.2016 11:29:18

Neues Projekt

Bevor wir mit dem Programmieren loslegen können, muss erst einmal das Visual Studio gestartet und die Projektmappe Projekt Luna geöffnet werden. Wer sich nun die Frage stellt: "Welche Projektmappe und was ist Visual Studio?", der sei zuerst auf Installation von Visual Studio und anschließend auf anlegen der ersten Projektmappe.

Wenn dies erledigt ist, können wir das neue Projekt Navigation anlegen. Hier wird erklärt wie das gemacht wird. Anschließend sollten wir folgendes Bild im Visual Studio vorfinden dürfen:

navigation_first_new_project.jpg
Neues Projekt

Dieses Projekt weist mehrere Reiter auf deren Bedeutung im folgenden teilweise erläutert wird:

Verweise

Hier nicht benötigt

Externe Abhängigkeiten

Dieser so genannte Filter listet alle Dateien auf, auf welche in dem geschriebenen Programmcode verwiesen wird. verwendet man beispielsweise folgenden Befehl: #include <stdio.h>, so wird zwangsläufig die Datei stdio.h in dem Filter "Externe Abhängigkeiten" aufgeführt.

Headerdateien

In diesem Filter sollten alle im Programm verwendeten Headerdateien untergebracht werden.

Quelldateien

In diesem Filter sollten alle im Programm verwendeten Quelldateien (.c, .cpp, usw.) untergebracht werden.

Ressourcendateien

Hier nicht benötigt

Mit main fängt alles an

Mit einem Rechts-klick auf den Filter Quelldateien und einem anschließenden Klick auf: Hinzufügen -> Neues Element, und anschließender Auswahl einer C++-Datei, wollen wir nun unsere erste Quelldatei dem Projekt hinzufügen. Diese wird entsprechend mit dem Namen: "navigation.cpp" versehen und in einem bevorzugten Speicherort erstellt. Sind alle Einstellungen getroffen, mit Hinzufügen bestätigen.

Im Anschluss auf das Hinzufügen der neuen Datei, wird diese auch sogleich geöffnet und wir können unsere ersten Zeilen Code schreiben. Diese lauten wie folgt:

#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\imgcodecs\imgcodecs.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char **argv)
{
VideoCapture capture;
Mat frame;
return 0;
}

Anschließend erstellen wir das Projekt ein erstes mal über: Erstellen -> Projektmappe erstellen. Sollten hierbei Fehler auftreten, bitte noch einmal den Einrichtungsprozess des Visual Studios und der OpenCV Bibliothek überprüfen und gegebenenfalls Fehler beheben.

Wenn der Buildvorgang erfolgreich war, darf ich dir zu deinem ersten erfolgreichen OpenCV Programm gratulieren.

Aber schauen wir uns doch mal an, was wir da überhaupt geschrieben haben. Die ersten fünf Zeilen Code sollten für die meisten kein Problem darstellen, werden hier doch lediglich benötigte Headerdateien für OpenCV eingebunden. Von den Namen der Dateien kann man dabei in etwa den Verwendungszweck bestimmen:

core.hpp

Der "Kern" von OpenCV beinhaltet viele essenzielle Klassen, Funktionen, Strukturen, usw.

highgui.hpp

High-Level GUI (Graphical User Interface)

imgproc.hpp

Image processing

imgcodecs.hpp

Image Codecs; Lesen und schreiben von Dateien

Des weiteren wird noch eine Datei namens iostream eingebunden. Diese stellt in etwa das C++ Gegenstück zur stdio.h aus C bekannt dar.

Anschließend wird wie bereits erwartet die main-Funktion deklariert.

Neu dürften für die meisten allerdings die beiden ersten Zeilen innerhalb der main-Funktion sein. Dort wird einmal eine Variable vom Typen VideoCapture und eine Variable vom Typen Mat deklariert. Für die, die bisher nur mit C gearbeitet haben hier eine kurze Einführung: Bei den Typen VideoCapture und Mat handelt es sich um so genannte Klassen. Klassen sind, um es denen mit C Kenntnissen einfacher zu machen, ähnlich wie Strukturen in C. Jedoch bieten sie die Möglichkeit, neben Feldern auch Funktionen zu beinhalten. Diese werden dann als Methoden bezeichnet. Zusätzlich bieten Klassen aber auch noch Vererbung und vieles mehr, von dem wir aber in dieser Programmier-Reihe keinen Gebrauch machen möchten.

Video Stream oder Einzelbilder

Abhängig von der Anwendung und der Konfiguration des verwendeten Rechners möchten wir einen sogenannten Video Stream oder nur einzelne Bilder an unsere Filter übergeben. Um unseren Code entsprechend steuern zu können, verwenden wir die sogenannten #if-, #else- und #endif-Direktiven. Deren Funktion entspricht denen der if- und else-Anweisungen die bereits bekannt sein sollten. Anders als diese Anweisungen jedoch, werden die Direktiven vom Preprozessor verarbeitet und sind somit für den Kompiler nicht mehr sichtbar. Sie bieten somit die Möglichkeit, ein Programm anhand von verschiedenen Parametern zu konfigurieren ohne dass dadurch der zu übersetzende Code unnötige Zeilen enthält.

#define VIDEO_STREAM 1
...
#if defined(VIDEO_STREAM)
Anweisung1
...
#else
Anweisung2
...
#endif

Video Stream

Ein Video Stream mag zunächst vom Namen her nach etwas kompliziertem klingen, entpuppt sich aber schnell als ein simples und einfach zu verstehendes Konzept. Wichtig dafür ist, die Funktionsweise einer Videokamera zu verstehen.

  1. Bildsensor wird abgefragt (Nacheinander wird jeder Wert einer Fotodiode in der Sensormatrix gemessen und gespeichert)
  2. Die aufgenommen Werte werden in entsprechenden Typen (Z.B. bei OpenCV im Typ Mat) gespeichert nachdem sie vorher in bestimmte Farbmodelle konvertiert wurden
  3. Entweder direkt nach der Abarbeitung oder nach einer fest eingestellten Zeit (Z.B. 30 FPS - Frames per Second o. zu deutsch: Bilder pro Sekunde - was eine Abfragezeit von 33 ms macht) wird wieder bei Schritt 1 begonnen

Dieser Fluss (auf englisch Stream) ist dabei kontinuierlich und wird lediglich durch einen Programmabbruch oder einen Fehler im Sensor/Schnittstelle unterbrochen.

Kurz gefasst ist der Video Stream also lediglich ein kontinuierlicher Fluss an Bildern die wir jedes für sich an unsere Filter übergeben. Jedoch wird für einen Video Stream entsprechende Hardware benötigt. An einem Rechner kann dies entweder ein über USB angeschlossene WebCam oder eine fest verbaute sein. Am NAO Roboter sind zwei Kameras verfügbar welche über das Netzwerk abgefragt und Bilddaten verschicken können. Für nähere Informationen bitte hier klicken.

Der Vorteil bei einem Videostream ist, die Wirkung von Filtern mit festen Einstellungen auf verschiedene Bilddaten anzuwenden. Somit kann eine direkte Anwendung unter realen Gegebenheiten simuliert werden.

Einzelbilder

Unter Einzelbilder ist eine Menge an Bildern, gespeichert auf der Festplatte, gemeint, welche bereits zum Programmstart existieren. Beim Video Stream war dies nicht der Fall, da die Bilder erst während der Ausführung des Programms erstellt werden.

Diese können ebenfalls mit Hilfe einer Webcam oder anderen Kameraformen erstellt und auf die Festplatte importiert werden. Der Vorteil bei den Einzelbildern liegt darin, bestimmte Filterparameter anzupassen und dabei eine direkte Rückmeldung über deren Auswirkungen zu erhalten. Es ist somit möglich bestimmte Filtereinstellungen zu vergleichen und die für die Situation nützlichste zu wählen.

Videos

Videos sind im Grunde genommen wie ein Videostream, jedoch werden die Bildaten nicht währen des Programmablaufs angefertigt sondern existieren bereits zu Programmbeginn.

Der Vorteil von Videos ist ähnlich dem Vorteil von Einzelbildern, jedoch ist hierbei der Vergleich von verschiedenen Filterparametern an bewegtem Bildmaterial.

Dateineingabe

Videostream

Um an kontinuierliche Bildaten einer Webcam zu gelangen, muss diese zunächst nach diesen Bilddaten gefragt werden. Glücklicherweise liefert OpenCV von Haus aus einen entsprechenden Typen mit: VideoCapture. Dies erleichtert uns die Arbeit ungemein, da eine eigene Implementation viel Zeit in Anspruch nehmen würde.

Wir legen also eine Variable vom Typen VideoCapture an:

Mat frame;
VideoCapture capture;
capture.open(0);

Der Typ VideoCapture ist dabei vom Basiskonstrukt Klasse. Dies heißt, dass er neben Feldern auch Methoden beinhält welche wir aufrufen können. Eine wichtige Methode ist dabei die open()-Methode, welche ein entsprechendes Aufnahmegerät (Webcam) so einrichtet, dass Bildmaterial von diesem angefordert werden kann. Der Parameter, welcher bei dieser Überladung der Methode übergeben wird, ist der Index der angeschlossenen Webcam. Wir wählen in diesem Falle den Index 0, da im normal Fall nur eine Webcam angeschlossen ist.

Anschließend müssen wir überprüfen, ob die Einrichtung erfolgreich war, da wir ansonsten fehlerhafte Bilddaten erhalten:

if (!capture.isOpened())
{
cout << "--(!)Error opening video capture" << endl;
return(-1);
}

Wenn die Einrichtung nicht erfolgreich war, wird eine Fehlermeldung ausgegeben und unser gesamtes Programm beendet. War die Einrichtung erfolgreich können wir zum letzten Schritt übergehen und damit beginnen, kontinuierlich Bilddaten abzufragen:

while (capture.read(frame))
{
if (frame.empty())
{
cout << "--(!)No captured frame -- Break!" << endl;
break;
}
}

Hierbei sind wieder zwei Mögliche Fehlerfälle zu überprüfen: Das lesen der Bilddaten war nicht erfolgreich und das abgefragte Bildmaterial ist leer. Beide führen zum Abbruch des Programms.

Nebenbei sehen wir aber auch noch den Zweck der frame Variablen vom Typ Mat. Diese dient als Speicher für das Videomaterial, ist ebenfalls vom Basiskonstrukt Klasse und enthält daher auch Methoden. Eine dieser Methoden ist empty(), welche zurück gibt, ob Bildmaterial vorhanden ist oder nicht.

Einzelbild

Video

Datenverarbeitung

Datenausgabe

A - Vollständiges Programm

#include "navigation.h"
#define VIDEO_STREAM 1
#define IMAGE_SINGLE 1
using namespace std;
String g_WindowName = "Video Stream";
int main(int argc, char **argv)
{
Mat frame;
#if defined(VIDEO_STREAM)
VideoCapture capture;
capture.open(0);
if (!capture.isOpened())
{
cout << "--(!)Error opening video capture" << endl;
return(-1);
}
while (capture.read(frame))
{
if (frame.empty())
{
cout << "--(!)No captured frame -- Break!" << endl;
break;
}
imshow(g_WindowName, frame);
int c = waitKey(10);
}
#elif IMAGE_SINGLE
#else
#endif
return 0;
}