Skip to content
Snippets Groups Projects
Commit 200f7ded authored by Jan Wille's avatar Jan Wille
Browse files

korrektur JB

parent 7fc06af9
No related branches found
No related tags found
No related merge requests found
...@@ -56,3 +56,10 @@ Gradientenwinkel ...@@ -56,3 +56,10 @@ Gradientenwinkel
Klassifizierungsschritt Klassifizierungsschritt
fehlklassifizierte fehlklassifizierte
Headerdatei Headerdatei
nutzernahen
Library
SparkFun
Kernelgröße
Gradientenbetrachtung
Canny-Edge-Detector
for-Schleife
...@@ -25,3 +25,36 @@ ...@@ -25,3 +25,36 @@
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QOptimierung durch eigene Implementierung des Canny-Edge-Detectors.\\E$"} {"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QOptimierung durch eigene Implementierung des Canny-Edge-Detectors.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDa dieser Code sehr viele verschachtelte und gedoppelte if-Abfragen aufweist, wird er in \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q vereinfach gezeigt.\\E$"} {"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDa dieser Code sehr viele verschachtelte und gedoppelte if-Abfragen aufweist, wird er in \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q vereinfach gezeigt.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDer Unterschied liegt in der Funktion edgeDetectionClassification().\\E$"} {"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDer Unterschied liegt in der Funktion edgeDetectionClassification().\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QSensor-Linse alignment Probleme in der Ausrichtung von Sensor und Linse (nach \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q)\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QBeim Start der \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q wird die main() Funktion aufgerufen, welche die notwendigen \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q Funktionen zur Initialisierung aufruft, das benötigt \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q abonniert, eine \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q anhängt und die eigenen Dummies veröffentlicht.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDie Beziehung der Dummies ist in \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q grafisch dargestellt.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDas erhaltene Bild wird auf die \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q reduziert und unter dem \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q /img/color veröffentlicht.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QAußerdem wird ein Schwarz-Weiß Version erzeugt und diese als /img/gray veröffentlicht, was hier aber nicht gezeigt ist.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QUm die Laufzeit der \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q zu bestimmen, wird die aktuelle Zeit, wie sie von der Funktion ros::Time::now() zurückgegeben wird, verwendet.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QZusammenhang der Fahrspurmarkierung-Erkennungs-\\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q mit den bestehenden Dummies\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QWie diese neuen Dummies mit den bestehenden Dummies in Beziehung stehen soll, ist in \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q grafisch dargestellt.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QUm kleine Störungen im Bild, welche bestehende Kanten verzerren oder als falsche Kante erkannt werden könnten, zu reduzieren, wird das Bild mit einem gauss-filter Gaußschen Filter geglättet.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QWird dieser Code auf das Beispielbild \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q angewendet und das Ergebnis des Dummies ausgegeben, ergibt sich \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDie eigentliche Kantenerkennung wird mittels eines Dummies durchgeführt.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\Q\\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q stellt hierzu die Funktion GaussianBlur() zur Verfügung, der das geladene Bild, die Kernelgröße und der Wert für \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q übergeben wird.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDiese gibt einen Winkel in rad zurück, welcher zur besseren Nachvollziehbarkeit in Grad umgerechnet wird.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QMit diesen wird nun die atan2(dy,dx) Funktion aufgerufen.\\E$"}
{"rule":"ART_ADJ_SOL","sentence":"^\\QDazu wird der nächste Nachbarpixel gesucht.\\E$"}
{"rule":"DE_CASE","sentence":"^\\QEin Beispiel ist in \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q für Linienmarkierungen der Orientierung Diagonal 1 gezeigt.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDort sieht man, dass für die \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q der Namen lane_marker_detection gewählt wird.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDie Beziehung der neuen \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q zu den bestehenden Dummies wurde bereits in \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q skizziert.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDas Bild mit den eingezeichneten, detektierten Spurmarkierungen wird nach Durchlauf des Algorithmus auf dem eigenen \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q /img/lanemarkings veröffentlichte.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QAußerdem wird das \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q /img/gray von der Entzerrer-\\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q abonniert, um jedes Schwarz-Weiß Bild zu bekommen.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDas Bild mit den eingezeichneten, detektierten Spurmarkierungen wird nach Durchlauf des Algorithmus auf dem eigenen \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q /img/lanemarkings veröffentlicht.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QBeim Abonnieren des /img/gray Topics wird die \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q callback_image() angehängt, sodass diese von \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q für jedes Bild aufgerufen und das Bild an sie übergeben wird.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QBeim Abonnieren des /img/gray Topics wird die \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q callback_image() angehängt, sodass diese von \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q für jedes Bild aufgerufen und das Bild an sie übergeben wird.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QWährend einer Testfahrt des Dummies wurden von der entzerrer \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q veröffentlichte Bilder abgespeichert, sodass sie zum lokalen Testen zur Verfügung stehen.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDazu werden mit \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q Sobel-Dummies die Gradienten \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q und \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q bestimmt.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QMit der atan2() Funktion kann aus diesen beiden Größen der Winkel des Gradientenvektors \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q berechnet werden.\\E$"}
{"rule":"DE_CASE","sentence":"^\\QZuordnung der Klassen zu Bits Bit Klasse 1 Vertikal 2 Diagonal 1 3 Diagonal 2 4 Horizontal 5 Vorzeichen-Bit\\E$"}
{"rule":"DE_DASH","sentence":"^\\Q\\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q- \\E(?:Dummy|Ina|Jimmy-)[0-9]+$"}
{"rule":"DE_CASE","sentence":"^\\QDies vereinfacht die folgenden Abfragen, da für die Vertikal und Horizontal Klasse der Betrag des Winkels ausreicht.\\E$"}
{"rule":"DE_VERBAGREEMENT","sentence":"^\\QBeim Abonnieren des /img/gray Topics wird die \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q callback_image() angehängt,\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDas so konvertierte Bild kann nun an die Funktion edgeDetectionClassification() übergeben werden, welche die Erkennung und Klassifizierung der Kanten durchführt.\\E$"}
{"rule":"DE_VERBAGREEMENT","sentence":"^\\QDie \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q callback_image() verläuft völlig analog zur Implementierung mit \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q.\\E$"}
{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDie \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q callback_image() verläuft völlig analog zur Implementierung mit \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q.\\E$"}
...@@ -5,12 +5,16 @@ ...@@ -5,12 +5,16 @@
150 150
], ],
"ltex.language": "de-DE", "ltex.language": "de-DE",
"todo-tree.general.tagGroups": {"ToDo":["TODO", "ToDo", "\\todo[", "\\todo{"]}, "todo-tree.general.tagGroups": {
"ToDo":["TODO", "ToDo", "\\todo[", "\\todo{"],
"missingfigure":["\\missingfigure{"]
},
"todo-tree.general.tags": [ "todo-tree.general.tags": [
"TODO", "TODO",
"ToDo", "ToDo",
"missingfigure"
], ],
"todo-tree.regex.regex": "(%|^|^[ \\t]*(-|\\d+.))\\s*($TAGS)|(\\\\todo[\\[\\{])", "todo-tree.regex.regex": "(%|^|^[ \\t]*(-|\\d+.))\\s*($TAGS)|(\\\\(todo|missingfigure)[\\[\\{])",
"todo-tree.filtering.excludeGlobs": [ "todo-tree.filtering.excludeGlobs": [
"**/.vscode/**", "**/.vscode/**",
"**/.aux/**", "**/.aux/**",
......
No preview for this file type
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
\author{Jan Wille} \author{Jan Wille}
\matrikelnr{1535115} \matrikelnr{1535115}
\subject{Bachelorarbeit} \subject{Bachelorarbeit}
\title{Video-basierte Erkennung von Fahrspurmarkierungen auf mobilen Robotern} \title[Video-basierte Erkennung von Fahrspurmarkierungen auf mobilen Robotern]{Video-basierte Erkennung von Fahrspurmarkierungen\\ auf mobilen Robotern}
%\subtitle{Subtitle} %\subtitle{Subtitle}
\date{09.05.2022 -- \today} \date{09.05.2022 -- \today}
...@@ -56,6 +56,9 @@ ...@@ -56,6 +56,9 @@
\printglossary \printglossary
\mainmatter \mainmatter
\todo[inline]{Schreibweise von langen Worten vereinheitlichen. Bindestrichen oder alles zusammen?}
\todo[inline]{Schwarzweißbild zu Grauwertbild und überall einheitlich}
\include{chap/einleitung} \include{chap/einleitung}
\include{chap/standdertechnik} \include{chap/standdertechnik}
......
...@@ -18,9 +18,8 @@ ...@@ -18,9 +18,8 @@
text={ROS}, text={ROS},
sort={ROS}, sort={ROS},
description={ description={
Das Robot Operating System ist eine Sammlung von Softwarebiblioteken und Werkzeugen die hilfreich beim erstellen von Roboter Aplikationen Das Robot Operating System ist eine Sammlung von Softwarebibliotheken und Werkzeugen, die hilfreich beim Erstellen von Roboter Applikationen
sind. Von Treibern bis moderne Algoritmen, ROS birgt alles was für das nächste Robotic Projekt benötigt wird. Dabei ist es vollständig Open sind. Es enthält Treiber, fertig implementierte Standardalgoritmen und andere hilfreiche Funktionen. Es ist vollständig Open Soruce.
Soruce.
} }
} }
...@@ -28,16 +27,16 @@ ...@@ -28,16 +27,16 @@
name={ROS Node}, name={ROS Node},
text={Node}, text={Node},
description={ description={
Eine ROS Node ist ein Teilprogramm welches von \gls{ROS} verwaltet wird. Es kann Informationen als \gls{Topic} veröffentlichen und Eine ROS Node ist ein Teilprogramm, welches von \gls{ROS} verwaltet wird. Es kann Informationen als \gls{Topic} veröffentlichen und
\glspl{Topic} Abonieren um die dort veröffentlichen Informationen weiter zu verarbeiten. \glspl{Topic} abonieren, um die dort veröffentlichen Informationen weiter zu verarbeiten.
} }
} }
\newglossaryentry{ROS Nodelet}{ \newglossaryentry{ROS Nodelet}{
name={ROS Nodelet}, name={ROS Nodelet},
description={ description={
Ein ROS Nodelet ist ein Programm welches in einem Verbund mit mehreren anderen Nodelets mittels \gls{ROS} gestartet wird. Alle Nodelets einer Ein ROS Nodelet ist ein Programm, welches in einem Verbund mit mehreren anderen Nodelets mittels \gls{ROS} gestartet wird. Alle Nodelets einer
Gruppe haben die Möglichkeit auf geteilen Anwendungsspeicher zuzugreifen. Gruppe haben die Möglichkeit auf geteilten Anwendungsspeicher zuzugreifen.
} }
} }
...@@ -61,8 +60,8 @@ ...@@ -61,8 +60,8 @@
\newglossaryentry{Callback}{ \newglossaryentry{Callback}{
name={Callback-Funktion}, name={Callback-Funktion},
description={ description={
Eine Funtkion die unter bestimmten Bedingungen automatisch aufgerufen wird. Im Bezug aus \gls{ROS} geht es meistens um Funktion die für jede Eine Funtkion, die unter bestimmten Bedingungen automatisch aufgerufen wird. Im Bezug auf \gls{ROS} geht es meistens um Funktionen die für
Nachricht auf einem abonierten \gls{Topic} mit deren Inhalt aufgerufen werden. jede Nachricht auf einem abonnierten \gls{Topic} mit deren Inhalt aufgerufen werden.
} }
} }
...@@ -78,14 +77,14 @@ ...@@ -78,14 +77,14 @@
name={JetBot}, name={JetBot},
description={ description={
Der in deser Arbeite verwendete Roboter. Gesteuert wird er von einem NVIDIA Jetson Nano Entwicklerboard, auf welchem die hier erstellten Der in deser Arbeite verwendete Roboter. Gesteuert wird er von einem NVIDIA Jetson Nano Entwicklerboard, auf welchem die hier erstellten
Programme laufen. Weiteres ist in \autoref{sec: JetBot} beschrieben. Programme laufen.
} }
} }
\newglossaryentry{Welt-coords}{ \newglossaryentry{Welt-coords}{
name={Weltkoordinaten}, name={Weltkoordinaten},
description={ description={
Ein 3D-Koordinatensystem das die gesamet Scene/Welt des derzeitigen Systems umfasst. Ein 3D-Koordinatensystem, das die gesamet Scene/Welt des derzeitigen Systems umfasst.
} }
} }
\newglossaryentry{Objekt-coords}{ \newglossaryentry{Objekt-coords}{
...@@ -115,7 +114,7 @@ ...@@ -115,7 +114,7 @@
\newglossaryentry{gauss-filter}{ \newglossaryentry{gauss-filter}{
name={Gaussscher Filter}, name={Gaussscher Filter},
description={ description={
Eine Filterfunktion um Bilder zu glätten. Sie biltet den mittels einer 2D-Gaußfunktion gewichten Mittelwert einer Nachbarschschaft. Eine Filterfunktion, um Bilder zu glätten. Sie biltet den mittels einer 2D-Gaußfunktion gewichten Mittelwert einer Nachbarschschaft.
} }
} }
......
\chapter{Ausblick} \label{chap: ausblick} \chapter{Ausblick} \label{chap: ausblick}
Die grundsätzliche Erkennung von Linienmarkierungen im Bilddatenstrom konnte erfolgreich umgesetzt werden. Jedoch bestehen in mehreren Bereichen Die grundsätzliche Erkennung von Linienmarkierungen im Bilddatenstrom konnte erfolgreich umgesetzt werden. Jedoch bestehen in mehreren Bereichen
des Programmes noch Weiterentwicklungsmöglichkeiten. Dadruch könnte die Erfassungsgenauigkeit insbesondere in schwierigen Situationen erhöt und des Programmes noch Weiterentwicklungsmöglichkeiten. Dadurch könnte die Erfassungsgenauigkeit insbesondere in schwierigen Situationen erhöht und
weite er Informationen gewonnen werden. weitere Informationen gewonnen werden.
\subsubsection{Mehr Orientierungsklassen} \subsubsection{Mehr Orientierungsklassen}
Derzeit wird bei der Klassifizierung der Kantenpixel lediglich in vier unterschiedliche Klassen eingeteilt. Das kann insbesondere bei Kurven Derzeit wird bei der Klassifizierung der Kantenpixel lediglich in vier unterschiedliche Klassen eingeteilt. Das kann insbesondere bei Kurven
zu dem Problem führen, dass die Klassifizierung innerhalb einer Kante plötzliche wechselt und so zwei unterschiedliche Linien auf derselben zu dem Problem führen, dass die Klassifizierung innerhalb einer Kante plötzlich wechselt und so zwei unterschiedliche Linien auf derselben
Kante erfasst werden. Kante erfasst werden.
Durch mehr Klassen und Berücksichtigung von benachbarten Klassen in der Linienverfolgung ließen sich potenziell auch gekrümmte Linien Durch mehr Klassen und Berücksichtigung von benachbarten Klassen in der Linienverfolgung ließen sich potenziell auch gekrümmte Linien
...@@ -17,19 +17,19 @@ ...@@ -17,19 +17,19 @@
\subsubsection{Reduzierung von Falschklassifizierungen} \subsubsection{Reduzierung von Falschklassifizierungen}
Dies ist möglicherweise bereits Teileweise durch den vorherigen Punkt mit abgedeckt, bedarf aber trotzdem einer weiteren Erklärung. Derzeit Dies ist möglicherweise bereits teilweise durch den vorherigen Punkt mit abgedeckt, bedarf aber trotzdem einer weiteren Erklärung. Derzeit
werden vor allem bei weiter von der Kamera entfernten Linien, also vor allem den Spurmarkierungen benachbarter Spuren, viele werden vor allem bei weiter von der Kamera entfernten Linien, also vor allem den Spurmarkierungen benachbarter Spuren, viele
Fehlklassifizierungen durchgeführt. Wie in \autoref{fig: schlechte kante} gezeigt wird eine Kante durch fehlklassifizierte \gls{Pixel} unterbrochen, Fehlklassifizierungen durchgeführt. Wie in \autoref{fig: schlechte kante} gezeigt wird, eine Kante durch fehlklassifizierte \gls{Pixel} unterbrochen,
sodass eine durchgängige Linienverfolgung nicht möglich ist. sodass eine durchgängige Linienverfolgung nicht möglich ist.
Hier könnten zuerst einmal Ansätze zum Verbinden von sehr eng zusammenliegenden Linien Abhilfe schaffen. Aber eine Verbesserung der Hier könnten zuerst einmal Ansätze zum Verbinden von sehr eng zusammenliegenden Linien Abhilfe schaffen. Aber eine Verbesserung der
Genauigkeit bei der Klassifizierung wäre ebenfalls denkbar. Genauigkeit bei der Klassifizierung wäre ebenfalls denkbar.
\subsubsection{Datenübergabe an weiter Prozesse} \subsubsection{Datenübergabe an weitere Prozesse}
Da diese Arbeit als Grundlage für weitere Prozesse, z.B. zum autonomen Einhalten der Spur beim Fahren, müsste die genaue Methode zur Da diese Arbeit als Grundlage für weitere Prozesse dient, z.B. zum autonomen Einhalten der Spur beim Fahren, müsste die genaue Methode zur
Datenübergabe festgelegt werden. Zurzeit wird ein weiteres Bild mit den eingezeichneten Fahrspurmarkierung veröffentlicht, weiter Prozesse Datenübergabe festgelegt werden. Zurzeit wird ein weiteres Bild mit den eingezeichneten Fahrspurmarkierung veröffentlicht, weitere Prozesse
benötigen aber die erzeugten Objekte mit den enthaltenen Informationen. benötigen aber die erzeugten Objekte mit den enthaltenen Informationen.
Diese liegen vor und können über die definierte Headerdatei genutzt werden, es findet aber noch keine Übertragung mittels \gls{ROS} statt. Diese liegen vor und können über die definierte Headerdatei genutzt werden, es findet aber noch keine Übertragung mittels \gls{ROS} statt.
......
...@@ -5,23 +5,23 @@ ...@@ -5,23 +5,23 @@
Auf der Projektfläche \emph{Autonomes Fahren} des Instituts für Konstruktionselemente, Mechatronik und Elektromobilität (IKME) der Hochschule Auf der Projektfläche \emph{Autonomes Fahren} des Instituts für Konstruktionselemente, Mechatronik und Elektromobilität (IKME) der Hochschule
Hannover ist eine große urbane Kreuzung im Maßstab 1:18 nachgebildet. Hier sollen in Zukunft automatisierte Logistikkonzepte mit mobilen Hannover ist eine große urbane Kreuzung im Maßstab 1:18 nachgebildet. Hier sollen in Zukunft automatisierte Logistikkonzepte mit mobilen
Roboterfahrzeugen entwickelt und getestet werden. Die Roboter sind jeweils mit einer nach vorne gerichteten Videokamera ausgerüstet. Um die Roboterfahrzeugen entwickelt und getestet werden. Die Roboter sind jeweils mit einer nach vorne gerichteten Videokamera ausgerüstet. Um die
Fahrzeuge damit sicher steuern zu können, soll damit eine zuverlässige Fahrspurerkennung benötigt. Fahrzeuge damit sicher steuern zu können, wird eine zuverlässige Fahrspurerkennung benötigt.
\section{Aufgabenstellung} \label{sec: aufgabe} \section{Aufgabenstellung} \label{sec: aufgabe}
Ziel der Arbeit ist es, eine echtzeitfähige Erkennung der Fahrspurmarkierungen aus dem Video-Bilddatenstrom zu realisieren und die Position Ziel der Arbeit ist es, eine echtzeitfähige Erkennung der Fahrspurmarkierungen aus dem Video-Bilddatenstrom zu realisieren und die Position
der Markierungen relativ zum Fahrzeug anzugeben. Um eine geometrisch richtige Darstellung zu erhalten, soll zunächst eine Bestimmung der der Markierungen relativ zum Fahrzeug anzugeben. Um eine geometrisch richtige Darstellung zu erhalten, soll zunächst eine Bestimmung der
intrinsischen und extrinsischen Kamera-Kalibrierung durchgeführt werden. Mit den so bestimmten intrinsischen Parametern so dann eine intrinsischen und extrinsischen Kamera-Kalibrierung durchgeführt werden. Mit den so bestimmten intrinsischen Parametern soll dann eine
Rektifizierung der Bilder durchgeführt werden. Rektifizierung der Bilder durchgeführt werden.
Auf den rektifizierten Bildern soll dann die eigentliche Erkennung der Spurmarkierungen erfolgen. Dies kann entweder kanten-basiert oder mit Auf den rektifizierten Bildern soll dann die eigentliche Erkennung der Spurmarkierungen erfolgen. Dies kann entweder kanten-basiert oder mit
tiefen neuronalen Netzen erfolgen. Die extrinsische Kalibrierung soll dann genutzt werden, um die Position der Markierungen in tiefen neuronalen Netzen erfolgen. Die extrinsische Kalibrierung soll dann genutzt werden, um die Position der Markierungen in
Fahrzeug-Koordinaten umzurechnen. Zusätzlich kann die Farbinformation des Bildes genutzt werden um zwischen weißen und gelben Linien zu Fahrzeug-Koordinaten umzurechnen. Zusätzlich kann die Farbinformation des Bildes genutzt werden, um zwischen weißen und gelben Linien zu
unterscheiden. unterscheiden.
Gegebenenfalls kann auch das zeitliche Tracking eines Spurmodells umgesetzt werden. Gegebenenfalls kann auch das zeitliche Tracking eines Spurmodells umgesetzt werden.
Die Bildverarbeitung sollte unter \gls{ROS} auf der Jetson-nano Hardware unter \gls{ROS} in Echtzeit lauffähig sein. Eine erste Die Bildverarbeitung sollte auf der Jetson-nano Hardware unter \gls{ROS} in Echtzeit lauffähig sein. Eine erste
Implementierung kann mit Python erfolgen. Für den längerfristigen Einsatz wäre eine Umsetzung in \gls{C++} mit \glspl{ROS Nodelet} Implementierung kann mit Python erfolgen. Für den längerfristigen Einsatz wäre eine Umsetzung in \gls{C++} mit \glspl{ROS Nodelet}
wünschenswert. wünschenswert.
......
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
Echtzeit auszustatten. Echtzeit auszustatten.
Durch eine intrinsische Kalibrierung der Kamera und das Erstellen einer \gls{ROS Node}, welche diese Kalibrierung anwendet, konnte das von der Durch eine intrinsische Kalibrierung der Kamera und das Erstellen einer \gls{ROS Node}, welche diese Kalibrierung anwendet, konnte das von der
Kamera erhaltene Bild deutlich verbessert und vorhanden Verzerrungen korrigiert werden. Insbesondere die radiale Verzerrung, die gerade Linien wie Kamera erhaltene Bild deutlich verbessert und vorhandene Verzerrungen korrigiert werden. Insbesondere die radiale Verzerrung, die gerade Linien wie
zum Beispiel Fahrspurmarkierungen im Bild gekrümmt erscheinen lässt, konnte annähern vollständig entfernt werden. Dies verbesserte die zum Beispiel Fahrspurmarkierungen im Bild gekrümmt erscheinen lässt, konnte annähernd vollständig entfernt werden. Dies verbesserte die
Voraussetzungen für die nachfolgenden Schritte. Voraussetzungen für die nachfolgenden Schritte.
Außerdem wurde die Erkennung der Fahrspurmarkierungen erfolgreich implementiert. Der Algorithmus wurde in der Programmiersprache \gls{python} Außerdem wurde die Erkennung der Fahrspurmarkierungen erfolgreich implementiert. Der Algorithmus wurde in der Programmiersprache \gls{python}
entwickelte und getestet, wobei insbesondere die einfache Syntax und gute Debuggmöglichkeiten das Vorgehen vereinfacht haben. Da \gls{python} aber entwickelt und getestet, wobei insbesondere die einfache Syntax und gute Debuggmöglichkeiten das Vorgehen vereinfacht haben. Da \gls{python} aber
nicht performant genug war, um die Echtzeitanforderung zu erfüllen, wurde die eigentliche Implementierung in \gls{C++} umgesetzt. nicht performant genug war, um die Echtzeitanforderung zu erfüllen, wurde die eigentliche Implementierung in \gls{C++} umgesetzt.
Dadurch wurde die Laufzeit des Programms deutlich beschleunigt und die Echtzeitfähigkeit gewährleistet. Dadurch wurde die Laufzeit des Programms deutlich beschleunigt und die Echtzeitfähigkeit gewährleistet.
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
Fahrspurmarkierungen gezogen werden. Die vier Punkte eines Linienpaares konnten dann als Kontur abgespeichert und die Mittellinie berechnet Fahrspurmarkierungen gezogen werden. Die vier Punkte eines Linienpaares konnten dann als Kontur abgespeichert und die Mittellinie berechnet
werden. werden.
Zur Veranschaulichung wurden die Konturen in ein leeres Bild eingezeichnet und dieses Ebenfalls veröffentlicht. Dadruch ist ein direkter Vergleich Zur Veranschaulichung wurden die Konturen in ein leeres Bild eingezeichnet und dieses ebenfalls veröffentlicht. Dadruch ist ein direkter Vergleich
zwischen Originalbild und gefunden Konturen in Echtzeit möglich. zwischen Originalbild und gefunden Konturen in Echtzeit möglich.
Eine weitere Optimierung durch eine eigene Implementierung des \gls{canny} und Kombinieren von diesem mit dem Klassifizierungsschritt, war nicht Eine weitere Optimierung durch eine eigene Implementierung des \gls{canny} und Kombinieren von diesem mit dem Klassifizierungsschritt, war nicht
......
\chapter{Fahrspurerkennung} \label{chap: implementation} \chapter{Fahrspurerkennung} \label{chap: implementation}
Dieses Kapitel thematisiert, wie die Erkennung der Fahrspurmarkierungen umgesetzt wir. Begonnen wird mit einer konzeptionellen Version in Dieses Kapitel thematisiert, wie die Erkennung der Fahrspurmarkierungen umgesetzt wird. Begonnen wird mit einer konzeptionellen Version in
Python, mit der der Ablauf des Algorithmus geplant und getestet wird. Danach wird die Logik in einer \gls{C++} \gls{ROS Node} umgesetzt, um die Python, mit der der Ablauf des Algorithmus geplant und getestet wird. Danach wird die Logik in einer \gls{C++} \gls{ROS Node} umgesetzt, um die
best möglichst Performance zu erhalten. bestmögliche Performance zu erhalten.
\begin{figure} \begin{figure}
\includegraphics[page=1,scale=.85]{svg/Topics_makerDetection.pdf} \includegraphics[page=1,scale=.85]{svg/Topics_makerDetection.pdf}
\caption{Zusammenhang der Fahrspurmarkierung-Erkennungs \gls{ROS Node} mit den bestehenden \glspl{ROS Node}} \caption{Zusammenhang der Fahrspurmarkierung-Erkennungs-\gls{ROS Node} mit den bestehenden \glspl{ROS Node}}
\label{fig: topics marker detection} \label{fig: topics marker detection}
\end{figure} \end{figure}
Wie diese neuen \glspl{ROS Node} mit den bestehenden \glspl{ROS Node} in Beziehung stehen soll, ist in \autoref{fig: topics marker detection} Wie diese neuen \glspl{ROS Node} mit den bestehenden \glspl{ROS Node} in Beziehung stehen soll, ist in \autoref{fig: topics marker detection}
grafisch dargestellt. Neu ist dabei, dass diese \gls{ROS Node} das korrigierte Schwarz-Weiß Bild von der in \autoref{sec: undistort Node} grafisch dargestellt. Neu ist dabei, dass diese \gls{ROS Node} das korrigierte Schwarz-Weiß Bild von der in \autoref{sec: undistort Node}
beschriebenen entzerrer \gls{ROS Node} abonniert und die eigenen Ergebnis als neues \gls{Topic} zur Verfügung stellt. beschriebenen entzerrer \gls{ROS Node} abonniert und das eigene Ergebnis als neues \gls{Topic} zur Verfügung stellt.
\section{Konzeptionierung in Python} \section{Konzeptionierung in Python}
Die Entwicklung und Konzeptionierung des Algorithmus Erfolg in \gls{python}, da diese Sprache nicht kompiliert werden muss, was das Testen Die Entwicklung und Konzeptionierung des Algorithmus erfolgt in \gls{python}. Diese Sprache muss nicht kompiliert werden und hat eine sehr
beschleunigt, und generell einfacher zu verwenden ist. einfache Syntax, wodurch das Testen beschleunigt wird und sie generell einfacher zu verwenden ist.
Der Algorithmus lässt sich in mehre Einzelschritte aufteilen und wird daher in den folgen Unterkapitel beschreiben. Zur Übersicht Der Algorithmus lässt sich in mehrere Einzelschritte aufteilen, welche in den folgenden Unterkapiteln beschreiben im Einzelnen beschrieben
ist aber der gesamte Ablauf in \autoref{fig: PAP} vereinfacht skizziert. Angefangen wird dort mit dem Erhalten des Bildes, womit sowohl sind. Zur Übersicht ist aber der gesamte Ablauf in \autoref{fig: PAP} skizziert. Angefangen wird dort mit dem Erhalten des Bildes,
manuelles laden eines Beispielbildes, als auch das Erhalten des Bildes über ein \gls{Topic} gemeint ist. womit sowohl manuelles Laden eines Beispielbildes, als auch das Erhalten des Bildes über ein \gls{Topic} gemeint ist.
\begin{figure} \begin{figure}
\includegraphics[scale=.85]{svg/PAP_marker_erkennung.pdf} \includegraphics[scale=.85]{svg/PAP_marker_erkennung.pdf}
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
\begin{figure} \begin{figure}
\includegraphics[width=.6\textwidth]{img/Marks_original.png} \includegraphics[width=.6\textwidth]{img/Marks_original.png}
\caption{Ein Beispiel Bild an dem der Ablauf demonstriert wird} \caption{Beispielbild an dem der Ablauf demonstriert wird}
\label{fig: beispiel bild} \label{fig: beispiel bild}
\end{figure} \end{figure}
...@@ -43,16 +43,15 @@ ...@@ -43,16 +43,15 @@
\pagebreak \pagebreak
\subsection{Kantenerkennung mittels Canny-Edge-Detektor} \subsection{Kantenerkennung mittels Canny-Edge-Detektor}
Begonnen wird mit der Detektion von Kante im Bild. Dazu wird das Bild zuerst mit \gls{OpenCV} geladen. \todo{Absatz Ja/Nein?} Begonnen wird mit der Detektion von Kanten im Bild. Dazu wird das Bild zuerst mit \gls{OpenCV} geladen.
Um kleine Störungen im Bild, welche bestehende Kanten verzerren oder als falsche Kante erkannt werden könnten, zu reduzieren, wird das Um kleine Störungen im Bild, welche bestehende Kanten verzerren oder als falsche Kante erkannt werden könnten, zu reduzieren, wird das
Bild mit einem \glslink{gauss-filter}{Gaußschen Filter} geglättet. Es wird ein $3\!\times\!3$ \gls{Kernel} mit einer Normalverteilung von Bild mit einem \glslink{gauss-filter}{Gaußschen Filter} geglättet. Es wird ein $3\!\times\!3$ \gls{Kernel} mit einer Normalverteilung von
$\sigma=1,5$ verwendet. \gls{OpenCV} stell hierzu die Funktion \lstinline{GaussianBlur()} zur Verfügung, der das geladene Bild, die $\sigma=1,5$ verwendet. \gls{OpenCV} stellt hierzu die Funktion \lstinline{GaussianBlur()} zur Verfügung, der das geladene Bild, die
Kegelgröße und der Wert für $\sigma$ übergeben wird. Kernelgröße und der Wert für $\sigma$ übergeben wird.
Die eigentliche Kantenerkennung wird mittels eines \glspl{canny} durchgeführt. Dabei handelt es sich um einen von John Canny 19983 Die eigentliche Kantenerkennung wird mittels eines \glspl{canny} durchgeführt. Dabei handelt es sich um einen von John Canny 19983
entwickelten und in \cite{Canny:computationAlapproachEdgeDetection} veröffentlichten Algorithmus. Dieser bestimmt für jeden \gls{Pixel} den entwickelten und in \cite{Canny:computationAlapproachEdgeDetection} veröffentlichten Algorithmus. Dieser bestimmt für jeden \gls{Pixel} den
Gradientenbetrag der Gradienten in X- und Y-Richtung. Dann werden diejenigen \gls{Pixel} unterdrückt, welche entlang der Gradientenrichtung kein Gradientenbetrag der Gradienten in $x$ und $y$-Richtung. Dann werden diejenigen \gls{Pixel} unterdrückt, welche entlang der Gradientenrichtung kein
Maximum darstellen. Zum Abschluss wird das Bild mit einem Hysterese-Schwellwert binarisiert. Das bedeutet, dass alle \gls{Pixel} über einem Maximum darstellen. Zum Abschluss wird das Bild mit einem Hysterese-Schwellwert binarisiert. Das bedeutet, dass alle \gls{Pixel} über einem
initialen, oberen Schwellwert als Kanten gesetzt werden und mittels eines zweiten, niedrigeren Schwellwerte, Lücken zwischen diesen Pixeln initialen, oberen Schwellwert als Kanten gesetzt werden und mittels eines zweiten, niedrigeren Schwellwerte, Lücken zwischen diesen Pixeln
geschlossen werden. \cite{Nischwitz:Computergrafik2} geschlossen werden. \cite{Nischwitz:Computergrafik2}
...@@ -63,7 +62,7 @@ ...@@ -63,7 +62,7 @@
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
style=example, style=example,
caption={Laden, glätten eines Bildes und durchführen der Kantenerkennung mit \gls{OpenCV}}, caption={Laden, Glätten eines Bildes und Durchführen der Kantenerkennung mit \gls{OpenCV}},
label=code: canny, label=code: canny,
language=Python language=Python
] ]
...@@ -77,8 +76,8 @@ ...@@ -77,8 +76,8 @@
\end{lstlisting} \end{lstlisting}
Wird dieser Code auf das Beispielbild \ref{fig: beispiel bild} angewendet und das Ergebnis des \glspl{canny} ausgegeben, ergibt sich Wird dieser Code auf das Beispielbild \ref{fig: beispiel bild} angewendet und das Ergebnis des \glspl{canny} ausgegeben, ergibt sich
\autoref{fig: canny edges}. Im Gegensatz zu alternativen, wie einer reinen Grabentenbetrachtung, liefert der \gls{canny} \autoref{fig: canny edges}. Im Gegensatz zu Alternativen, wie einer reinen Gradientenbetrachtung, liefert der \gls{canny}
Kantenmarkierungen (hier in weiß) die nur ein\todo{einen?} \gls{Pixel} breit sind. Dies ermöglicht die in den folgenden Unterkapiteln Kantenmarkierungen, hier in Weiß, die nur einen \gls{Pixel} breit sind. Dies ermöglicht die in den folgenden Unterkapiteln
beschriebenen Schritte. beschriebenen Schritte.
\begin{figure} \begin{figure}
...@@ -90,15 +89,15 @@ ...@@ -90,15 +89,15 @@
\subsection{Klassifizierung der Kantenpixel} \subsection{Klassifizierung der Kantenpixel}
Nur die Identifikation von Pixeln als Kantenpixeln reicht nicht aus, um bereits Linienmarkierungen zu erkennen. Uns Menschen fällt es zwar Nur die Identifikation von Pixeln als Kantenpixeln reicht nicht aus, um Linienmarkierungen zu erkennen. Menschen fällt es zwar
Leicht in \autoref{fig: canny edges} die gesuchten Linien zu identifizieren, für den Algorithmus handelt es sich aber nur um eine leicht in \autoref{fig: canny edges} die gesuchten Linien zu identifizieren, für den Algorithmus handelt es sich aber nur um eine
"zufällige"\todo{"zufällige" OK?} Ansammlung von weißen Pixeln. Es werden weiter Informationen benötigt. scheinbar zufällige Ansammlung von weißen Pixeln. Es werden weitere Informationen benötigt.
Deshalb werden jedem Kantenpixel eine Klasse entsprechend seiner Orientierung zugeordnet. Um die Datenmenge gering und die Laufzeit Deshalb wird jedem Kantenpixel eine Klasse entsprechend seiner Orientierung zugeordnet. Um die Datenmenge gering und die Laufzeit
schnell zu halten, werden lediglich die vier Klassen \emph{Vertikal}, \emph{Horizontal}, \emph{Diagonal 1} und \emph{Diagonal 2} schnell zu halten, werden lediglich die vier Klassen \emph{Vertikal}, \emph{Horizontal}, \emph{Diagonal 1} und \emph{Diagonal 2}
verwendend. Zusätzlich wird noch die Richtungsinformation als Vorzeichen abgespeichert. verwendend. Zusätzlich wird noch die Richtungsinformation als Vorzeichen abgespeichert.
Die Klassifizierte erfolgt anhand der \gls{Gradientenorientierung} eines Pixels. Dazu werden mit $3\!\times\!3$ Sobel-\glspl{Kernel} die Die Klassifizierung erfolgt anhand der \gls{Gradientenorientierung} eines Pixels. Dazu werden mit $3\!\times\!3$ Sobel-\glspl{Kernel} die
Gradienten $d_x$ und $d_y$ bestimmt. Mit der \lstinline{atan2()} Funktion kann aus diesen beiden Größen der Winkel des Gradientenvektors Gradienten $d_x$ und $d_y$ bestimmt. Mit der \lstinline{atan2()} Funktion kann aus diesen beiden Größen der Winkel des Gradientenvektors
$\vec{G}$ berechnet werden. Mit diesem Winkel kann nun entsprechen der \autoref{fig: gadienten orientierung} die Klasse bestimmt werden. $\vec{G}$ berechnet werden. Mit diesem Winkel kann nun entsprechen der \autoref{fig: gadienten orientierung} die Klasse bestimmt werden.
Dabei ist zu beachten das $\vec{G}$ immer orthogonal auf der eigentlichen Kante steht, deshalb ist die Klasse \emph{Vertikal} auch auf der Dabei ist zu beachten das $\vec{G}$ immer orthogonal auf der eigentlichen Kante steht, deshalb ist die Klasse \emph{Vertikal} auch auf der
...@@ -126,9 +125,9 @@ ...@@ -126,9 +125,9 @@
\end{tabular} \end{tabular}
\end{table} \end{table}
Um die Klassifizierung in \gls{python} durchzuführen, wird zuerst ein weiteres, leere 8-Bit Bild mit identischer Größe angelegt. Dann wird Um die Klassifizierung in \gls{python} durchzuführen, wird zuerst ein weiteres, leeres 8-Bit Bild mit identischer Größe angelegt. Dann
erneut über alle \gls{Pixel} des Bildes iteriert. Da allerdings die meisten \gls{Pixel} schwarz und damit uninteressant sind, können diese direkt wird erneut über alle \gls{Pixel} des Bildes iteriert. Da allerdings die meisten \gls{Pixel} schwarz und damit uninteressant sind, können
verworfen werden. Für alle verbleibenden, weißen \gls{Pixel} wird die Klassifizierung durchgeführt. diese direkt verworfen werden. Für alle verbleibenden, weißen \gls{Pixel} wird die Klassifizierung durchgeführt.
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
...@@ -173,10 +172,10 @@ ...@@ -173,10 +172,10 @@
\end{split} \end{split}
\end{equation} \end{equation}
Zuerst die Gradienten $d_x$ und $d_y$ ermittel. Dazu wird die $3\!\times\!3$ \gls{Pixelnachbarschaft} des aktuellen Pixels elementweise mit dem Dazu werden zuerst die Gradienten $d_x$ und $d_y$ ermittelt. Dazu wird die $3\!\times\!3$ \gls{Pixelnachbarschaft} des aktuellen Pixels
jeweiligen Sobel-\gls{Kernel} multipliziert und die Summe der Ergebnismatrix gebildet (siehe \autoref{eq: dx dy}). Das Pythonpaket elementweise mit dem jeweiligen Sobel-\gls{Kernel} multipliziert und die Summe der Ergebnismatrix gebildet (siehe \autoref{eq: dx dy}).
\lstinline{numpy} stellt hierfür sehr hilfreiche Funktion zum Arbeiten mit Matrizen zu Verfügung. Dadurch lässt sich diese Operation in Das Pythonpaket \lstinline{numpy} stellt hierfür sehr hilfreiche Funktion zum Arbeiten mit Matrizen zur Verfügung. Dadurch lässt sich
wenigen Zeilen durchführen, wie \autoref{code: dx dy} gezeigt. diese Operation in wenigen Zeilen durchführen, wie \autoref{code: dx dy} gezeigt.
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
...@@ -190,11 +189,11 @@ ...@@ -190,11 +189,11 @@
dy = np.sum(nh * SOBEL_Y) dy = np.sum(nh * SOBEL_Y)
\end{lstlisting} \end{lstlisting}
Mit diesen werden wird nun die \lstinline{atan2(dy,dx)} Funktion aufgerufen. Diese gibt einen Winkel in rad zurück, welcher zur besseren Mit diesen wird nun die \lstinline{atan2(dy,dx)} Funktion aufgerufen. Diese gibt einen Winkel in rad zurück, welcher zur besseren
Nachvollziehbarkeit in Grad umgerechnet wird. Nachvollziehbarkeit in Grad umgerechnet wird.
Nun werden durch eine Folge von Bedingungen die Klasse des aktuellen Pixels bestimmt. Zuerst wird das Vorzeichen bestimmt und im 5. Bit Durch eine Folge von Bedingungen wird nun die Klasse des aktuellen Pixels bestimmt. Zuerst wird das Vorzeichen bestimmt und im 5. Bit
abgespeichert. Dies vereinfacht die folgenden Abfragen, da für die \emph{Vertikale} und \emph{Horizontale} Klasse der Betrag des Winkels abgespeichert. Dies vereinfacht die folgenden Abfragen, da für die \emph{Vertikal} und \emph{Horizontal} Klasse der Betrag des Winkels
ausreicht. ausreicht.
Ist die Klasse bestimmt, wird das entsprechende Bit des Pixels gesetzt. Die Umsetzung in Python ist in Ist die Klasse bestimmt, wird das entsprechende Bit des Pixels gesetzt. Die Umsetzung in Python ist in
...@@ -203,7 +202,7 @@ ...@@ -203,7 +202,7 @@
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
style=example, style=example,
caption={Durchführen der Klassifizierung mittel des bestimmten Winkels}, caption={Durchführen der Klassifizierung mittels des bestimmten Winkels},
label=code: python Klassifizierung, label=code: python Klassifizierung,
language=Python language=Python
] ]
...@@ -222,7 +221,7 @@ ...@@ -222,7 +221,7 @@
pixel_info[u, v] |= D2 if not pixel_info[u, v] else D1 pixel_info[u, v] |= D2 if not pixel_info[u, v] else D1
\end{lstlisting} \end{lstlisting}
Wurde jeder Kantenpixel klassifiziert, ist der Vorgang beendet. Zur Veranschaulichung wurde ein Bild erstellt, wo jeder Klasse und Wurde jeder Kantenpixel klassifiziert, ist der Vorgang beendet. Zur Veranschaulichung wurde ein Bild erstellt, in dem jeder Klasse und
Vorzeichen eine eindeutige Farbe zugeordnet ist. So ist genau zu erkennen, welche Kanten derselben Klasse zugeordnet wurden. Diese Bild Vorzeichen eine eindeutige Farbe zugeordnet ist. So ist genau zu erkennen, welche Kanten derselben Klasse zugeordnet wurden. Diese Bild
ist in \autoref{fig: classified edges} gezeigt. ist in \autoref{fig: classified edges} gezeigt.
...@@ -237,8 +236,8 @@ ...@@ -237,8 +236,8 @@
\subsubsection{Genauigkeit der Klassifizierung} \label{sub: genauigkeit klassifizierung} \subsubsection{Genauigkeit der Klassifizierung} \label{sub: genauigkeit klassifizierung}
Die Klassifizierung erfolgt leider nicht immer völlig zuverlässig. Gut klassifizierte Kanten haben für die gesamte Länge der Linie Die Klassifizierung erfolgt nicht immer völlig zuverlässig. Gut klassifizierte Kanten haben für die gesamte Länge der Linie
dieselbe Klasse erhalten wie es im vergrößerten Bildausschnitt \autoref{fig: gute kante} zu sehen ist. Im Gegensatz dazu haben dieselbe Klasse erhalten, wie es im vergrößerten Bildausschnitt \autoref{fig: gute kante} zu sehen ist. Im Gegensatz dazu haben
unzuverlässig klassifizierte Kanten mehrere Klassen in einem engen Bereich und wechseln häufig sogar mehrfach zwischen mehreren unzuverlässig klassifizierte Kanten mehrere Klassen in einem engen Bereich und wechseln häufig sogar mehrfach zwischen mehreren
Klassen, wie das Beispiel in \autoref{fig: schlechte kante} zeigt. Klassen, wie das Beispiel in \autoref{fig: schlechte kante} zeigt.
...@@ -251,11 +250,11 @@ ...@@ -251,11 +250,11 @@
\label{fig: schlechte kante} \label{fig: schlechte kante}
\includegraphics[scale=4]{img/Marks_classification_cut2.png} \includegraphics[scale=4]{img/Marks_classification_cut2.png}
} }
\caption{Vergleich gut und schlecht klassifizierte Bildbereiche} \caption{Vergleich von gut und schlecht klassifizierten Bildbereichen}
\end{figure} \end{figure}
Durch Störungen und generell schlechtere Bildqualität in weiter von der Kamera entfernten Bildbereichen weisen vor allem die äußeren Durch Störungen und generell schlechtere Bildqualität in weiter von der Kamera entfernten Bildbereichen weisen vor allem die äußeren
Linien viele dieser Ungenauigkeiten auf. Das führt dazu, das die nachfolgende Logik viele einzelne, kleine Linien anstatt der Linien viele dieser Ungenauigkeiten auf. Das führt dazu, dass die nachfolgende Logik viele einzelne, kleine Linien anstatt der
vollständigen, durchgängigen Linie, erkennt. vollständigen, durchgängigen Linie, erkennt.
Die zentralen Linienmarkierungen der eigenen Spur werden aber zuverlässig genug klassifiziert. Die zentralen Linienmarkierungen der eigenen Spur werden aber zuverlässig genug klassifiziert.
...@@ -263,20 +262,20 @@ ...@@ -263,20 +262,20 @@
\subsection{Linienbildung} \label{sec: linien finden} \subsection{Linienbildung} \label{sec: linien finden}
Mit den Informationen über die Kantenklasse kann nun der eigentliche Prozess der Linienerkennung erfolgen. Dieser glieder sich in zwei Mit den Informationen über die Kantenklasse kann nun der eigentliche Prozess der Linienerkennung erfolgen. Dieser gliedert sich in zwei
Schritte, das Zusammenfassen von gleich klassifizierten Kantenpixeln zu durchgängigen Linien und das Zusammenfassen von Linien zu einer Schritte, das Zusammenfassen von gleich klassifizierten Kantenpixeln zu durchgängigen Linien und das Zusammenfassen von Linien zu einer
Fahrspurmarkierung. Fahrspurmarkierung.
Für den ersten Schritt ist es ein weiters mal nötig über das gesamte Bild zu iteriert. Auch diesmal können wieder alle schwarzen Pixel Für den ersten Schritt ist es ein weiteres Mal nötig, über das gesamte Bild zu iteriert. Auch diesmal können wieder alle schwarzen Pixel
übersprungen werden. Wird ein klassifiziertes \gls{Pixel} gefunden, muss überprüft werden, ob es sich um ein Startpixel handelt. Startpixel sind übersprungen werden. Wird ein klassifiziertes \gls{Pixel} gefunden, muss überprüft werden, ob es sich um ein Startpixel handelt. Startpixel sind
Pixel, die keine Nachbarn in der ihrer Klasse entsprechenden Ursprungsrichtung haben. Zum Beispiel wäre ein \gls{Pixel} der \emph{Vertikal} Pixel, die keine Nachbarn in der ihrer Klasse entsprechenden Ursprungsrichtung haben. Zum Beispiel wäre ein \gls{Pixel} der \emph{Vertikal}
Klasse ein Startpixel, wenn sich direkt oder diagonal über ihm keine weiteren \gls{Pixel} derselben Klasse befinden. Klasse ein Startpixel, wenn sich direkt oder diagonal über ihm keine weiteren \gls{Pixel} derselben Klasse befinden.
Ist ein Startpixel gefunden, wird es für später abgespeichert. Nun wird der Linie zu ihrem Ende gefolgt. Dazu wird der nächste Ist ein Startpixel gefunden, wird es für später abgespeichert. Nun wird der Linie zu ihrem Ende gefolgt. Dazu wird der nächste
Nachbarpixel gesucht. Da die grobe Richtung entsprechend der Klasse bekannt ist, müssen hier nicht alle Nachbarpixel überprüft werden. Nachbarpixel gesucht. Da die grobe Richtung entsprechend der Klasse bekannt ist, müssen hier nicht alle Nachbarpixel überprüft werden.
Beim Beispiel mit der \emph{Vertikal} Klasse müssten die \gls{Pixel} direkt und diagonal unterhalb betrachtet werden. Existiert ein Nachbar wird Beim Beispiel mit der \emph{Vertikal} Klasse müssten die \gls{Pixel} direkt und diagonal unterhalb betrachtet werden. Existiert ein
dieser ausgewählt und der Prozess wiederholt. Dabei werden alle bereits besuchten \gls{Pixel} aus dem Bild gelöscht, damit sich nicht nocheinaml Nachbar wird dieser ausgewählt und der Prozess wiederholt. Dabei werden alle bereits besuchten \gls{Pixel} aus dem Bild gelöscht, damit
untersucht werden. sich nicht noch einmal untersucht werden.
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
...@@ -302,8 +301,8 @@ ...@@ -302,8 +301,8 @@
lines.append(l) lines.append(l)
\end{lstlisting} \end{lstlisting}
Hat ein \gls{Pixel} keine weiteren Nachbarn, ist er der Endpunkt dieser Linie. Start und Endpunkt werden in ein Linienobjekt zusammengefasst Hat ein \gls{Pixel} keine weiteren Nachbarn, ist er der Endpunkt dieser Linie. Start- und Endpunkt werden in ein Linienobjekt
und abgespeichert. Zusätzliche wird ebenfalls die Orientierungsklasse mit abgespeichert. zusammengefasst und abgespeichert. Zusätzliche wird ebenfalls die Orientierungsklasse mit abgespeichert.
Mittels Start- und Endpunkt kann außerdem die Länge der Linie bestimmt werden. Da durch die in \autoref{sub: genauigkeit klassifizierung} Mittels Start- und Endpunkt kann außerdem die Länge der Linie bestimmt werden. Da durch die in \autoref{sub: genauigkeit klassifizierung}
beschriebenen Störungen viele kurze Linien gefunden werden, deren Berücksichtigung zu viel Rechenzeit in Anspruch nehmen würde, werden beschriebenen Störungen viele kurze Linien gefunden werden, deren Berücksichtigung zu viel Rechenzeit in Anspruch nehmen würde, werden
...@@ -313,11 +312,11 @@ ...@@ -313,11 +312,11 @@
Orientierungsklasse entstanden ist. Orientierungsklasse entstanden ist.
\medskip \medskip
Da ein Linienmarker immer aus einer linken und einer rechten Kante besteht, können wir diese durch Bilden von Linienpaaren gleicher Da ein Linienmarker immer aus einer linken und einer rechten Kante besteht, kann dieser durch Bilden von Linienpaaren gleicher
Orientierung, aber unterschiedlichem Vorzeichen, die in geringem Abstand zueinander liegen, identifizieren. Orientierung, aber unterschiedlichem Vorzeichen, die in geringem Abstand zueinander liegen, identifiziert werden.
Dazu werden die Elemente der entsprechenden Listen nacheinander miteinander verglichen, biss ein passendes Paar gefunden wurde. Ein Dazu werden die Elemente der entsprechenden Listen nacheinander miteinander verglichen, bis ein passendes Paar gefunden wurde. Ein
Beispiel ist in \autoref{code: find pairs} für Linienmarkierungen der Orientierung \emph{Digital 1} gezeigt. Es wird für jeden Kandidaten Beispiel ist in \autoref{code: find pairs} für Linienmarkierungen der Orientierung \emph{Diagonal 1} gezeigt. Es wird für jeden Kandidaten
aus der ersten Liste ein Partner in der zweiten Liste gesucht. Ein solcher ist gefunden, wenn die Start- und Endpunkte beider Linien aus der ersten Liste ein Partner in der zweiten Liste gesucht. Ein solcher ist gefunden, wenn die Start- und Endpunkte beider Linien
innerhalb bestimmter Bereiche liegen. innerhalb bestimmter Bereiche liegen.
...@@ -360,7 +359,7 @@ ...@@ -360,7 +359,7 @@
\subsubsection{Komplexere Szenen} \subsubsection{Komplexere Szenen}
Nicht jede Situation, auf die der Roboter treffen kann, führt zu so guten Ergebnissen wie das gezeigte Beispiel. Daher sind in Nicht jede Situation, auf die der Roboter treffen kann, führt zu so guten Ergebnissen wie das gezeigte Beispiel. Daher sind in
\autoref{fig: vergleich szenen} einige weitere Beispiele und die darin detektireten Markierungen im direkten Vergleich gezeigt. \autoref{fig: vergleich szenen} einige weitere Beispiele und die darin detektierten Markierungen im direkten Vergleich gezeigt.
In \ref{subfig: demo C} sind die Linienbegrenzungen nahe am Fahrzeug durchgezogen und werden erst in größerer Entfernung gestrichelt. In \ref{subfig: demo C} sind die Linienbegrenzungen nahe am Fahrzeug durchgezogen und werden erst in größerer Entfernung gestrichelt.
Hier war die Erkennung der durchgezogenen Teile sehr gut möglich, jedoch kam es zu einer unpräzisen Identifizierung im oberen Teil der Hier war die Erkennung der durchgezogenen Teile sehr gut möglich, jedoch kam es zu einer unpräzisen Identifizierung im oberen Teil der
...@@ -369,8 +368,8 @@ ...@@ -369,8 +368,8 @@
Bei der Szene \ref{subfig: demo A} handelt es sich um eine Kurve. Dies erschwert die Erkennung deutlich, da eine durchgängige Bei der Szene \ref{subfig: demo A} handelt es sich um eine Kurve. Dies erschwert die Erkennung deutlich, da eine durchgängige
Klassifizierung einer Kante nicht garantiert ist. Daher sind auch nicht alle Spurmarkierungen identifiziert. Klassifizierung einer Kante nicht garantiert ist. Daher sind auch nicht alle Spurmarkierungen identifiziert.
Der letzte Vergleich \ref{subfig: demo B} zeigt eine Szene mit vollständig durchgezogener Linie. Dies ist aus dem Grund schwierig, das Der letzte Vergleich \ref{subfig: demo B} zeigt eine Szene mit vollständig durchgezogener Linie. Dies ist aus dem Grund schwierig,
die Wahrscheinlichkeit einer Störung durch die hohe Pixelanzahl sehr groß ist. Daher wurde die rechte Kante der Linie auch nicht dass die Wahrscheinlichkeit einer Störung durch die hohe Pixelanzahl sehr groß ist. Daher wurde die rechte Kante der Linie auch nicht
durchgängig erkannt und der gefunden Marker wirkt verzehrt. durchgängig erkannt und der gefunden Marker wirkt verzehrt.
\begin{figure} \begin{figure}
...@@ -393,16 +392,16 @@ ...@@ -393,16 +392,16 @@
\section{Implementierung in eine ROS Node} \section{Implementierung in eine ROS Node}
Um den Spurmarkererkennungs-Algorithmus auf jedes Kamerabild anwenden zu können, wird er in einer \gls{ROS Node} umgesetzt. Da der Python-Code Um den Algorithmus zur Erkennung von Spurmarkierungen auf jedes Kamerabild anwenden zu können, wird er in einer \gls{ROS Node} umgesetzt. Da
bereits auf einem leistungsfähigen Entwickler-PC Laufzeiten von $>0,25\,\s$ hat, wird diese im \gls{C++} implementiert. Dies wird die der Python-Code bereits auf einem leistungsfähigen Entwickler-PC Laufzeiten von $>0,25\,\s$ hat, wird die Programmiersprache \gls{C++} für die
Performance deutlich verbessern. \gls{ROS Node} gewählt. Dadurch sollte sich die Performance deutlich verbessern.
\subsection{Erstellung des Quellcodes} \subsection{Erstellung des Quellcodes}
Die Beziehung der neuen \gls{ROS Node} zu den bestehenden \glspl{ROS Node} wurde bereits in \autoref{fig: topics marker detection} skizziert. Die Beziehung der neuen \gls{ROS Node} zu den bestehenden \glspl{ROS Node} wurde bereits in \autoref{fig: topics marker detection} skizziert.
Dort sieht man, dass die \gls{ROS Node} wir mit dem Namen \lstinline{lane_marker_detection} inizialisiert wird. Außerdem wird das \gls{Topic} Dort sieht man, dass für die \gls{ROS Node} der Namen \lstinline{lane_marker_detection} gewählt wird. Außerdem wird das \gls{Topic}
\lstinline{/img/gray} von der Entzerrer-\gls{ROS Node} abonniert, um jedes Schwarz-Weiß Bild zu bekommen. Das Bild mit den eingezeichneten, \lstinline{/img/gray} von der Entzerrer-\gls{ROS Node} abonniert, um jedes Schwarz-Weiß Bild zu bekommen. Das Bild mit den eingezeichneten,
detektierten Spurmarkern wird nach Durchlauf des Algorithmus auf dem eigenen \gls{Topic} \lstinline{/img/lanemarkings} veröffentlichte. detektierten Spurmarkierungen wird nach Durchlauf des Algorithmus auf dem eigenen \gls{Topic} \lstinline{/img/lanemarkings} veröffentlicht.
Beim Abonnieren des \lstinline{/img/gray} Topics wird die \gls{Callback} \lstinline{callback_image()} angehängt, sodass diese von \gls{ROS} Beim Abonnieren des \lstinline{/img/gray} Topics wird die \gls{Callback} \lstinline{callback_image()} angehängt, sodass diese von \gls{ROS}
für jedes Bild aufgerufen und das Bild an sie übergeben wird. Da dieses in einem \gls{ROS} eigenen Bild-Datentyp übergeben wird, \gls{OpenCV} für jedes Bild aufgerufen und das Bild an sie übergeben wird. Da dieses in einem \gls{ROS} eigenen Bild-Datentyp übergeben wird, \gls{OpenCV}
...@@ -428,8 +427,9 @@ ...@@ -428,8 +427,9 @@
cv::Canny(image, canny, 180, 50); cv::Canny(image, canny, 180, 50);
\end{lstlisting} \end{lstlisting}
Die Implementierung der Kantenklassifizierung in \gls{C++} lauf im groben sehr ähnlich zur \gls{python}, die wichtigsten Unterschied sind Die Implementierung der Kantenklassifizierung in \gls{C++} läuft im Groben sehr ähnlich zur \gls{python}-Version, die wichtigsten
die For-Schleifen für beide Dimensionen des Bildes, welche explizit einzeln verwendet werden müssen, und die Beachtung von Datentypen. Unterschied sind die For-Schleifen für beide Dimensionen des Bildes, welche explizit einzeln verwendet werden müssen, und die Beachtung
von Datentypen.
Die Erstellung des benötigten, leeren Bildes und die For-Schleifen sind in \autoref{code: schleifen klassifizierung} zu sehen. Auch hier Die Erstellung des benötigten, leeren Bildes und die For-Schleifen sind in \autoref{code: schleifen klassifizierung} zu sehen. Auch hier
werden wieder aller leeren \gls{Pixel} übersprungen. werden wieder aller leeren \gls{Pixel} übersprungen.
...@@ -437,7 +437,7 @@ ...@@ -437,7 +437,7 @@
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
style=example, style=example,
caption={Inizialisieren des leeren Bildes und iteriert über jenes.}, caption={Inizialisieren des leeren Bildes und iterieren über jenes.},
label=code: schleifen klassifizierung, label=code: schleifen klassifizierung,
language=C++ language=C++
] ]
...@@ -471,9 +471,9 @@ ...@@ -471,9 +471,9 @@
\end{lstlisting} \end{lstlisting}
Die eigentliche Klassifizierung ist praktisch identisch zur \gls{python}-Version. Lediglich die Überprüfung der Winkelbereiche ist etwas Die eigentliche Klassifizierung ist praktisch identisch zur \gls{python}-Version. Lediglich die Überprüfung der Winkelbereiche ist etwas
langwieriger, da nicht mehrere Vergleiche direkt nacheinander möglich sind. Die codierung der Klassen erfolg wieder über die einzelnen Bit langwieriger, da nicht mehrere Vergleiche direkt nacheinander möglich sind. Die Codierung der Klassen Erfolg wieder über die einzelnen Bit
des Bytes der einzelnen Pixel. des Bytes der einzelnen Pixel.
Wie es implementiert ist, ist in \autoref{code: klassen c++} dargestellt. Die Umsetzung ist in \autoref{code: klassen c++} dargestellt.
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
...@@ -508,16 +508,16 @@ ...@@ -508,16 +508,16 @@
Mit dem klassifizierten Bild kann nun dieselbe Methodik zur Identifizierung zusammenhängender Linien wie in Python angewendet werden. Mit dem klassifizierten Bild kann nun dieselbe Methodik zur Identifizierung zusammenhängender Linien wie in Python angewendet werden.
Allerdings ist in \gls{C++} das Definieren und Testen der relevanten \gls{Pixelnachbarschaft} nicht so übersichtlich möglich wie in Python. Allerdings ist in \gls{C++} das Definieren und Testen der relevanten \gls{Pixelnachbarschaft} nicht so übersichtlich möglich wie in Python.
Daher müssen viele lange \lstinline{if} Bedingungen verwendet werden, welche in den folgenden Codebeispielen zur Übersichtlichkeit verkürzt Daher müssen viele lange \lstinline{if}-Bedingungen verwendet werden, welche in den folgenden Codebeispielen zur Übersichtlichkeit verkürzt
sind. sind.
Begonnen wird wieder mit einer doppelten For-Schleife über das gesamte Bild, wie in \autoref{code: loop for lines c++} zu sehen. Dabei Begonnen wird wieder mit einer doppelten \lstinline{for}-Schleife über das gesamte Bild, wie in \autoref{code: loop for lines c++} zu sehen. Dabei
werden wieder alle leeren \gls{Pixel} vernachlässigt. werden wieder alle leeren \gls{Pixel} vernachlässigt.
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
style=example, style=example,
caption={For-Schleifen über alle Klassifizierten Pixel}, caption={\lstinline{for}-Schleifen über alle klassifizierten Pixel},
label=code: loop for lines c++, label=code: loop for lines c++,
language=C++ language=C++
] ]
...@@ -558,7 +558,7 @@ ...@@ -558,7 +558,7 @@
} }
if ( has_neighbour ) if ( has_neighbour )
continue; continue;
\end{lstlisting} \end{lstlisting}\todo{Zusammenkürzen, wie auch später gemacht?}
Ist ein Startpixel gefunden, wird er gespeichert und wieder so lange der nächste Nachbar ausgewählt, bis kein Nachbar mehr vorhanden ist. Ist ein Startpixel gefunden, wird er gespeichert und wieder so lange der nächste Nachbar ausgewählt, bis kein Nachbar mehr vorhanden ist.
Dann ist die gesamte Linie nachverfolgt. Dabei werden alle besuchten \gls{Pixel} aus dem Bild gelöscht. Siehe dazu \autoref{code: follow c++}. Dann ist die gesamte Linie nachverfolgt. Dabei werden alle besuchten \gls{Pixel} aus dem Bild gelöscht. Siehe dazu \autoref{code: follow c++}.
...@@ -571,42 +571,42 @@ ...@@ -571,42 +571,42 @@
language=C++ language=C++
] ]
pnt start(v,u); pnt start(v,u);
// folow line to its end // follow line to its end
do { do {
classified_edges.at<uint8_t>(u,v) = 0; classified_edges.at<uint8_t>(u,v) = 0;
has_neighbour = false; has_neighbour = false;
switch (clsif) { switch (clsif) {
case V: case V:
if ( /* any neighbour existis */ ) if ( /* any neighbour exists */ )
// u+-;v+-; change u,v to new coordinates // u+-;v+-; change u,v to new coordinates
has_neighbour = true; has_neighbour = true;
case D1: case D1:
if ( /* any neighbour existis */ ) if ( /* any neighbour exists */ )
// u+-;v+-; change u,v to new coordinates // u+-;v+-; change u,v to new coordinates
has_neighbour = true; has_neighbour = true;
case D2: case D2:
if ( /* any neighbour existis */ ) if ( /* any neighbour exists */ )
// u+-;v+-; change u,v to new coordinates // u+-;v+-; change u,v to new coordinates
has_neighbour = true; has_neighbour = true;
} }
case H: case H:
if ( /* any neighbour existis */ ) if ( /* any neighbour exists */ )
// u+-;v+-; change u,v to new coordinates // u+-;v+-; change u,v to new coordinates
has_neighbour = true; has_neighbour = true;
} }
} while ( has_neighbour ); } while ( has_neighbour );
\end{lstlisting} \end{lstlisting}\todo{Zusammenkürzen, wie auch später gemacht?}
\pagebreak \pagebreak
Die so gefunden Linien werden wieder auf ihre Länge überprüft und als Objekte abgespeichert. Dabei werden einzelne Listen für jede Klasse Die so gefunden Linien werden wieder auf ihre Länge überprüft und als Objekte abgespeichert. Dabei werden einzelne Listen für jede Klasse
angelegt, sodass diese Später verglichen werden können. angelegt, sodass diese später verglichen werden können.
Besonders wichtig ist es, dass im Gegensatz zu \gls{python} die Schleifenvariablen \lstinline{u, v} manuell wieder auf die Besonders wichtig ist es, dass im Gegensatz zu \gls{python} die Schleifenvariablen \lstinline{u, v} manuell wieder auf die
Startkoordinaten zurückgesetzt werden müssen. Startkoordinaten zurückgesetzt werden müssen.
\subsubsection{Paarbildung} \subsubsection{Paarbildung}
Auch die Bildung von Linienpaaren aus einer rechten und linken Linie Erfolg analog zu \gls{python}. Hier gibt es auch keine großartigen Auch die Bildung von Linienpaaren aus einer rechten und linken Linie erfolgt analog zu \gls{python}. Hier gibt es auch keine großartigen
Unterschiede in der Umsetzung, wie \autoref{code: pairing c++} zu sehen ist. Unterschiede in der Umsetzung, wie \autoref{code: pairing c++} zu sehen ist.
Auch hier wird mit den gefundenen Paaren wieder ein Linienmarker-Objekt erzeugt und zur oberen Nutzung abgespeichert. Auch hier wird mit den gefundenen Paaren wieder ein Linienmarker-Objekt erzeugt und zur oberen Nutzung abgespeichert.
...@@ -646,7 +646,7 @@ ...@@ -646,7 +646,7 @@
\subsection{Performance Betrachtung} \subsection{Performance Betrachtung}
Mit dieser zusätzlichen \gls{ROS Node} ist es erneut interessant, wie sich diese auf die Performance auswirkt. Aus \autoref{sec: intrinsic} Mit dieser zusätzlichen \gls{ROS Node} ist es erneut interessant, wie sich diese auf die Performance auswirkt. Aus \autoref{sec: intrinsic}
ist ja bereits die Performance mit laufender Kamera- und Entzerrer-Node bekannt. Zum Vergleich wurde wieder die Systemauslastung mit dem ist bereits die Performance mit laufender Kamera- und Entzerrer-Node bekannt. Zum Vergleich wurde wieder die Systemauslastung mit dem
Programm \lstinline{jtop} aufgenommen und die Durchlaufzeit nach Erhalt eines Bildes gemessen. Programm \lstinline{jtop} aufgenommen und die Durchlaufzeit nach Erhalt eines Bildes gemessen.
\begin{figure} \begin{figure}
...@@ -726,7 +726,7 @@ ...@@ -726,7 +726,7 @@
Funktion \lstinline{edgeDetectionClassification()}. Diese führt nun die drei Schritte des \gls{canny} durch: Gradienten Bestimmung, Funktion \lstinline{edgeDetectionClassification()}. Diese führt nun die drei Schritte des \gls{canny} durch: Gradienten Bestimmung,
Unterdrücken von Nicht-Lokalen-Maxima und Hysterese-Grenzwertbildung (siehe \cite{Canny:computationAlapproachEdgeDetection}). Unterdrücken von Nicht-Lokalen-Maxima und Hysterese-Grenzwertbildung (siehe \cite{Canny:computationAlapproachEdgeDetection}).
Zusätzlich wird außerdem die Klassifizierung durchgeführt. Da im ersten Schritt die Sobel-Gradienten ohnehin berechnet werden, können hier Zusätzlich wird außerdem die Klassifizierung durchgeführt. Da im ersten Schritt die Sobel-Gra\-di\-enten ohnehin berechnet werden, können hier
ohne viel Zusatzaufwand auch die Gradientenwinkel mit bestimmt werden. Der \autoref{code: own canny 1} zeigt diesen Schritt. ohne viel Zusatzaufwand auch die Gradientenwinkel mit bestimmt werden. Der \autoref{code: own canny 1} zeigt diesen Schritt.
\begin{lstlisting}[ \begin{lstlisting}[
......
\chapter{Kamera Kalibrierung} \label{chap: kalibrierung} \chapter{Kamera Kalibrierung} \label{chap: kalibrierung}
Damit der später beschriebene Fahrspurerkennung möglichst zuverlässig funktioniert und möglichst reproduzierbar ist, wird eine Kalibrierung Damit die später beschriebene Fahrspurerkennung möglichst zuverlässig funktioniert und möglichst reproduzierbar ist, wird eine Kalibrierung
vorgenommen. Das Vergehen dazu und die Ergebnisse sind im folgenden Kapitel dokumentiert. vorgenommen. Das Vorgehen dazu und die Ergebnisse sind im folgenden Kapitel dokumentiert.
\section{Intrinsische Kalibrierung} \label{sec: intrinsic} \section{Intrinsische Kalibrierung} \label{sec: intrinsic}
Bedingt durch den technischen Aufbau des Linsensystems und Ungenauigkeiten bei der Herstellung sind die von der Kamera gelieferten Bedingt durch den technischen Aufbau des Linsensystems und Ungenauigkeiten bei der Herstellung sind die von der Kamera gelieferten
Bilder merklich verzehrt. In \autoref{fig: kamerabild unkalibriert} ist dies gut anhand der Linien des Schachbrettes zu erkennen, die in der Bilder merklich verzehrt. In \autoref{fig: kamerabild unkalibriert} ist dies gut anhand der Linien des Schachbrettes zu erkennen, die in der
Realität natürlich alle parallel verlaufen, im Bild aber gekrümmt aussehen. Realität alle parallel verlaufen, im Bild aber gekrümmt aussehen.
\begin{figure} \begin{figure}
\includegraphics[width=.4\textwidth]{img/unkalibriert.png} \includegraphics[width=.4\textwidth]{img/unkalibriert.png}
\caption{Unkalibriertes Kamerabild mit tonnenförmiger Verehrung} \caption{Unkalibriertes Kamerabild mit tonnenförmiger Verzerrung}
\label{fig: kamerabild unkalibriert} \label{fig: kamerabild unkalibriert}
\end{figure} \end{figure}
\subsection{Radiale Verzerrung} \subsection{Radiale Verzerrung}
Die erste mögliche Art der Verzerrung ist die radiale Verzerrung. Diese ist die auffälligste Art der Verzerrung und wird häufig auch Die erste mögliche Art der Verzerrung ist die radiale Verzerrung. Diese ist die auffälligste Art und wird häufig auch
\emph{Fischaugen Effekt} genannt. Bedingt durch die Brechung des Lichtes an den Kanten der Blende und der Linse entsteht eine Ablenkung \emph{Fischaugen Effekt} genannt. Bedingt durch die Brechung des Lichtes an den Kanten der Blende und der Linse entsteht eine Ablenkung
der Lichtstrahlen in der Kamera, die mit der Entfernung vom Mittelpunkt immer weiter zu nimmt. Nimmt die Ablenkung mit der Entfernung zu, der Lichtstrahlen in der Kamera, die mit der Entfernung vom Mittelpunkt immer weiter zu nimmt. Nimmt die Ablenkung mit der Entfernung zu,
spricht man von positiver, kissenförmige Verzerrung, den umgekehrte Fall nennt man negative, tonnenförmige Verzerrung. Zur Verdeutlichung spricht man von positiver, kissenförmige Verzerrung, den umgekehrte Fall nennt man negative, tonnenförmige Verzerrung. Zur Verdeutlichung
...@@ -28,27 +28,28 @@ ...@@ -28,27 +28,28 @@
\subfigure[Kissenförmige Verzerrung]{\includegraphics[page=3,width=.3\textwidth]{svg/Lens_distorsion.pdf}} \subfigure[Kissenförmige Verzerrung]{\includegraphics[page=3,width=.3\textwidth]{svg/Lens_distorsion.pdf}}
\subfigure[Verzerrungsfreies Bild]{\includegraphics[page=2,width=.3\textwidth]{svg/Lens_distorsion.pdf}} \subfigure[Verzerrungsfreies Bild]{\includegraphics[page=2,width=.3\textwidth]{svg/Lens_distorsion.pdf}}
\subfigure[Tonnenförmige Verzerrung]{\includegraphics[page=1,width=.3\textwidth]{svg/Lens_distorsion.pdf}} \subfigure[Tonnenförmige Verzerrung]{\includegraphics[page=1,width=.3\textwidth]{svg/Lens_distorsion.pdf}}
\caption{Darstellung der optischen Verzerrung (nach \cite{wiki:LinsenVerzerung})} \caption{Darstellung der optischen Verzerrungen (nach \cite{wiki:LinsenVerzerung})}
\label{fig: optische verzerrung} \label{fig: optische verzerrung}
\end{figure} \end{figure}
Mathematisch lässt dich die Veränderung eines Punktes durch die Verzierung wie in \autoref{eq: radiale verzerrung} beschrieben berechnen. Mathematisch lässt dich die Veränderung eines Punktes durch die Verzerrung wie in \autoref{eq: radiale verzerrung} beschrieben berechnen.
Dabei beschreiben $x$ und $y$ die unverzerrten Pixelkoordinaten, $k_1$, $k_3$ und $k_3$ die Verzerrungskoeffizienten. Dabei beschreiben $x$ und $y$ die unverzerrten Pixelkoordinaten, $k_1$, $k_3$ und $k_3$ die Verzerrungskoeffizienten.
Theoretisch existieren noch weitere Koeffizienten, aber in der Praxis haben sich die ersten drei als ausreichend herausgestellt. Theoretisch existieren noch weitere Koeffizienten, aber in der Praxis haben sich die ersten drei als ausreichend herausgestellt.
\cite{Hanning:highPrecisionCamCalibration} \cite{Hanning:highPrecisionCamCalibration}
\begin{equation}\label{eq: radiale verzerrung} \begin{equation}\label{eq: radiale verzerrung}
\begin{split} \begin{split}
x_{distored} &= x( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6 ) \\ x_{distorted} &= x( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6 ) \\
y_{distored} &= y( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6 ) \\ y_{distorted} &= y( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6 ) \\
\end{split} \end{split}
\end{equation} \end{equation}
\pagebreak \pagebreak
\subsection{Tangentiale Verzerrung} \subsection{Tangentiale Verzerrung}
Die tangentiale Verzerrung entsteht durch kleine Ausrichtungsfehler im Linsensystem. Dadruch liegt die Linse nicht perfekt in der Die tangentiale Verzerrung entsteht durch kleine Ausrichtungsfehler im Linsensystem. Dadurch liegt die Linse nicht perfekt in der
Bildebene und der Bildmittelpunk sowie die Bildausrichtung können leicht verschoben sein. Bildebene und der Bildmittelpunkt sowie die Bildausrichtung können leicht verschoben sein.
\begin{figure} \begin{figure}
\missingfigure{Sensor-Linse alignment} \missingfigure{Sensor-Linse alignment}
...@@ -59,15 +60,15 @@ ...@@ -59,15 +60,15 @@
\begin{equation} \begin{equation}
\begin{split} \begin{split}
x_{distored} &= x + \left[2p_1xy + p_2(r^2 + 2x^2)\right] \\ x_{distorted} &= x + \left[2p_1xy + p_2(r^2 + 2x^2)\right] \\
y_{distored} &= y + \left[p_1(r^2 + 2y^2) + 2p_2xy\right] \\ y_{distorted} &= y + \left[p_1(r^2 + 2y^2) + 2p_2xy\right] \\
\end{split} \end{split}
\end{equation} \end{equation}
\bigskip \bigskip
Durch beide Verzerrungsarten zusammen werden also durch fünf Parameter beschrieben, die sogenannten Verzerrungskoeffizienten. Historisch Die beiden Verzerrungsarten zusammen werden also durch fünf Parameter beschrieben, die sogenannten Verzerrungskoeffizienten. Historisch
begründet wird dabei $k_3$ an das Ende geschrieben, da dieses Parameter früher kaum berücksichtigt wurde. begründet wird dabei $k_3$ an das Ende geschrieben, da dieser Parameter früher kaum berücksichtigt wurde.
\begin{equation} \begin{equation}
D_{coeff} = (k_1, k_2, p_1, p_2, k_3) D_{coeff} = (k_1, k_2, p_1, p_2, k_3)
...@@ -79,7 +80,7 @@ ...@@ -79,7 +80,7 @@
\medskip \medskip
In der Praxis werden 2D-Muster verwendet, um Punktepaare zu bestimmen. Da sich alle Punkte dieser Muster in einer Ebene befinden, kann der In der Praxis werden 2D-Muster verwendet, um Punktepaare zu bestimmen. Da sich alle Punkte dieser Muster in einer Ebene befinden, kann der
Uhrsprung der \gls{Welt-coords} in eine Ecke des Musters gelegt werden, sodass die Z-Koordinate keine Relevanz mehr hat und wegfällt. Ursprung der \gls{Welt-coords} in eine Ecke des Musters gelegt werden, sodass die Z-Koordinate keine Relevanz mehr hat und wegfällt.
\cite{uniFreiburg:rob2-CamCalibration} \cite{uniFreiburg:rob2-CamCalibration}
Dabei werden Muster so gewählt, dass es möglichst einfach fällt die Weltkoordinaten der Punkte zu bestimmen. Beispielsweise sind bei einem Dabei werden Muster so gewählt, dass es möglichst einfach fällt die Weltkoordinaten der Punkte zu bestimmen. Beispielsweise sind bei einem
...@@ -89,7 +90,7 @@ ...@@ -89,7 +90,7 @@
\section{Durchführung der intrinsischen Kalibrierung} \section{Durchführung der intrinsischen Kalibrierung}
Zur Durchführung der Kalibrierung wir ein Python-Script erstellt, um die den Vorgang einfach und wiederholbar zu machen. Als Vorlage für Zur Durchführung der Kalibrierung wir ein Python-Script erstellt, um den Vorgang einfach und wiederholbar zu machen. Als Vorlage für
dieses dient die Anleitung zur Kamera Kalibrierung aus der \gls{OpenCV} Dokumentation \cite{OpenCV:CameraCalibration}. dieses dient die Anleitung zur Kamera Kalibrierung aus der \gls{OpenCV} Dokumentation \cite{OpenCV:CameraCalibration}.
Außerdem wird eine \gls{ROS Nodelet} erstellt, welches die Kalibrierung auf den Video-Stream anwendet und korrigierte Bilder veröffentlicht. Außerdem wird eine \gls{ROS Nodelet} erstellt, welches die Kalibrierung auf den Video-Stream anwendet und korrigierte Bilder veröffentlicht.
...@@ -100,13 +101,13 @@ ...@@ -100,13 +101,13 @@
Grundlage für die Kalibrierung ist es, eine Reihe von Bildern mit der zu kalibrierenden Kamera aufzunehmen, auf denen sich ein Grundlage für die Kalibrierung ist es, eine Reihe von Bildern mit der zu kalibrierenden Kamera aufzunehmen, auf denen sich ein
Schachbrettartiges Kalibriermuster befindet. Wichtig ist es, dasselbe Muster und dieselbe Auflösung für alle Bilder verwendet werden. Es muss Schachbrettartiges Kalibriermuster befindet. Wichtig ist es, dasselbe Muster und dieselbe Auflösung für alle Bilder verwendet werden. Es muss
sich dabei nicht um eine quadratische Anordnung handeln, jedoch muss die Anzahl der Zeilen und spalten im Code angegeben werden. Dabei ist sich dabei nicht um eine quadratische Anordnung handeln, jedoch muss die Anzahl der Zeilen und spalten im Code angegeben werden. Dabei ist
allerdings nicht die Anzahle der Felder gemeint, sondern die Anzahl der inneren Kreuzungspunkten. Ein normales Schachbrett hat beispielsweise allerdings nicht die Anzahl der Felder gemeint, sondern die Anzahl der inneren Kreuzungspunkten. Ein normales Schachbrett hat beispielsweise
$8 \!\times\! 8$ Felder, aber nur $7 \!\times\! 7$ interne Kreuzungen. Zur Verdeutlichung sind die Kreuzungspunkte des Verwendeten $8 \!\times\! 8$ Felder, aber nur $7 \!\times\! 7$ interne Kreuzungen. Zur Verdeutlichung sind die Kreuzungspunkte des verwendeten
Kalibriermuster in \autoref{fig: kalibriermuster} grün markiert. Kalibriermusters in \autoref{fig: kalibriermuster} grün markiert.
\begin{figure} \begin{figure}
\includegraphics[width=.4\textwidth]{img/kalibrieren_PATTER.png} \includegraphics[width=.4\textwidth]{img/kalibrieren_PATTER.png}
\caption{Schachbrett Kalibriermuster mit markierten inneren Kreuzungen} \caption{Schachbrett-Kalibriermuster mit markierten inneren Kreuzungen}
\label{fig: kalibriermuster} \label{fig: kalibriermuster}
\end{figure} \end{figure}
...@@ -116,7 +117,7 @@ ...@@ -116,7 +117,7 @@
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
style=example, style=example,
caption=Definiteion der Größe des Kalibriermuster, caption=Definition der Größe des Kalibriermusters,
language=Python language=Python
] ]
# define the grid pattern to look for # define the grid pattern to look for
...@@ -128,7 +129,7 @@ ...@@ -128,7 +129,7 @@
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
style=example, style=example,
caption=Initialisierung von Variablen für die Kalibreirung, caption=Initialisierung von Variablen für die Kalibrierung,
language=Python, language=Python,
label=code: kali var init label=code: kali var init
] ]
...@@ -142,9 +143,9 @@ ...@@ -142,9 +143,9 @@
imgpoints = [] # 2d points in image plane. imgpoints = [] # 2d points in image plane.
\end{lstlisting} \end{lstlisting}
Nun werden alle im aktuellen Ordner befindlichen Bilder eingelesen und in einer Liste abgespeichert. Jedes Listenelement wird eingelesen und Nun werden alle im aktuellen Ordner befindlichen Bilder eingelesen und in einer Liste abgespeichert. Jedes Listenelement wird eingelesen
in ein Schwarzweißbild umgewandelt. Dieses wird dann an die \gls{OpenCV} Funktion \lstinline{findChessboardCorners()} übergeben, welche die und in ein Schwarzweißbild umgewandelt. Dieses wird dann an die \gls{OpenCV} Funktion \lstinline{findChessboardCorners()} übergeben,
Kreuzungspunkten findet und zurückgibt. welche die Kreuzungspunkte findet und zurückgibt.
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
...@@ -176,7 +177,7 @@ ...@@ -176,7 +177,7 @@
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
style=example, style=example,
caption=Abspeichern der Gefundenen Bildpunkte, caption=Abspeichern der gefundenen Bildpunkte,
language=Python language=Python
] ]
# If found, add object points, image points # If found, add object points, image points
...@@ -220,13 +221,13 @@ ...@@ -220,13 +221,13 @@
\end{align*} \end{align*}
Um zu zeigen, wie sich das Bild damit verbessern lässt, werden die Ergebnisse auf eines der Bilder angewandt. Da sich die Abmessungen des Um zu zeigen, wie sich das Bild damit verbessern lässt, werden die Ergebnisse auf eines der Bilder angewandt. Da sich die Abmessungen des
entzerrten Bildes von denen des Verzehrten unterscheiden, wird zuerst die \gls{OpenCV} Funktion \lstinline{getOptimalNewCameraMatrix()} entzerrten Bildes, von denen des verzehrten unterscheiden, wird zuerst die \gls{OpenCV} Funktion \lstinline{getOptimalNewCameraMatrix()}
verwendet, welche eine weiter Skalierte Kameramatrix ermittelt, mit der die Abmessungen zueinander passen. Diese liefert außerdem eine verwendet, welche eine weiter skalierte Kameramatrix ermittelt, mit der die Abmessungen zueinander passen. Diese liefert außerdem eine
\gls{ROI}, also den Bildbereich der nur relevante (nicht leere) \gls{Pixel} enthält. \gls{ROI}, also den Bildbereich der nur relevante (nicht leere) \gls{Pixel} enthält.
Mit dieser zusätzlichen Matrix kann nun die \gls{OpenCV} Funktion \lstinline{undistort()} auf das Bild angewandt werden. Diese Produziert das Mit dieser zusätzlichen Matrix kann nun die \gls{OpenCV} Funktion \lstinline{undistort()} auf das Bild angewandt werden. Diese produziert
entzerrte Bild mit leeren Pixeln in den Bereichen, wo keine Informationen im Originalbild vorlagen. Um diese leeren \gls{Pixel} zu entfernen wird das entzerrte Bild mit leeren Pixeln in den Bereichen, wo keine Informationen im Originalbild vorlagen. Um diese leeren \gls{Pixel} zu
das Bild auf die \gls{ROI} reduziert. entfernen wird das Bild auf die \gls{ROI} reduziert.
\medskip \medskip
In \autoref{fig: intrinsik schritte} ist die Entzerrung des Beispielbildes mit dem Zwischenschritt mit Leerpixeln gezeigt. In \autoref{fig: intrinsik schritte} ist die Entzerrung des Beispielbildes mit dem Zwischenschritt mit Leerpixeln gezeigt.
...@@ -240,14 +241,14 @@ ...@@ -240,14 +241,14 @@
\subsubsection{Reprojektions-Fehler} \subsubsection{Reprojektions-Fehler}
Um eine Aussage über die Genauigkeit der gefundenen Kalibrierungs-Parameter treffen zu können, wird der Reprojektions-Fehler bestimmt. Um eine Aussage über die Genauigkeit der gefundenen Kalibrierungs-Parameter treffen zu können, wird der Reprojektions-Fehler bestimmt.
Dieser gibt den Abstand zwischen einem im Kalibriermuster gefundenen Kreuzungspunkt und den mittels der Kalibrierung Ergebnisse Dieser gibt den Abstand zwischen einem im Kalibriermuster gefundenen Kreuzungspunkt und den mittels der Kalibrierungsergebnisse
berechneten \gls{Welt-coords}. Der Mittelwert aller Abweichungen in allen verwendeten Bilder gibt den Reprojektions-Fehler für den berechneten \gls{Welt-coords}. Der Mittelwert aller Abweichungen in allen verwendeten Bilder gibt den Reprojektions-Fehler für den
ganzen Kalibriervorgang an. ganzen Kalibriervorgang an.
Der \autoref{code: reprojektions fehler} zeigt die Berechnung mittels von OpenCV zur Verfügung gestellten Funktionen und den zuvor Der \autoref{code: reprojektions fehler} zeigt die Berechnung mittels von OpenCV zur Verfügung gestellten Funktionen und den zuvor
ermittelten Kalibrierdaten. Für jeden Satz an theoretischen \gls{Welt-coords} des Kalibriermusters in \lstinline{objpoints} werden ermittelten Kalibrierdaten. Für jeden Satz an theoretischen \gls{Welt-coords} des Kalibriermusters in \lstinline{objpoints} werden
die Punkte im Bild mit der OpenCV Funktion \lstinline{projectPoints()} bestimmt und mit den gefundenen Punkten verglichen. Dazu wird die Punkte im Bild mit der OpenCV Funktion \lstinline{projectPoints()} bestimmt und mit den gefundenen Punkten verglichen. Dazu wird
die OpenCV Funktion \lstinline{norm()} verwendet, die direkt die summe aller Differenzen zweigen den beiden Punktelisten liefert. die OpenCV Funktion \lstinline{norm()} verwendet, die direkt die Summe aller Differenzen zwischen den beiden Punktelisten liefert.
Das Ergebnis wird auf dem Bildschirm ausgegeben. Das Ergebnis wird auf dem Bildschirm ausgegeben.
...@@ -267,15 +268,15 @@ ...@@ -267,15 +268,15 @@
print(f"total error: {mean_error/len(objpoints)}") print(f"total error: {mean_error/len(objpoints)}")
\end{lstlisting} \end{lstlisting}
Mit dem verwendeten Datensatz ergibt sich ein Reprojektions-Fehler von $0,049$, was genau genug für diesen Anwendungsfall ist. Mit dem verwendeten Datensatz ergibt sich ein Reprojektions-Fehler von $0,049$. Dies ist genau genug für diesen Anwendungsfall.
\subsection{Anwenden der Kalibrierung in einer ROS Node} \label{sec: undistort Node} \subsection{Anwenden der Kalibrierung in einer ROS Node} \label{sec: undistort Node}
Um die Kalibrierungsergebnisse auf jedes Bild, dass vom Kamera Treiber veröffentlicht wird, anzuwenden, wird eine weitere \gls{ROS Node} Um die Kalibrierungsergebnisse auf jedes Bild, dass vom Kamera Treiber veröffentlicht wird, anzuwenden, wird eine weitere \gls{ROS Node}
erstellt. Diese entzerrt jedes erhaltene Bild und veröffentlicht die korrigierte Version als eigens \gls{Topic}. Das korrigierte Bild wird erstellt. Diese entzerrt jedes erhaltene Bild und veröffentlicht die korrigierte Version als eigenes \gls{Topic}. Das korrigierte Bild wird
sowohl in Farbe als auch in Schwarz-Weiß veröffentlicht. Die Beziehung der \glspl{Topic} ist in \autoref{fig: topics graph undistoreter node} sowohl in Farbe als auch in Schwarz-Weiß veröffentlicht. Die Beziehung der \glspl{Topic} ist in \autoref{fig: topics graph undistoreter node}
Grafisch dargestellt. grafisch dargestellt.
\begin{figure} \begin{figure}
\includegraphics[scale=.85]{svg/Topics_undistorter.pdf} \includegraphics[scale=.85]{svg/Topics_undistorter.pdf}
...@@ -287,13 +288,13 @@ ...@@ -287,13 +288,13 @@
\subsubsection{Initialisieren der Node} \subsubsection{Initialisieren der Node}
Beim Start der \gls{ROS Node} wird die \lstinline{main()} Funktion aufgerufen, welche die notwendigen \gls{ROS} Funktionen zur Beim Start der \gls{ROS Node} wird die \lstinline{main()} Funktion aufgerufen, welche die notwendigen \gls{ROS} Funktionen zur
Initialisierung aufruft, das benötigt \gls{Topic} abonniert, ein \gls{Callback} anhängt und die eigenen \glspl{Topic} veröffentlicht. Initialisierung aufruft, das benötigt \gls{Topic} abonniert, eine \gls{Callback} anhängt und die eigenen \glspl{Topic} veröffentlicht.
\todo{Code hierzu?}
Außerdem werden die Kalibrierdaten aus einer Konfigurationsdatei im YAML-Format eingelesen und in variablen übernommen. Die Außerdem werden die Kalibrierdaten aus einer Konfigurationsdatei im YAML-Format eingelesen und in Variablen übernommen. Die
Verzerrungsparameter werden als Vektor eingelesen und die Kameramatrix wird in eine \gls{OpenCV} Matrix umgewandelt. Außerdem wird die Verzerrungsparameter werden als Vektor eingelesen und die Kameramatrix wird in eine \gls{OpenCV} Matrix umgewandelt. Außerdem wird die
Bildgröße benötigt und aus der Konfigurationsdatei gelesen. \autoref{code: intrinsic einlesen YAML} zeigt den Ablauf. Es ist sinnvoll, Bildgröße benötigt und daher aus der Konfigurationsdatei gelesen. \autoref{code: intrinsic einlesen YAML} zeigt den Ablauf. Es ist
dies bereits in der \lstinline{main()} Funktion durchzuführen, um die \gls{Callback} zu entlasten und dort Rechenzeit einzusparen. sinnvoll, dies bereits in der \lstinline{main()} Funktion durchzuführen, um die \gls{Callback} zu entlasten und dort Rechenzeit
einzusparen.
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
...@@ -307,7 +308,7 @@ ...@@ -307,7 +308,7 @@
YAML::Node full_config = YAML::LoadFile(configFilePath); YAML::Node full_config = YAML::LoadFile(configFilePath);
YAML::Node camera_config = full_config["cameras"]["default"]; YAML::Node camera_config = full_config["cameras"]["default"];
// read distorion coeffitiones and convert to OpenCV vector // read distorion coefficients and convert to OpenCV vector
auto distortion_YAML = camera_config["intrinsic"]["distortion"] .as<std::vector<double>>(); auto distortion_YAML = camera_config["intrinsic"]["distortion"] .as<std::vector<double>>();
cv::Mat distortion ( distortion_YAML ); cv::Mat distortion ( distortion_YAML );
...@@ -322,18 +323,19 @@ ...@@ -322,18 +323,19 @@
); );
\end{lstlisting} \end{lstlisting}
Mit diesen Werten können nun \emph{Mappings} erzeugt werden, welche die geometrische Beziehung zwischen einem \gls{Pixel} im Originalbild und Mit diesen Werten können nun \emph{Mappings} erzeugt werden, welche die geometrische Beziehung zwischen einem \gls{Pixel} im
einem \gls{Pixel} im entzerrten Bild abspeichern. Es werden zwei \emph{Mappings} für die X und die Y-Koordinate erzeugt, welche in globalen Originalbild und einem \gls{Pixel} im entzerrten Bild abspeichern. Es werden zwei \emph{Mappings} für die X und die Y-Koordinate
Variablen abgelegt werden. Das ist notwendig damit die Informationen der \gls{Callback} zur Verfügung stehen. erzeugt, welche in globalen Variablen abgelegt werden. Das ist notwendig damit die Informationen der \gls{Callback} zur Verfügung
stehen.
Zuvor ist es aber noch sinnvoll, eine umskalierte, optimierte Kameramatrix zu erzeugen. \gls{OpenCV} stellt hierzu die Funktion Zuvor ist es aber noch sinnvoll, eine umskalierte, optimierte Kameramatrix zu erzeugen. \gls{OpenCV} stellt hierzu die Funktion
\lstinline{getOptimalNewCameraMatrix()} zur Verfügung. Diese erstellt die neue Matrix abhängig von einem freien Skalierungsparameter \lstinline{getOptimalNewCameraMatrix()} zur Verfügung. Diese erstellt die neue Matrix abhängig von einem freien Skalierungsparameter
$\alpha$. Für $\alpha=0$ ist die zurückgegebene Matrix so gewählt, dass das entzerrte Bild möglichst wenig unbekannte \gls{Pixel} enthält. Das $\alpha$. Für $\alpha=0$ ist die zurückgegebene Matrix so gewählt, dass das entzerrte Bild möglichst wenig unbekannte \gls{Pixel}
bedeutet aber, dass einige \gls{Pixel} des Originalbildes außerhalb des neuen Bildbereiches liegen und vernachlässigt werden. Mit $\alpha=1$ enthält. Das bedeutet aber, dass einige \gls{Pixel} des Originalbildes außerhalb des neuen Bildbereiches liegen und vernachlässigt
enthält das entzerrte Bild alle \gls{Pixel} des Originalbildes, allerdings bleiben einige \gls{Pixel} schwarz. Da die Funktion zusätzlichen eine werden. Mit $\alpha=1$ enthält das entzerrte Bild alle \gls{Pixel} des Originalbildes, allerdings bleiben einige \gls{Pixel} schwarz.
\gls{ROI} liefert, welches den Bildausschnitt ohne schwarze \gls{Pixel} beschreibt, wird hier $\alpha=1$ verwendet. Die veröffentlichten Bilder Da die Funktion zusätzlichen eine \gls{ROI} liefert, welches den Bildausschnitt ohne schwarze \gls{Pixel} beschreibt, wird hier
werden zwar auf die \gls{ROI} reduziert, aber die vorhanden Informationen werdenden grundsätzlich erhalten und bei Bedarf kann das $\alpha=1$ verwendet. Die veröffentlichten Bilder werden zwar auf die \gls{ROI} reduziert, aber die zusätzlichen Informationen sind
Programm einfach angepasst werden, um die vollständigen Bilder zu veröffentlichen. grundsätzlich vorhanden und bei Bedarf kann das Programm einfach angepasst werden, um die vollständigen Bilder zu veröffentlichen.
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
...@@ -362,8 +364,8 @@ ...@@ -362,8 +364,8 @@
Das Bild kann nun mit der OpenCV Funktion \lstinline{remap()} entzerrt werden. Diese benutzt die zuvor bestimmten \emph{Mappings}, um Das Bild kann nun mit der OpenCV Funktion \lstinline{remap()} entzerrt werden. Diese benutzt die zuvor bestimmten \emph{Mappings}, um
jeden \gls{Pixel} des Originalbildes an die korrekte Position im entzerrten Bild zu übertragen. Dabei wird linear interpoliert. jeden \gls{Pixel} des Originalbildes an die korrekte Position im entzerrten Bild zu übertragen. Dabei wird linear interpoliert.
Das Erhalten Bild wird auf die \gls{ROI} reduziert und unter dem \gls{Topic} \lstinline{/img/color} veröffentlicht. Außerdem wird ein Das erhaltene Bild wird auf die \gls{ROI} reduziert und unter dem \gls{Topic} \lstinline{/img/color} veröffentlicht. Außerdem wird ein
Schwarz-Weiß Version erzeugt und diese als \lstinline{/img/gray} veröffentlicht (was hier aber nicht gezeigt ist). Schwarz-Weiß Version erzeugt und diese als \lstinline{/img/gray} veröffentlicht, was hier aber nicht gezeigt ist.
\begin{lstlisting}[ \begin{lstlisting}[
float, float,
...@@ -373,19 +375,19 @@ ...@@ -373,19 +375,19 @@
language=C++ language=C++
] ]
void callback_undistort_image(sensor_msgs::Image original) { void callback_undistort_image(sensor_msgs::Image original) {
cv::Mat undistoredImage; cv::Mat undistortedImage;
// convert from ROS msg-type to opencv matrix // convert from ROS msg-type to opencv matrix
cv_bridge::CvImagePtr imagePtr = cv_bridge::toCvCopy(original); cv_bridge::CvImagePtr imagePtr = cv_bridge::toCvCopy(original);
// apply the calculated maps to undistort the image // apply the calculated maps to undistort the image
cv::remap(imagePtr->image, undistoredImage, rectifyMapX, rectifyMapY, cv::INTER_LINEAR); cv::remap(imagePtr->image, undistortedImage, rectifyMapX, rectifyMapY, cv::INTER_LINEAR);
// crop relevant section from image // crop relevant section from image
undistoredImage = undistoredImage(ROI); undistortedImage = undistortedImage(ROI);
// publish images // publish images
cv_bridge::CvImage colorImage(std_msgs::Header(), "rgb8", undistoredImage); cv_bridge::CvImage colorImage(std_msgs::Header(), "rgb8", undistortedImage);
pub_colorImage->publish(colorImage.toImageMsg()); pub_colorImage->publish(colorImage.toImageMsg());
} }
\end{lstlisting} \end{lstlisting}
...@@ -395,9 +397,9 @@ ...@@ -395,9 +397,9 @@
\subsubsection{Performance Betrachtung} \subsubsection{Performance Betrachtung}
Da diese \gls{ROS Node} eine Grundlagenfunktion darstellt und parallel zu jeder anderen Anwendungen laufen muss, ist es wichtig, dass Da diese \gls{ROS Node} eine Grundlagenfunktion darstellt und parallel zu jeder anderen Anwendungen laufen muss, ist es wichtig, dass
sie möglichst Performant ist und wenig Ressourcen des JetBots verbraucht. sie möglichst performant ist und wenig Ressourcen des JetBots verbraucht.
Daher wurde die mittlere CPU Auslastung und die durchschnittliche Laufzeit der \gls{Callback}, welche ja für jedes Bild durchlaufen Daher wurde die mittlere CPU Auslastung und die durchschnittliche Laufzeit der \gls{Callback}, welche für jedes Bild durchlaufen
wird, gemessen. wird, gemessen.
\begin{figure} \begin{figure}
...@@ -412,10 +414,10 @@ ...@@ -412,10 +414,10 @@
Das ist aber auf die starke Fluktuation in der CPU Auslastung und daher ungenauen Messung zurückzuführen. Die Auslastung wird daher Das ist aber auf die starke Fluktuation in der CPU Auslastung und daher ungenauen Messung zurückzuführen. Die Auslastung wird daher
als identisch betrachtete. als identisch betrachtete.
Um die Laufzeit der \gls{ROS Node} zu bestimmen wird die aktuelle Zeit wie sie von der Funktion \lstinline{ros::Time::now()} Um die Laufzeit der \gls{ROS Node} zu bestimmen, wird die aktuelle Zeit, wie sie von der Funktion \lstinline{ros::Time::now()}
zurückgegeben wird verwendet. Die aktuelle Zeit beim Start der \gls{Callback} wird abgespeichert. Nach Durchlauf der Funktion wird zurückgegeben wird, verwendet. Die Zeit beim Start der \gls{Callback} wird abgespeichert. Nach Durchlauf der Funktion wird erneut die
erneut die aktuelle Zeit bestimmt und die Differenz in Sekunden als Debug-Nachricht ausgegeben. Die Laufzeit der \gls{ROS Node} wird aktuelle Zeit bestimmt und die Differenz in Millisekunden als Debug-Nachricht ausgegeben. Die Laufzeit der \gls{ROS Node} wird über
über einige Zeit gemittelt. Dabei ergibt sich eine Laufzeit von $\approx 4,07\,\ms$. einige Durchläufe gemittelt. Dabei ergibt sich eine Laufzeit von $\approx 4,07\,\ms$.
\begin{table} \begin{table}
\caption{Gemessene Laufzeit bei 10 Durchläufen der \gls{Callback}} \caption{Gemessene Laufzeit bei 10 Durchläufen der \gls{Callback}}
...@@ -432,7 +434,6 @@ ...@@ -432,7 +434,6 @@
9 & 3,752863 \,\ms \\ 9 & 3,752863 \,\ms \\
10 & 4,095999 \,\ms \\ 10 & 4,095999 \,\ms \\
\end{tabular} \end{tabular}
\todo[inline]{ist die Tabelle überhaupt sinnvoll?}
\end{table} \end{table}
......
...@@ -97,13 +97,13 @@ ...@@ -97,13 +97,13 @@
\section{OpenCV} \label{sec: opencv} \section{OpenCV} \label{sec: opencv}
Das Open-Source-Projekt \gls{OpenCV} (kurz für \emph{Open Source Computer Vision Library}) ist eine Sammlung von Softwaremodule die der Das Open-Source-Projekt \gls{OpenCV} (kurz für \emph{Open Source Computer Vision Library}) ist eine Sammlung von Softwaremodulen, die der
Bildverarbeitung und dem maschinellen Lernen dienen. Sie verfügt über mehr als 2500 optimierte Algorithmen mit denen Anwendungen wie Bildverarbeitung und dem maschinellen Lernen dienen. Sie verfügt über mehr als 2500 optimierte Algorithmen mit denen Anwendungen wie
Objekterkennung, Bewegungserkennung und 3D-Modell Extraktion erstellt werden können. Daher ist sie eine der standard Biblioteken, wenn es um Objekterkennung, Bewegungserkennung und 3D-Modell Extraktion erstellt werden können. Daher ist sie eine der Standardbibliotheken, wenn es um
digitale Bildverarbeitung geht und wird fast immer zur Demonstration neuer Konzepte benutzt. Da sie sowohl in C/\gls{C++}, Java und Python digitale Bildverarbeitung geht und wird fast immer zur Demonstration neuer Konzepte benutzt. Da sie sowohl in C/\gls{C++}, Java und Python
genutzt werden kann, ist sie außerdem sehr vielseitig und hat den Vorteil, dass Konzepte in einer abstrakten Sprache wie \gls{python} getestet genutzt werden kann, ist sie außerdem sehr vielseitig und hat den Vorteil, dass Konzepte in einer abstrakten Sprache wie \gls{python} getestet
werden und später relativ simple in eine Hardwarenahe Programmiersprache übersetzt werden können. Für mehr Informationen kann die Webseite werden und später relativ simple in eine hardwarenahe Programmiersprache übersetzt werden können. Weitere Informationen sind in der
des Projektes \cite{OpenCV:homepage} besucht werden. Dokumentation des Projektes \cite{OpenCV:homepage} zu finden.
In dieser Arbeit wird diese Bibliothek daher insbesondere für die Entwicklungsphase verwendet. Da der Bibliothekskunde jedoch auch viele In dieser Arbeit wird diese Bibliothek daher insbesondere für die Entwicklungsphase verwendet. Da der Bibliothekskunde jedoch auch viele
potenziell nicht benötigte Zusatzfunktionen mit bring, wird auch ein wechsel auf eine eigene Implementierung mit besserer Performance in potenziell nicht benötigte Zusatzfunktionen mit bring, wird auch ein wechsel auf eine eigene Implementierung mit besserer Performance in
...@@ -112,10 +112,10 @@ ...@@ -112,10 +112,10 @@
\section{Das Robot Operating System} \label{sec: ros} \section{Das Robot Operating System} \label{sec: ros}
Das \glslink{ROS}{Robot Operating System} (kurz: ROS) ist eine Sammlung von Software Bibliotheken und Werkzeugen die zum Erstellen von Roboter Das \glslink{ROS}{Robot Operating System} (kurz: ROS) ist eine Sammlung von Software Bibliotheken und Werkzeugen, die zum Erstellen von Roboter
Applikationen dienen. Es bietet eine eigene Paketverwaltung über die verschiedenste bestehende Bibliotheksfunktionen für die Verwendung Applikationen dienen. Es bietet eine eigene Paketverwaltung über die bestehende Bibliotheksfunktionen für die Verwendung
heruntergeladen werden können. Dabei handelte es sich um verschiedenste Anwendungen, angefangen Treiben, über fertige, direkt anwendbare heruntergeladen werden können. Dabei handelte es sich um verschiedenste Anwendungen, angefangen Treibern, über fertige, direkt anwendbare
Algorithmen bis zu Nutzer nahen Steueroberflächen und sogar (Lern-)Spiele. Die Webseite des Projektes \cite{ROS:homepage} bietet hierzu Algorithmen, bis zu nutzernahen Steueroberflächen und sogar (Lern-)Spiele. Die Webseite des Projektes \cite{ROS:homepage} bietet hierzu
weitere Informationen. Außerdem bietet \gls{ROS} Integrationen mit anderen bestehenden Projekten, wie zum Beispiel \gls{OpenCV}. weitere Informationen. Außerdem bietet \gls{ROS} Integrationen mit anderen bestehenden Projekten, wie zum Beispiel \gls{OpenCV}.
Auch wenn es sich bei \gls{ROS} genaugenommen um kein vollständiges Betriebssystem handelt, stellt es für ein solches typische Auch wenn es sich bei \gls{ROS} genaugenommen um kein vollständiges Betriebssystem handelt, stellt es für ein solches typische
...@@ -151,7 +151,7 @@ ...@@ -151,7 +151,7 @@
\caption{CPU Auslastung des JetBots ohne \gls{ROS}} \caption{CPU Auslastung des JetBots ohne \gls{ROS}}
\end{figure} \end{figure}
Basline Auslastung ohne irgendwelche laufenden Prozesse $\approx8\,\percent$. Baseline Auslastung ohne irgendwelche laufenden Prozesse $\approx8\,\percent$.
\begin{figure} \begin{figure}
\includegraphics[width=.6\textwidth]{img/jtop_camera.png} \includegraphics[width=.6\textwidth]{img/jtop_camera.png}
......
...@@ -74,8 +74,8 @@ if __name__ == "__main__": ...@@ -74,8 +74,8 @@ if __name__ == "__main__":
# undistort # undistort
dst = cv.undistort(img, mtx, dist, None, newcameramtx) dst = cv.undistort(img, mtx, dist, None, newcameramtx)
cv.namedWindow("undistored", cv.WINDOW_NORMAL) cv.namedWindow("undistorted", cv.WINDOW_NORMAL)
cv.imshow('undistored', dst) cv.imshow('undistorted', dst)
# crop the image # crop the image
x, y, w, h = roi x, y, w, h = roi
......
...@@ -161,7 +161,7 @@ void callback_image(sensor_msgs::Image image_msg) { ...@@ -161,7 +161,7 @@ void callback_image(sensor_msgs::Image image_msg) {
// no neighbour found means start pixel // no neighbour found means start pixel
pnt start(v,u); pnt start(v,u);
// folow line to its end // follow line to its end
do { do {
classified_edges[pnt(v,u)] = 0; classified_edges[pnt(v,u)] = 0;
has_neighbour = false; has_neighbour = false;
......
...@@ -133,7 +133,7 @@ void callback_image(sensor_msgs::Image image_msg) { ...@@ -133,7 +133,7 @@ void callback_image(sensor_msgs::Image image_msg) {
// no neighbour found means start pixel // no neighbour found means start pixel
pnt start(v,u); pnt start(v,u);
// folow line to its end // follow line to its end
do { do {
classified_edges.at<uint8_t>(u,v) = 0; classified_edges.at<uint8_t>(u,v) = 0;
has_neighbour = false; has_neighbour = false;
......
...@@ -25,26 +25,26 @@ cv::Rect ROI; ...@@ -25,26 +25,26 @@ cv::Rect ROI;
void callback_undistort_image(sensor_msgs::Image original) { void callback_undistort_image(sensor_msgs::Image original) {
ros::Time starttime = ros::Time::now(); ros::Time starttime = ros::Time::now();
cv::Mat undistoredImage; cv::Mat undistortedImage;
// convert from ROS msg-type to opencv matrix // convert from ROS msg-type to opencv matrix
cv_bridge::CvImagePtr imagePtr = cv_bridge::toCvCopy(original); cv_bridge::CvImagePtr imagePtr = cv_bridge::toCvCopy(original);
// apply the calculated rectification maps to undistort the image // apply the calculated rectification maps to undistort the image
cv::remap(imagePtr->image, undistoredImage, rectifyMapX, rectifyMapY, cv::INTER_LINEAR); cv::remap(imagePtr->image, undistortedImage, rectifyMapX, rectifyMapY, cv::INTER_LINEAR);
// crop relevant section from image // crop relevant section from image
undistoredImage = undistoredImage(ROI); undistortedImage = undistortedImage(ROI);
// convert to gray // convert to gray
cv::Mat undistoredImage_gray; cv::Mat undistortedImage_gray;
cv::cvtColor(undistoredImage, undistoredImage_gray, CV_RGB2GRAY); cv::cvtColor(undistortedImage, undistortedImage_gray, CV_RGB2GRAY);
// publish images // publish images
cv_bridge::CvImage colorImage(std_msgs::Header(), "rgb8", undistoredImage); cv_bridge::CvImage colorImage(std_msgs::Header(), "rgb8", undistortedImage);
pub_colorImage->publish(colorImage.toImageMsg()); pub_colorImage->publish(colorImage.toImageMsg());
cv_bridge::CvImage grayImage(std_msgs::Header(), "mono8", undistoredImage_gray); cv_bridge::CvImage grayImage(std_msgs::Header(), "mono8", undistortedImage_gray);
pub_grayImage->publish(grayImage.toImageMsg()); pub_grayImage->publish(grayImage.toImageMsg());
ROS_DEBUG("run took: %fs", (ros::Time::now()-starttime).toSec()); ROS_DEBUG("run took: %fs", (ros::Time::now()-starttime).toSec());
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment