diff --git a/.vscode/ltex.dictionary.de-DE.txt b/.vscode/ltex.dictionary.de-DE.txt index fe784058a47a1a34eb442fd87e152fb451d0f4a3..eff374b0d62c817490201abba36998983a3474af 100644 --- a/.vscode/ltex.dictionary.de-DE.txt +++ b/.vscode/ltex.dictionary.de-DE.txt @@ -68,3 +68,5 @@ GPIO grafikintensive Jetson Nano +image +fehlklassifiziert diff --git a/.vscode/ltex.hiddenFalsePositives.de-DE.txt b/.vscode/ltex.hiddenFalsePositives.de-DE.txt index 36ecef1eed907d9561fa341703b323767d418a38..8bfdacf518ad9a1f8b69043ec3f9ddc33bcba775 100644 --- a/.vscode/ltex.hiddenFalsePositives.de-DE.txt +++ b/.vscode/ltex.hiddenFalsePositives.de-DE.txt @@ -62,3 +62,11 @@ {"rule":"BEI_VERB","sentence":"^\\QDiese können dann bei erhalt der \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q auf diese reagieren.\\E$"} {"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDieser wurde als \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q mit dem Namen camera_driver unter ROS implementiert und stellt alle \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q ein aktuelles Bild auf dem \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q /img/raw zur Verfügung.\\E$"} {"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QEs ist für den Großteil der zusätzlichen Auslastung verantwortlich, sodass zusätzliche Dummies die Auslastung nur geringfügig erhöhen werden.\\E$"} +{"rule":"DE_CASE","sentence":"^\\Q[Korrekt ausgerichtetes Linsensystem] [Nicht Ideal ausgerichtetes Linsensystem] Probleme in der Ausrichtung von Sensor und Linse (nach \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q)\\E$"} +{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QUm die Parameter bestimmen zu können, müssen folglich mindestens fünf Punkte gefunden werden, von denen die Dummies und die Dummies bekannt sind.\\E$"} +{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDabei werden Muster so gewählt, dass es möglichst einfach fällt die Dummies der Punkte zu bestimmen.\\E$"} +{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDort sieht man, dass für diese \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q der Namen lane_marker_detection gewählt wird.\\E$"} +{"rule":"DE_VERBAGREEMENT","sentence":"^\\QDie \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q callback_image() verläuft vollständig analog zur Implementierung mit \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q.\\E$"} +{"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QUm kleine Störungen im Bild, welche bestehende Kanten verzerren oder als falsche Kante detektiert werden könnten, zu reduzieren, wird das Bild mit einem gauss-filter Gaußschen Filter geglättet.\\E$"} +{"rule":"IN_WEISS","sentence":"^\\QIm Gegensatz zu Alternativen, wie einer reinen Gradientenbetrachtung, liefert der \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q Kantenmarkierungen, hier in weiß, die nur einen \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q breit sind.\\E$"} +{"rule":"DE_CASE","sentence":"^\\QUm die Datenmenge gering und die Laufzeit schnell zu halten, werden lediglich die vier Klassen Vertikal, Horizontal, Diagonal 1 und Diagonal 2 verwendet.\\E$"} diff --git a/Bachelorarbeit.pdf b/Bachelorarbeit.pdf index baaed55dfec1f550a3a81dc6c91891c6f41e3e57..e90698f258c94d456f55bb893abbc2750d58a352 100644 Binary files a/Bachelorarbeit.pdf and b/Bachelorarbeit.pdf differ diff --git a/Bachelorarbeit.tex b/Bachelorarbeit.tex index 0c1c848a6a9643147e1046259ee434c29f012671..9e9f13d1165d05c1f69cfbef5ff50a1642d50ca8 100644 --- a/Bachelorarbeit.tex +++ b/Bachelorarbeit.tex @@ -39,7 +39,7 @@ \title[Video-basierte Erkennung von Fahrspurmarkierungen auf mobilen Robotern]{Video-basierte Erkennung von Fahrspurmarkierungen\\ auf mobilen Robotern} %\subtitle{Subtitle} -\date{09.05.2022 -- \today} +\date{09.05.2022 -- \today \todo{Nur Endatum oder ganzen Zeitraum?}} \erstpruefer{Prof. Dr.-Ing. Hanno Homann} \zweitpruefer{Prof. Dr.-Ing. Martin Mutz} diff --git "a/bib/Glossareintr\303\244ge.tex" "b/bib/Glossareintr\303\244ge.tex" index ed04197eb60be41774e715dea32af32c0adf96d7..a84e8b391c1efc2acfb8c2a8749b9e77acb5f65a 100644 --- "a/bib/Glossareintr\303\244ge.tex" +++ "b/bib/Glossareintr\303\244ge.tex" @@ -3,13 +3,13 @@ \newglossaryentry{C++}{ name={C\nolinebreak[4]\hspace{-.05em}\raisebox{.3ex}{\footnotesize\textbf{++}}}, description={ - Eine relativ hardwarenahe Programiersprache + Eine hardwarenahe, performante Programiersprache } } \newglossaryentry{python}{ name={Python}, description={ - Eine höher, sehr einfach zu benutzende Programmiersprache. + Eine höhere, einfach zu benutzende Programmiersprache. } } @@ -19,7 +19,7 @@ sort={ROS}, description={ Das Robot Operating System ist eine Sammlung von Softwarebibliotheken und Werkzeugen, die hilfreich beim Erstellen von Roboter Applikationen - sind. Es enthält Treiber, fertig implementierte Standardalgoritmen und andere hilfreiche Funktionen. Es ist vollständig Open Soruce. + sind. Es enthält Treiber, fertig implementierte Standardalgoritmen und andere hilfreiche Funktionen. Es ist vollständig Open Source. } } @@ -28,7 +28,7 @@ text={Node}, description={ 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} abonnieren, um die dort veröffentlichten Informationen weiter zu verarbeiten. } } @@ -45,8 +45,8 @@ text={Message}, sort={ROS Message}, description={ - Ein Datensatz der von ROS von einer \gls{ROS Node} andere \glspl{ROS Node} übertragen wird. Sie wird unter einem \gls{Topic} veröffentlicht - und alle abonenten diese \glspl{Topic} erhalten die Message. \gls{ROS} definiert eigene Datentypen denen Nachrichten entsprechen müssen. + Ein Datensatz, der von ROS von einer \gls{ROS Node} an eine andere übertragen wird. Sie wird unter einem \gls{Topic} veröffentlicht + und alle Abonenten diese \glspl{Topic} erhalten die Message. \gls{ROS} definiert eigene Datentypen denen Nachrichten entsprechen müssen. } } @@ -55,8 +55,8 @@ text={Topic}, sort={ROS Topic}, description={ - Über ein Topic stellt eine \gls{ROS Node} Daten bereit. Es ist eine Bekanntmachung, das andere \glspl{ROS Node} hier Informationen erhalten - können. + Über ein Topic stellt eine \gls{ROS Node} \glspl{ROS Message} bereit. Es ist eine Bekanntmachung, das andere \glspl{ROS Node} hier + Informationen erhalten können. } } @@ -71,7 +71,7 @@ \newglossaryentry{Callback}{ name={Callback-Funktion}, description={ - Eine Funtkion, die unter bestimmten Bedingungen automatisch aufgerufen wird. Im Bezug auf \gls{ROS} geht es meistens um Funktionen die für + Eine Funktion, die unter bestimmten Bedingungen automatisch aufgerufen wird. Im Bezug auf \gls{ROS} geht es meistens um Funktionen die für jede Nachricht auf einem abonnierten \gls{Topic} mit deren Inhalt aufgerufen werden. } } @@ -79,35 +79,42 @@ \newglossaryentry{OpenCV}{ name={OpenCV}, description={ - OpenCV ist eine Open Source Software Bibliotek mit typischen Algorithem und Funktionen für die Bildverarbeitung, \emph{Computer Vision} und - maschinelles Lehren. + OpenCV ist eine Open Source Software Bibliothek mit typischen Algorithmen und Funktionen für die Bildverarbeitung, \emph{Computer Vision} und + maschinelles Leren. } } \newglossaryentry{JetBot}{ name={JetBot}, description={ - Der in deser Arbeite verwendete Roboter. Gesteuert wird er von einem NVIDIA Jetson Nano Entwicklerboard, auf welchem die hier erstellten + Der in deser Arbeit verwendete Roboter. Gesteuert wird er von einem NVIDIA Jetson Nano Entwicklerboard, auf welchem die erstellten Programme laufen. } } \newglossaryentry{Welt-coords}{ name={Weltkoordinaten}, + text={Weltkoordinate}, + plural={Bildkoordinaten}, description={ - Ein 3D-Koordinatensystem, das die gesamet Scene/Welt des derzeitigen Systems umfasst. + Ein 3D-Koordinatensystem, das die gesamte Scene/Welt des derzeitigen Systems umfasst. } } + \newglossaryentry{Objekt-coords}{ name={Fahrzeugkoordinaten (Objektkoordinaten)}, - text={Fahrzeugkoordinaten}, + text={Fahrzeugkoordinate}, + plural={Fahrzeugkoordinaten}, description={ - Ein 3D-Koordinatensystem welches relativ zum Fahrzeug/Objet liegt. Es bewegt sich mit dem Fahrzeug und hat seinen Uhrsprung im/nahe am + Ein 3D-Koordinatensystem welches relativ zum Fahrzeug/Objekt liegt. Es bewegt sich mit dem Fahrzeug und hat seinen Uhrsprung im/nahe am Fahrzeug. } } + \newglossaryentry{Bild-coords}{ name={Bildkoordinaten}, + text={Bildkoordinate}, + plural={Bildkoordinaten}, description={ Ein Koordinatensystem welches die sicht der Kamera Widerspiegelt. X- und Y-Koordinaten korrospondieren dabei zu pixelkoordinaten und die Z-Achse stellt die Entfernung zur Kamera dar. @@ -117,41 +124,37 @@ \newglossaryentry{ROI}{ name={ROI, kurz für Region of Interest}, text={ROI}, - description={ - Ein Bildbereich, der für die derzeitige Anwendung relevant ist. Das restliche Bild wird vernachlässigt. - } + description={Ein Bildbereich, der für die derzeitige Anwendung relevant ist. Das restliche Bild wird vernachlässigt.} } \newglossaryentry{gauss-filter}{ - name={Gaussscher Filter}, + name={Gaußscher Filter}, 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 bildet den mittels einer 2D-Gaußfunktion gewichteten Mittelwert einer Nachbarschaft. } } \newglossaryentry{canny}{ name={Canny-Edge-Detektor}, description={ - Ein Algorithmus zur Erkennung von Kanten in Bildern. + Ein Algorithmus zur Erkennung von Kanten in Bildern, entwickelt von John Canny \cite{Canny:computationAlapproachEdgeDetection}. } } \newglossaryentry{Gradientenorientierung}{ name={Gradientenorientierung}, - description={ - Richtung des Farbgradienten in einer Pixelnachbarschaft. Verläuft von dunklen Bildbereichen zu hellen Bildbereichen. - } + description={Richtung des Farbgradienten in einer Pixelnachbarschaft. Verläuft von dunklen zu hellen Bildbereichen.} } \newglossaryentry{Kernel}{ name={Kernel}, plural={Kerneln}, - description={Eine Matrix die beim Filtern und verarbeiten von Bildern verwendet wird. Mit ihr werden Pixelnachbarschafen gewichtet.} + description={Eine Matrix die beim Filtern und Verarbeiten von Bildern verwendet wird. Mit ihr werden Pixelnachbarschaften gewichtet.} } \newglossaryentry{Pixel}{ name={Pixel}, - description={Ein einzelner Bildpunkt. Er hat einen Wert zwischen Scharz ($0$) und Weiß ($255$)} + description={Ein einzelner Bildpunkt. Er hat einen Wert zwischen Schwarz ($0$) und Weiß ($255$).} } \newglossaryentry{Pixelnachbarschaft}{ diff --git a/chap/ausblick.tex b/chap/ausblick.tex index f2912dd10d2d94c975f0122a73ac3fad48ff19ea..dbf254653f8df8455f8b3da33f46bfdd638c874d 100644 --- a/chap/ausblick.tex +++ b/chap/ausblick.tex @@ -4,7 +4,6 @@ des Programmes noch Weiterentwicklungsmöglichkeiten. Dadurch könnte die Erfassungsgenauigkeit insbesondere in schwierigen Situationen erhöht und weitere Informationen gewonnen werden. - \subsubsection{Mehr Orientierungsklassen} Derzeit wird bei der Klassifizierung der Kantenpixel lediglich in vier unterschiedliche Klassen eingeteilt. Das kann insbesondere bei Kurven @@ -14,17 +13,14 @@ Durch mehr Klassen und Berücksichtigung von benachbarten Klassen in der Linienverfolgung ließen sich potenziell auch gekrümmte Linien verfolgen. - \subsubsection{Reduzierung von Falschklassifizierungen} - 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 - 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. + Derzeit werden vor allem bei weiter von der Kamera entfernten Linien, also vor allem den Spurmarkierungen benachbarter Spuren, viele + \gls{Pixel} fehlklassifiziert. Wie in \autoref{fig: schlechte kante} gezeigt, wird eine Kante durch solche \gls{Pixel} unterbrochen und 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 - Genauigkeit bei der Klassifizierung wäre ebenfalls denkbar. - + Genauigkeit bei der Klassifizierung wäre ebenfalls denkbar, vor allem da dies bereits teilweise durch den vorherigen Punkt mit abgedeckt ist. \subsubsection{Datenübergabe an weitere Prozesse} @@ -35,9 +31,8 @@ Diese liegen vor und können über die definierte Headerdatei genutzt werden, es findet aber noch keine Übertragung mittels \gls{ROS} statt. Diese müsste bei Bedarf eingerichtet werden. - \subsubsection{Weiters Optimierungspotenzial} Der Versuch den \gls{C++} Quellcode durch eine eigene Implementierung des \gls{canny} zu optimieren, hat leider keine nutzbaren Ergebnisse erbracht. Grundsätzlich sind die gemachten Überlegungen aber gültig und mit tieferem Verständnis der Programmiersprache wäre eine erfolgreiche - Optimierung möglich. Da \gls{OpenCV} Quelloffen ist, wäre es auch denkbar, deren Quellcode direkt zu verwenden. + Optimierung möglich. Da \gls{OpenCV} quelloffen ist, wäre es auch denkbar, deren Quellcode direkt zu verwenden. diff --git a/chap/fazit.tex b/chap/fazit.tex index 7c7157a3aec9d8686563c86188ce176f254e270a..4e88e41d3a83fa3f8dde4d62b9b4c1ba6290be78 100644 --- a/chap/fazit.tex +++ b/chap/fazit.tex @@ -4,9 +4,9 @@ Echtzeit auszustatten. 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 vorhandene Verzerrungen korrigiert werden. Insbesondere die radiale Verzerrung, die gerade Linien wie - 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. + 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ähernd vollständig entfernt werden. Dies verbesserte die + Voraussetzungen für die nachfolgenden Schritte. Die dabei gemessene Durchlaufzeit pro Bild von $\approx 4\,\ms$ garantierte eine Echtzeitfähigkeit. Außerdem wurde die Erkennung der Fahrspurmarkierungen erfolgreich implementiert. Der Algorithmus wurde in der Programmiersprache \gls{python} entwickelt und getestet, wobei insbesondere die einfache Syntax und gute Debuggmöglichkeiten das Vorgehen vereinfacht haben. Da \gls{python} aber @@ -15,12 +15,11 @@ Die Erkennung von Fahrspurmarkierungen wurde mittels Kantenerkennung durch einen \gls{canny} und Klassifizierung der einzelnen Kantenpixel entsprechend ihrer Orientierung umgesetzt. So ließen sich aus den Kanten erfolgreich zusammenhängende Linien einer Orientierung ableiten und deren - Start- und Endpunkte abspeichern. Durch Paarbildung zwischen rechten und linken Linien konnten dann Rückschlüsse über einzelne - Fahrspurmarkierungen gezogen werden. Die vier Punkte eines Linienpaares konnten dann als Kontur abgespeichert und die Mittellinie berechnet - werden. + Start- und Endpunkte abspeichern. Durch Paarbildung zwischen rechten und linken Linien konnten Rückschlüsse über einzelne + Fahrspurmarkierungen gezogen werden. Die vier Punkte eines Linienpaares wurden dann als Kontur abgespeichert und die Mittellinie berechnet. - 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. + Zur Veranschaulichung wurden die Konturen in ein leeres Bild eingezeichnet und dieses ebenfalls veröffentlicht. Da die gemessene Durchlaufzeit pro + Bild bei $\approx 3,5\,\ms$ lag, war ein direkter Vergleich 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 - möglich\todo{Stattdessen erfolgreich ?}. Ziertuch wurden deutlich schlechtere Laufzeiten erzielt. + möglich\todo{Stattdessen erfolgreich ?}. Hierdurch wurden deutlich schlechtere Laufzeiten erzielt. diff --git a/chap/implementation.tex b/chap/implementation.tex index e7b7a22af096fead7f157c1c63f4e65fbd2f4f7e..7a51133da026c780e4061d055160779cf574dd94 100644 --- a/chap/implementation.tex +++ b/chap/implementation.tex @@ -16,7 +16,7 @@ \section{Konzeptionierung in Python} - Die Entwicklung und Konzeptionierung des Algorithmus erfolgt in \gls{python}. Diese Sprache muss nicht kompiliert werden und hat eine sehr + Die Entwicklung und Konzeptionierung des Algorithmus erfolgt in \gls{python}. Diese Sprache muss nicht kompiliert werden und hat eine einfache Syntax, wodurch das Testen beschleunigt wird und sie generell einfacher zu verwenden ist. Der Algorithmus lässt sich in mehrere Einzelschritte aufteilen, welche in den folgenden Unterkapiteln beschreiben im Einzelnen beschrieben @@ -44,20 +44,20 @@ \subsection{Kantenerkennung mittels Canny-Edge-Detektor} 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 detektiert 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 $\sigma=1,5$ verwendet. \gls{OpenCV} stellt hierzu die Funktion \lstinline{GaussianBlur()} zur Verfügung, der das geladene Bild, die 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 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 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 - 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 Schwellwerts, Lücken zwischen diesen Pixeln geschlossen werden. \cite{Nischwitz:Computergrafik2} Auch dieser Algorithmus ist in \gls{OpenCV} bereits implementiert und wird für den ersten Entwurf verwendet. Die Funktion bekommt das - geladene und geglättet Bild sowie die beiden Hysterese-Schwellwerte übergeben. Diese ist auch in \autoref{code: canny} gezeigt. + geladene und geglättete Bild sowie die beiden Hysterese-Schwellwerte übergeben. Diese ist auch in \autoref{code: canny} gezeigt. \begin{lstlisting}[ float, @@ -77,7 +77,7 @@ 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 Gradientenbetrachtung, liefert der \gls{canny} - Kantenmarkierungen, hier in Weiß, die nur 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. \begin{figure} @@ -95,12 +95,12 @@ 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} - verwendend. Zusätzlich wird noch die Richtungsinformation als Vorzeichen abgespeichert. + verwendet. Zusätzlich wird noch die Richtungsinformation als Vorzeichen abgespeichert. 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 - $\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 + $\vec{G}$ berechnet werden. Mit diesem Winkel kann nun entsprechend der \autoref{fig: gadienten orientierung} die Klasse bestimmt werden. + Dabei ist zu beachten, dass $\vec{G}$ immer orthogonal auf der eigentlichen Kante steht. Deshalb ist die Klasse \emph{Vertikal} auch der links-rechts Achse der Abbildung zu finden. \begin{figure} @@ -172,10 +172,10 @@ \end{split} \end{equation} - Dazu werden zuerst die Gradienten $d_x$ und $d_y$ ermittelt. Dazu wird die $3\!\times\!3$ \gls{Pixelnachbarschaft} des aktuellen Pixels + Dazu werden zuerst die Gradienten $d_x$ und $d_y$ ermittelt. Die $3\!\times\!3$ \gls{Pixelnachbarschaft} des aktuellen Pixels wird dabei elementweise mit dem jeweiligen Sobel-\gls{Kernel} multipliziert und die Summe der Ergebnismatrix gebildet (siehe \autoref{eq: dx dy}). Das Pythonpaket \lstinline{numpy} stellt hierfür sehr hilfreiche Funktion zum Arbeiten mit Matrizen zur Verfügung. Dadurch lässt sich - diese Operation in wenigen Zeilen durchführen, wie \autoref{code: dx dy} gezeigt. + diese Operation in wenigen Zeilen durchführen, wie \autoref{code: dx dy} zeigt. \begin{lstlisting}[ float, @@ -192,7 +192,7 @@ 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. - Durch eine Folge von Bedingungen wird nun 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 ermittelt und im 5. Bit abgespeichert. Dies vereinfacht die folgenden Abfragen, da für die \emph{Vertikal} und \emph{Horizontal} Klasse der Betrag des Winkels ausreicht. @@ -222,7 +222,7 @@ \end{lstlisting} 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. Dieses Bild ist in \autoref{fig: classified edges} gezeigt. \begin{figure} @@ -266,7 +266,7 @@ Schritte, das Zusammenfassen von gleich klassifizierten Kantenpixeln zu durchgängigen Linien und das Zusammenfassen von Linien zu einer Fahrspurmarkierung. - 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 + Für den ersten Schritt ist es ein weiteres Mal nötig, über das gesamte Bild zu iterieren. 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 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. @@ -302,13 +302,13 @@ \end{lstlisting} Hat ein \gls{Pixel} keine weiteren Nachbarn, ist er der Endpunkt dieser Linie. Start- und Endpunkt werden in ein Linienobjekt - zusammengefasst und abgespeichert. Zusätzliche wird ebenfalls die Orientierungsklasse mit abgespeichert. + zusammengefasst und abgespeichert. Zusätzlich 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} beschriebenen Störungen viele kurze Linien gefunden werden, deren Berücksichtigung zu viel Rechenzeit in Anspruch nehmen würde, werden Linien unter einer Minimallänge von 5 Pixeln vernachlässigt. - Akzeptierte Linien werden ihrer Orientierung entsprechen in einzelnen Listen abgespeichert, sodass am Ende eine Liste für jede + Akzeptierte Linien werden ihrer Orientierung entsprechend in einzelnen Listen abgespeichert, sodass am Ende eine Liste für jede Orientierungsklasse entstanden ist. \medskip @@ -317,10 +317,9 @@ 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{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 gesucht. Ein solcher ist gefunden, wenn die Start- und Endpunkte beider Linien innerhalb bestimmter Bereiche liegen. - \begin{lstlisting}[ float, style=example, @@ -370,7 +369,7 @@ Der letzte Vergleich \ref{subfig: demo B} zeigt eine Szene mit vollständig durchgezogener Linie. Dies ist aus dem Grund schwierig, 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 gefundenen Marker wirkt verzehrt. \begin{figure} \subfigure[lange Linien]{ \label{subfig: demo C} @@ -390,7 +389,7 @@ \end{figure} - \section{Implementierung in eine ROS Node} + \section{Implementierung in einer ROS Node} Um den Algorithmus zur Erkennung von Spurmarkierungen auf jedes Kamerabild anwenden zu können, wird er in einer \gls{ROS Node} umgesetzt. Da der Python-Code bereits auf einem leistungsfähigen Entwickler-PC Laufzeiten von $>0,25\,\s$ hat, wird die Programmiersprache \gls{C++} für die @@ -399,7 +398,7 @@ \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. - 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} + Dort sieht man, dass für diese \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, detektierten Spurmarkierungen wird nach Durchlauf des Algorithmus auf dem eigenen \gls{Topic} \lstinline{/img/lanemarkings} veröffentlicht. @@ -427,8 +426,8 @@ cv::Canny(image, canny, 180, 50); \end{lstlisting} - Die Implementierung der Kantenklassifizierung in \gls{C++} läuft im Groben sehr ähnlich zur \gls{python}-Version, die wichtigsten - Unterschied sind die For-Schleifen für beide Dimensionen des Bildes, welche explizit einzeln verwendet werden müssen, und die Beachtung + Die Implementierung der Kantenklassifizierung in \gls{C++} läuft im Allgemeinen sehr ähnlich zur \gls{python}-Version, die wichtigsten + Unterschiede 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 @@ -566,7 +565,7 @@ \begin{lstlisting}[ float, style=example, - caption={Line verfolgen, bis keine Nachbarn mehr existieren}, + caption={Linie verfolgen, bis keine Nachbarn mehr existieren}, label=code: follow c++, language=C++ ] @@ -598,23 +597,23 @@ \end{lstlisting} \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 gefundenen 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. - 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} und \lstinline{v} manuell wieder auf die Startkoordinaten zurückgesetzt werden müssen. \subsubsection{Paarbildung} - 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. + Auch die Bildung von Linienpaaren aus einer rechten und linken Linie erfolgt analog zu \gls{python}. Hier gibt es auch keine signifikanten + Unterschiede in der Umsetzung, wie in \autoref{code: pairing c++} zu sehen ist. Auch hier wird mit den gefundenen Paaren wieder ein Linienmarker-Objekt erzeugt und zur oberen Nutzung abgespeichert. \begin{lstlisting}[ float, style=example, - caption={Line verfolgen, bis keine Nachbarn mehr existieren}, + caption={Linie verfolgen, bis keine Nachbarn mehr existieren}, label=code: pairing c++, language=C++ ] @@ -636,7 +635,7 @@ \end{lstlisting} Da noch nicht klar ist, wie genau die gefundenen Daten potenziell zukünftigen Prozessen zu Verfügung gestellt werden sollen, werden diese - zurzeit nicht veröffentlicht. Stadtmessen wird analog zur Python-Implementierung ein Bild mit eingezeichneten Spurmarkern erzeugt und als + zurzeit nicht veröffentlicht. Stattdessen wird analog zur Python-Implementierung ein Bild mit eingezeichneten Spurmarkern erzeugt und als visuelles Ergebnis zur Verfügung gestellt. Dieses kann unter dem \gls{Topic} \lstinline{/img/temp} abgerufen und live angeschaut werden. \bigskip @@ -680,14 +679,14 @@ \end{table} - \section{Optimierung\todo{oder "Versuchte Optimierung" ??} durch eigene Implementierung des Canny-Edge-Detectors} + \section{Optimierung durch eigene Implementierung des Canny-Edge-Detectors} Im Folgenden wird untersucht, ob eine eigene Implementierung des \gls{canny} einen Vorteil gegenüber der Verwendung von \gls{OpenCV} bietet. - Da die Bibliotheksfunktionen viel potenziell nicht benötigte Zusatzfunktionen und Schutzmechanismen enthalten, welche Zeit + Da die Bibliotheksfunktionen viel potenziell nicht erforderlich Zusatzfunktionen und Schutzmechanismen enthalten, welche Zeit und Performance benötigen und für deren Verwendung eine Datenumwandlung notwendig ist, kann eine eigene Implementierung hier Vorteile bieten. Außerdem führt der \gls{canny} intern bereits eine Sobel-Filterung durch und es ließe sich die Klassifizierung dort direkt mit durchführen. - Das eliminiert die Notwendigkeit zweimal über das Bild zu integrieren. + Das eliminiert die Notwendigkeit zweimal über das Bild zu iterieren. \subsection{Anpassung des Quellcodes} @@ -697,10 +696,10 @@ ermöglicht lediglich den Zugriff auf die Daten über $x$ und $y$ Koordinaten. \autoref{code: image class} zeigt die Umsetzung mit dem Namen \lstinline{Image}. Initialisiert werden kann eine Instanz entweder nur mit - Höhe und Breite, wodurch ein neues, leeres Bild erzeugt wird, oder mit einem existieren Bild, wobei dessen Daten übernommen werden. + Höhe und Breite, wodurch ein neues, leeres Bild erzeugt wird, oder mit einem existierenden Bild, wobei dessen Daten übernommen werden. - Um auf die Bilddaten zuzugreifen, werden die Operatoren \lstinline{[]} überladen, sodass ein Punkt bestehen aus einer $x$ und einer $y$ - Koordinate übergeben werden kann. Das entsprechende Pixel wird dann zurückgegeben oder beschreiben. + Um auf die Bilddaten zuzugreifen, wird der Operator \lstinline{[]} überladen, sodass ein Punkt bestehend aus einer $x$ und einer $y$ + Koordinate übergeben werden kann. Das entsprechende Pixel wird dann zurückgegeben oder beschrieben. \begin{lstlisting}[ float, @@ -722,12 +721,12 @@ }; \end{lstlisting} - Die \gls{Callback} \lstinline{callback_image()} verläuft völlig analog zur Implementierung mit \gls{OpenCV}. Der Unterschied liegt in der - 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}). + Die \gls{Callback} \lstinline{callback_image()} verläuft vollständig analog zur Implementierung mit \gls{OpenCV}. Der Unterschied liegt in + der 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}). - 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. + Zusätzlich wird außerdem die Klassifizierung durchgeführt. Da im ersten Schritt die Sobel-Gra\-di\-enten ohnehin berechnet werden, können + hierüber ohne viel Zusatzaufwand auch die Gradientenwinkel mit bestimmt werden. Der \autoref{code: own canny 1} zeigt diesen Schritt. \begin{lstlisting}[ float, @@ -786,8 +785,8 @@ Der letzte Schritt der Hysterese-Grenzwertbildung wurde für diese Anwendung zu einem simplen Grenzwert vereinfacht. Dies erzeugt ausreichend gute Ergebnisse. - Wichtig ist außerdem, dass im Gegensatz zu einem herkömmlichen \gls{canny} kein Binarisiertes Bild, mit nur völlig weißen oder völlig - schwarzen Pixelwerten, zurückgegeben wird. Stadtmessen enthalten alle detektierten Kantenpixel bereits den Wert, der ihre Klasse + Wichtig ist außerdem, dass im Gegensatz zu einem herkömmlichen \gls{canny} kein binarisiertes Bild, mit nur völlig weißen oder völlig + schwarzen Pixelwerten, zurückgegeben wird. Stattdessen enthalten alle detektierten Kantenpixel bereits den Wert, der ihre Klasse repräsentiert. \begin{lstlisting}[ @@ -820,12 +819,12 @@ \end{figure} Wie aus \autoref{fig: jtop markings own} abzulesen ist, ist die CPU-Auslastung mit durchschnittlichen $37.75\,\percent$ exakt identisch - zur Implementierung mit \gls{OpenCV}. Das ist wenig überraschen, da der Algorithmus Grunde immer noch dieselbe Arbeit verrichtet. + zur Implementierung mit \gls{OpenCV}. Das ist wenig überraschend, da der Algorithmus Grunde immer noch dieselbe Arbeit verrichtet. \pagebreak[3] Die Messung der Laufzeit zeigt allerdings, dass die eigene Implementierung deutlich weniger performant ist. Mit einer Durchlaufzeit von $\approx 22,58\,\ms$ ist diese Version um mehr als Faktor 6 größer. Hier gleichen die Vorteile durch das Kombinieren von Einzelschritten - die Leistungsfähigkeit einer stark optimierten Bibliotheksfunktion also leider nicht mal annähern aus. + die Leistungsfähigkeit einer stark optimierten Bibliotheksfunktion also leider nicht einmal annähernd aus. \begin{table} \caption{Gemessene Laufzeit bei 10 Durchläufen der \gls{Callback}} diff --git a/chap/kalibrierung.tex b/chap/kalibrierung.tex index ed458ae56a6da919f554e875502b206c8ce40288..c6aac89f6446b17132b33e5cccd0d29a2946bb0d 100644 --- a/chap/kalibrierung.tex +++ b/chap/kalibrierung.tex @@ -1,13 +1,15 @@ -\chapter{Kamera Kalibrierung} \label{chap: kalibrierung} +\chapter{Kamera-Kalibrierung} \label{chap: kalibrierung} - Damit die später beschriebene Fahrspurerkennung möglichst zuverlässig funktioniert und möglichst reproduzierbar ist, wird eine Kalibrierung - vorgenommen. Das Vorgehen dazu und die Ergebnisse sind im folgenden Kapitel dokumentiert. + Damit die später beschriebene Fahrspurerkennung zuverlässig funktioniert, wird eine Kalibrierung der Kamera vorgenommen. Das Vorgehen dazu und die + Ergebnisse sind im folgenden Kapitel dokumentiert. \section{Intrinsische Kalibrierung} \label{sec: intrinsic} - 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 - Realität alle parallel verlaufen, im Bild aber gekrümmt aussehen. + 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. Sie verlaufen in + der Realität alle parallel, im Bild sehen sie aber gekrümmt aus. + + Es können unterschiedliche Arten von Verzerrung in einem Kamerabild auftreten, die in den folgenden Unterkapiteln einzeln erläutert werden. \begin{figure} \includegraphics[width=.4\textwidth]{img/unkalibriert.png} @@ -20,9 +22,9 @@ 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 - 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 - ist in \autoref{fig: optische verzerrung} die Auswirkung dieser Verzerrung auf ein Rechteckmuster gezeigt. + der Lichtstrahlen in der Kamera, die mit der Entfernung vom Mittelpunkt immer stärker wird. Nimmt die Ablenkung mit der Entfernung zu, + spricht man von positiver, kissenförmiger Verzerrung, den umgekehrte Fall nennt man negative, tonnenförmige Verzerrung. Zur Verdeutlichung + ist in \autoref{fig: optische verzerrung} die Auswirkung dieser Verzerrungen auf ein Rechteckmuster skizziert. \begin{figure} \subfigure[Kissenförmige Verzerrung]{\includegraphics[page=3,width=.3\textwidth]{svg/Lens_distorsion.pdf}} @@ -45,15 +47,23 @@ \end{equation} - \pagebreak \subsection{Tangentiale Verzerrung} - Die tangentiale Verzerrung entsteht durch kleine Ausrichtungsfehler im Linsensystem. Dadurch liegt die Linse nicht perfekt in der - Bildebene und der Bildmittelpunkt sowie die Bildausrichtung können leicht verschoben sein. + Die tangentiale Verzerrung entsteht durch kleine Ausrichtungsfehler im Linsensystem. Dadurch liegt die Linse oder der Sensor nicht ideal + in der Bildebene und der Bildmittelpunkt sowie die Bildausrichtung können leicht verschoben sein. Das einfallende Licht wird dadurch + nicht mehr an dieselbe Stelle gebündelt wie im Idealfall und das Bild wird verzehrt. Dies ist in \autoref{fig: linsensystem} skizziert. \begin{figure} - \missingfigure{Sensor-Linse alignment} + \subfigure[Korrekt ausgerichtetes Linsensystem]{ + \hspace*{.15\textwidth} + \includegraphics[page=1]{svg/Lens_alignment.pdf} + \hspace{.15\textwidth}} + \subfigure[Nicht Ideal ausgerichtetes Linsensystem]{ + \hspace*{.15\textwidth} + \includegraphics[page=2]{svg/Lens_alignment.pdf}\hfill + \hspace{.15\textwidth}} \caption{Probleme in der Ausrichtung von Sensor und Linse (nach \cite{Matlab:CameraCalibration})} + \label{fig: linsensystem} \end{figure} Mathematisch wird diese Verzerrung durch den folgenden Zusammenhang beschrieben. \cite{Hanning:highPrecisionCamCalibration} @@ -66,31 +76,30 @@ \end{equation} - \bigskip - Die beiden Verzerrungsarten zusammen werden also durch fünf Parameter beschrieben, die sogenannten Verzerrungskoeffizienten. Historisch + Somit werden beiden Verzerrungsarten zusammen durch fünf Parameter beschrieben, die sogenannten Verzerrungskoeffizienten. Historisch begründet wird dabei $k_3$ an das Ende geschrieben, da dieser Parameter früher kaum berücksichtigt wurde. \begin{equation} D_{coeff} = (k_1, k_2, p_1, p_2, k_3) \end{equation} - Um die Parameter bestimmen zu können, müssen also mindestens fünf Punkte gefunden werden, von denen die \gls{Welt-coords} und die - Bildkoordinaten bekannt sind. Da sich die Punktepaare aber nur schwer mathematisch perfekt bestimmen lassen, werden mehr Paare benötigt, + Um die Parameter bestimmen zu können, müssen folglich mindestens fünf Punkte gefunden werden, von denen die \glspl{Welt-coords} und die + \glspl{Bild-coords} bekannt sind. Da sich die Punktepaare aber nur schwer mathematisch perfekt bestimmen lassen, werden mehr Paare benötigt, um ein überbestimmtes Gleichungssystem zu erhalten und dieses nach dem geringsten Fehler zu lösen. \cite{OpenCV:CameraCalibration} \medskip In der Praxis werden 2D-Muster verwendet, um Punktepaare zu bestimmen. Da sich alle Punkte dieser Muster in einer Ebene befinden, kann der - Ursprung 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} - 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 \glspl{Welt-coords} der Punkte zu bestimmen. Beispielsweise sind bei einem Schachbrettmuster die Entfernungen alle identisch und können als $1$ angenommen werden, wodurch die Koordinaten der Punkte direkt ihrer Position im Muster entsprechen. \section{Durchführung der intrinsischen Kalibrierung} - Zur Durchführung der Kalibrierung wir ein Python-Script erstellt, um den Vorgang einfach und wiederholbar zu machen. Als Vorlage für + Zur Durchführung der Kalibrierung wird 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}. Außerdem wird eine \gls{ROS Nodelet} erstellt, welches die Kalibrierung auf den Video-Stream anwendet und korrigierte Bilder veröffentlicht. @@ -99,11 +108,11 @@ \subsection{Python Script zur Durchführung der Kalibrierung} 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 - 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 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 - Kalibriermusters in \autoref{fig: kalibriermuster} grün markiert. + Schachbrettartiges Kalibriermuster befindet. Wichtig ist es, dasselbe Muster und dieselbe Auflösung für alle Bilder zu verwenden. Es muss + 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 Anzahl der Felder gemeint, sondern die Anzahl der inneren Kreuzungspunkte. Ein normales Schachbrett hat + beispielsweise $8 \!\times\! 8$ Felder, aber nur $7 \!\times\! 7$ interne Kreuzungen. Zur Verdeutlichung sind die Kreuzungspunkte des + verwendeten Kalibriermusters in \autoref{fig: kalibriermuster} grün markiert. \begin{figure} \includegraphics[width=.4\textwidth]{img/kalibrieren_PATTER.png} @@ -111,7 +120,7 @@ \label{fig: kalibriermuster} \end{figure} - Es wird nun ein Standard Schachbrett als Kalibriermuster verwendet, wie es bereits in \autoref{fig: kalibriermuster} zu sehen ist. Dessen + Es wird nun ein standard Schachbrett als Kalibriermuster verwendet, wie es bereits in \autoref{fig: kalibriermuster} zu sehen ist. Dessen Kalibriermustergröße von $7 \!\times\! 7$ wird im Code als Konstante definiert: \begin{lstlisting}[ @@ -167,11 +176,11 @@ \end{lstlisting} Dabei ist es gar kein Problem, wenn nicht in jedem Bild das Kalibriermuster gefunden werden kann, solange insgesamt ausreichend nutzbare - Bilder vorhanden sind. Bei nicht nutzbaren Bildern gibt \lstinline{findChessboardCorners()} \lstinline{None} zurück und das Bild wird einfach + Bilder vorhanden sind. Bei nicht nutzbaren Bildern gibt \lstinline{findChessboardCorners()} \lstinline{None} zurück und das Bild wird übersprungen. Für alle nutzbaren Bilder werden die in \autoref{code: kali var init} erstellten Punktbezeichnungen zur Liste der gefundenen Objekte - hinzugefügt. Die Genauigkeit der gefunden Eckkoordinaten wird über die Funktion \lstinline{cornerSubPix()} erhöht und diese werden an die + hinzugefügt. Die Genauigkeit der gefundenen Eckkoordinaten wird über die Funktion \lstinline{cornerSubPix()} erhöht und diese werden an die Liste der gefundenen Bildpunkte angehängt. \begin{lstlisting}[ @@ -268,7 +277,7 @@ print(f"total error: {mean_error/len(objpoints)}") \end{lstlisting} - Mit dem verwendeten Datensatz ergibt sich ein Reprojektions-Fehler von $0,049$. Dies ist genau genug für diesen Anwendungsfall. + Mit dem verwendeten Datensatz ergibt sich ein Reprojektions-Fehler von $0,049$. Dies ist ausreichen für diesen Anwendungsfall. \subsection{Anwenden der Kalibrierung in einer ROS Node} \label{sec: undistort Node} @@ -292,7 +301,7 @@ 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 - Bildgröße benötigt und daher aus der Konfigurationsdatei gelesen. \autoref{code: intrinsic einlesen YAML} zeigt den Ablauf. Es ist + Bildgröße benötigt und daher aus der Konfigurationsdatei gelesen. \autoref{code: intrinsic einlesen YAML} zeigt diesen Ablauf. Es ist sinnvoll, dies bereits in der \lstinline{main()} Funktion durchzuführen, um die \gls{Callback} zu entlasten und dort Rechenzeit einzusparen. @@ -324,7 +333,7 @@ \end{lstlisting} Mit diesen Werten können nun \emph{Mappings} erzeugt werden, welche die geometrische Beziehung zwischen einem \gls{Pixel} im - Originalbild und einem \gls{Pixel} im entzerrten Bild abspeichern. Es werden zwei \emph{Mappings} für die X und die Y-Koordinate + Originalbild und einem \gls{Pixel} im entzerrten Bild abspeichern. Es werden zwei \emph{Mappings} für die $x$ und die $y$-Koordinate erzeugt, welche in globalen Variablen abgelegt werden. Das ist notwendig damit die Informationen der \gls{Callback} zur Verfügung stehen. @@ -335,7 +344,7 @@ werden. Mit $\alpha=1$ enthält das entzerrte Bild alle \gls{Pixel} des Originalbildes, allerdings bleiben einige \gls{Pixel} schwarz. Da die Funktion zusätzlichen eine \gls{ROI} liefert, welches den Bildausschnitt ohne schwarze \gls{Pixel} beschreibt, wird hier $\alpha=1$ verwendet. Die veröffentlichten Bilder werden zwar auf die \gls{ROI} reduziert, aber die zusätzlichen Informationen sind - grundsätzlich vorhanden und bei Bedarf kann das Programm einfach angepasst werden, um die vollständigen Bilder zu veröffentlichen. + grundsätzlich vorhanden und bei Bedarf kann das Programm angepasst werden, um die vollständigen Bilder zu veröffentlichen. \begin{lstlisting}[ float, @@ -362,7 +371,8 @@ \lstinline{toCvCopy()} kopiert die Daten des Originalbildes in eine OpenCV Matrix, welche weiter verwendet werden kann. 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 erfolgt eine lineare + Interpolation. 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. @@ -396,7 +406,7 @@ \pagebreak \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 Anwendung laufen muss, ist es wichtig, dass sie möglichst performant ist und wenig Ressourcen des JetBots verbraucht. Daher wurde die mittlere CPU Auslastung und die durchschnittliche Laufzeit der \glslink{Callback}{Call\-back-Funk\-tion}, welche für @@ -408,16 +418,16 @@ \label{fig: jtop cam+undist} \end{figure} - Der \lstinline{jtop} Screenshot in \autoref{fig: jtop cam+undist} zeigt die CPU Nutzung bei aktivem ROS-Core, Kameratreiber und - der neu erstellten Entzerrer \gls{ROS Node}. Die durchschnittliche CPU Auslastung liegt bei ungefähr $35,25\,\percent$, ist also - sogar sehr geringfügig niedriger als die in \autoref{sub: performance baseline} gemessene Grundauslastung ohne die neue \gls{ROS Node}. - Das ist aber auf die starke Fluktuation in der CPU Auslastung und daher ungenauen Messung zurückzuführen. Die Auslastung wird daher + Der \lstinline{jtop} Screenshot in \autoref{fig: jtop cam+undist} zeigt die CPU Nutzung bei aktivem ROS Core, Kameratreiber und der + neu erstellten Entzerrer \gls{ROS Node}. Die durchschnittliche CPU Auslastung liegt bei ungefähr $35,25\,\percent$, ist also sogar + sehr geringfügig niedriger als die in \autoref{sub: performance baseline} gemessene Grundauslastung ohne die neue \gls{ROS Node}. Das + ist aber auf die starke Fluktuation in der CPU Auslastung und die daher ungenauen Messung zurückzuführen. Die Auslastung wird daher 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()} zurückgegeben wird, verwendet. Die Zeit beim Start der \gls{Callback} wird abgespeichert. Nach Durchlauf der Funktion wird erneut die aktuelle Zeit bestimmt und die Differenz in Millisekunden als Debug-Nachricht ausgegeben. Die Laufzeit der \gls{ROS Node} wird über - einige Durchläufe gemittelt. Dabei ergibt sich eine Laufzeit von $\approx 4,07\,\ms$. + einige Durchläufe gemittelt. Es ergibt sich ein Mittelwert von $\approx 4,07\,\ms$. \begin{table} \caption{Gemessene Laufzeit bei 10 Durchläufen der \gls{Callback}} diff --git a/chap/standdertechnik.tex b/chap/standdertechnik.tex index f8e28d36300732f9ca36c08c8c0476eeda98206a..61597477a6a6796a2853667b89a523a07263dda2 100644 --- a/chap/standdertechnik.tex +++ b/chap/standdertechnik.tex @@ -2,7 +2,7 @@ \section{Techniken zur Fahrspurerkennung} - Das Thema Fahrspurerkennung beschäftigt die Wissenschaft und auch die Automobilindustrie bereits seit einigen Jahren. Im Folgenden möchte ich + Das Thema Fahrspurerkennung beschäftigt die Wissenschaft und auch die Automobilindustrie bereits seit einigen Jahren. Im Folgenden werden daher die existierenden üblichen Ansätze zu diesem Thema erläutern. Dabei ist besonders interessant, wie diese in diese Arbeit einfließen. @@ -11,8 +11,8 @@ Bei den klassischen und ältesten Methoden wird an die Thematik mit mathe\-ma\-tisch-geo\-me\-trisch\-en Ansätzen herangegangen. Diese werden zu Algorithmen verknüpft, um unterschiedliche Informationen herauszuarbeiten, zu verknüpfen und das Ergebnis zu verfeinern. - Grundlage bilden hierbei verschiedene Operationen, mit welchen sich Bilder verändern lassen. Solche Operationen verknüpfen eine bestimmen - menge an Pixeln eines Ursprungsbildes mittels einer mathematischen Operation, um ein neues \gls{Pixel} für das Zielbild zu ermitteln. Ein + Grundlage bilden hierbei verschiedene Operationen, mit welchen sich Bilder verändern lassen. Solche Operationen verknüpfen eine bestimmte + Menge an Pixeln eines Ursprungsbildes mittels einer mathematischen Operation, um ein neues \gls{Pixel} für das Zielbild zu ermitteln. Ein relativ simples Beispiel hierfür ist das Bilden eines Mittelwertes von jeweils drei Farbpixeln, um ein Schwarzweißbild zu erzeugen. % Häufig kommen bei diesen Operationen sogenannten \gls{Kernel} zum Einsatz. Dabei handelt es sich um Matrizen, welche je nach Anwendung @@ -25,8 +25,8 @@ % der gewichtete Mittelwert aller Nachbarpixel gebildet und das Bild geglättet. Der sehr grobe Ablauf, welcher solche Operationen zu einem Algorithmus verknüpft, ist in \autoref{fig: genersich} skizziert. Dabei sind - die Einzelschritte in der Realität jedoch häufig sehr kompliziert. Begonnen wird eigentlich immer mit einem Vorbereitung-Schritt, da die - Bilder einer Kamera nur selten direkt verwendet werden können. Teilweise ist eine solche Vorverarbeitung aber auch Hardwareseitig oder in + die Einzelschritte in der Realität jedoch häufig sehr kompliziert. Begonnen wird eigentlich immer mit einem Vorbereitungs-Schritt, da die + Bilder einer Kamera nur selten direkt verwendet werden können. Teilweise ist eine solche Vorverarbeitung aber auch hardwareseitig oder in vorgelagerten Programmteilen umgesetzt. Meisten wird das Bild außerdem in ein Schwarzweißbild umgewandelt, da so nur ein Drittel der Pixel untersucht werden müssen, was die Performance verbessert. @@ -37,14 +37,15 @@ \end{figure} Bei den sogenannten Features handelt es sich um spezifische, möglichst eindeutige Muster im Bild. Im Fall von Fahrspurmarkierungen sind - dies eigentlich immer Kannten und Ecken ebendieser. Häufig wird hier der \gls{canny} eingesetzt, der von John Canny in + dies meist Kannten und Ecken ebendieser. Häufig wird hier der \gls{canny} eingesetzt, der von John Canny \cite{Canny:computationAlapproachEdgeDetection} entwickelt wurde. Dieser Algorithmus ist sehr gut zum Identifizieren von Kantenpixeln - geeignet und erzeigt ein Binärbild, in dem nur noch ein \gls{Pixel} dicke Umrisse verbleiben. + geeignet und erzeugt ein Binärbild, in dem nur noch ein \gls{Pixel} dicke Umrisse verbleiben. - Das genaue Vorgehen um Features zu finden und zu verknüpfen, hängt von der Methode ab. In \cite{laneDetection:aReview} werden verscheiden - Möglichkeiten die Hough Transformation zu verwenden miteinander verglichen und \cite{assistanceSystem:laneDetection-vehicleRecognition} - verwendet zusätzlich eine Methode zum Kombinieren von einzelnen Liniensegmenten. \cite{robustLaneMarkingDetection} demonstriert einen - Ansatz, um direkt Mittellinien von Fahrspurmarkierungen anhand von bekannten Größenparametern abzuleiten. + Das genaue Vorgehen um Features zu finden und zu verknüpfen, ist Gegenstand von Forschung und Entwicklung. In \cite{laneDetection:aReview} + werden Beispielsweise verschieden Möglichkeiten die Hough Transformation zu verwenden miteinander verglichen und + \cite{assistanceSystem:laneDetection-vehicleRecognition} verwendet zusätzlich eine Methode zum Kombinieren von einzelnen Liniensegmenten. + \cite{robustLaneMarkingDetection} demonstriert einen Ansatz, um direkt Mittellinien von Fahrspurmarkierungen anhand von bekannten + Größenparametern abzuleiten. Das Ergebnis kann durch das Einbeziehen weiterer Informationen noch weiter verbessert werden. Oft wird hier das originale Farbbild mit einbezogen (siehe \cite{laneDetection:aReview}), aber auch das Zurückgreifen auf das vorherige Bild, wie in @@ -102,11 +103,11 @@ 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 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. Weitere Informationen sind in der + werden und später relativ simpel in eine hardwarenahe Programmiersprache übersetzt werden können. Weitere Informationen sind in der 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 - potenziell nicht benötigte Zusatzfunktionen mit bring, wird auch ein wechsel auf eine eigene Implementierung mit besserer Performance in + In dieser Arbeit wird diese Bibliothek daher insbesondere für die Entwicklungsphase verwendet. Da der Bibliothekscode jedoch auch viele + potenziell nicht benötigte Zusatzfunktionen mit bring, wird auch ein Wechsel auf eine eigene Implementierung mit besserer Performance in Betracht gezogen. @@ -133,7 +134,7 @@ Diese wird dann veröffentlicht und an alle Abonnenten des \glspl{Topic} verschickt. Diese können dann bei erhalt der \gls{ROS Message} auf diese reagieren. - So lassen einzelne Komponenten dieser Arbeit abgekapselt voneinander umsetzen und stellen ihre Ergebnisse auf potenziellen, später noch + So lassen sich einzelne Komponenten dieser Arbeit abgekapselt voneinander umsetzen und stellen ihre Ergebnisse auf potenziellen, später noch entwickelten Prozessen zur Verfügung. diff --git a/src/config.tex b/src/config.tex index 52a1396631f935a4730642290dc8cedc86551941..40d9a30db4e9002579529bb126ffaba9969987b3 100644 --- a/src/config.tex +++ b/src/config.tex @@ -56,7 +56,7 @@ % listings styling: \renewcommand{\lstlistingname}{Code} % listings name - \renewcommand{\lstlistlistingname}{Codeverzeichniss} % list of listings + \renewcommand{\lstlistlistingname}{Codeverzeichnis} % list of listings \lstdefinestyle{example}{ backgroundcolor=\color{lightgray!20!white}, breakatwhitespace=true, diff --git a/svg/Lens_alignment.svg b/svg/Lens_alignment.svg new file mode 100644 index 0000000000000000000000000000000000000000..f215229483b236c5d5d9ffebd913a60faf4b32d1 --- /dev/null +++ b/svg/Lens_alignment.svg @@ -0,0 +1,200 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24.241247mm" + height="29.358212mm" + viewBox="0 0 24.241247 29.358212" + version="1.1" + id="svg25531" + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" + sodipodi:docname="Lens_alignment.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview25533" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="mm" + showgrid="false" + showguides="false" + inkscape:zoom="3.3638608" + inkscape:cx="195.01401" + inkscape:cy="99.587949" + inkscape:window-width="1920" + inkscape:window-height="1017" + inkscape:window-x="1912" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="layer1"> + <sodipodi:guide + position="-4.4284666,24.678806" + orientation="0,-1" + id="guide27677" + inkscape:locked="false" /> + <sodipodi:guide + position="7.1294251,4.7278856" + orientation="0,-1" + id="guide27679" + inkscape:locked="false" /> + <inkscape:page + x="0" + y="0" + width="24.241247" + height="29.358212" + id="page29995" /> + <inkscape:page + x="34.241247" + y="0" + width="24.241247" + height="29.358212" + id="page29997" /> + </sodipodi:namedview> + <defs + id="defs25528" /> + <g + inkscape:label="Ebene 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-13.883398,-9.2805247)"> + <rect + style="fill:#000000;fill-rule:evenodd;stroke:none;stroke-width:0.138231" + id="rect27519" + width="0.5" + height="20" + x="20.512823" + y="13.910859" + rx="0" + ry="0" /> + <ellipse + style="fill:#0d47a1;fill-opacity:0.50196081;fill-rule:evenodd;stroke:none;stroke-width:0.264999" + id="path27681" + cx="25.955776" + cy="23.910854" + rx="1.4285719" + ry="10.000004" /> + <text + xml:space="preserve" + style="font-size:3.175px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583" + x="13.650854" + y="11.553254" + id="text27893"><tspan + sodipodi:role="line" + id="tspan27891" + style="font-size:3.175px;stroke-width:0.264583" + x="13.650854" + y="11.553254">Bildebene</tspan></text> + <text + xml:space="preserve" + style="font-size:3.175px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#0c46a1;fill-opacity:1;stroke-width:0.264583" + x="30.666185" + y="25.028616" + id="text28738"><tspan + sodipodi:role="line" + id="tspan28736" + style="font-size:3.175px;fill:#0c46a1;fill-opacity:1;stroke-width:0.264583" + x="30.666185" + y="25.028616">Linse</tspan></text> + <rect + style="fill:#1b5e20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264999" + id="rect28861" + width="0.67790216" + height="18.798628" + x="21.012821" + y="14.511545" /> + <path + style="fill:none;stroke:#f57f17;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 37.007018,15.02304 H 25.955776 l -4.265053,8.916437 4.265053,8.916436 h 11.051242" + id="path29589" + sodipodi:nodetypes="ccccc" /> + <text + xml:space="preserve" + style="font-size:3.175px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#1b5e20;fill-opacity:1;stroke-width:0.264583" + x="16.228828" + y="38.599979" + id="text29642"><tspan + sodipodi:role="line" + id="tspan29640" + style="font-size:3.175px;fill:#1b5e20;fill-opacity:1;stroke-width:0.264583" + x="16.228828" + y="38.599979">Sensor</tspan></text> + <rect + style="fill:#000000;fill-rule:evenodd;stroke:none;stroke-width:0.138231" + id="rect29782" + width="0.5" + height="20" + x="54.754066" + y="13.910859" + rx="0" + ry="0" /> + <ellipse + style="fill:#0d47a1;fill-opacity:0.50196081;fill-rule:evenodd;stroke:none;stroke-width:0.264999" + id="ellipse29784" + cx="59.693584" + cy="25.14134" + rx="1.4285719" + ry="10.000004" + transform="rotate(-1.1760584)" /> + <text + xml:space="preserve" + style="font-size:3.175px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583" + x="47.892097" + y="11.553254" + id="text29788"><tspan + sodipodi:role="line" + id="tspan29786" + style="font-size:3.175px;stroke-width:0.264583" + x="47.892097" + y="11.553254">Bildebene</tspan></text> + <text + xml:space="preserve" + style="font-size:3.175px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#0c46a1;fill-opacity:1;stroke-width:0.264583" + x="64.907433" + y="25.028616" + id="text29792"><tspan + sodipodi:role="line" + id="tspan29790" + style="font-size:3.175px;fill:#0c46a1;fill-opacity:1;stroke-width:0.264583" + x="64.907433" + y="25.028616">Linse</tspan></text> + <rect + style="fill:#1b5e20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264999" + id="rect29794" + width="0.67790216" + height="18.798628" + x="55.685192" + y="13.791871" + transform="rotate(0.74945537)" /> + <path + style="fill:none;stroke:#f58016;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.933333" + d="M 71.248266,15.02304 H 60.014568 l -4.082597,9.870125 4.448687,7.962748 h 10.867608" + id="path29796" + sodipodi:nodetypes="ccccc" /> + <text + xml:space="preserve" + style="font-size:3.175px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#1b5e20;fill-opacity:1;stroke-width:0.264583" + x="50.470074" + y="38.599979" + id="text29800"><tspan + sodipodi:role="line" + id="tspan29798" + style="font-size:3.175px;fill:#1b5e20;fill-opacity:1;stroke-width:0.264583" + x="50.470074" + y="38.599979">Sensor</tspan></text> + <path + style="fill:none;stroke:#0c46a1;stroke-width:0.125;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.50196081;stroke-dasharray:0.375,0.375;stroke-dashoffset:0" + d="m 59.971254,12.913167 0.451543,21.995374" + id="path33866" /> + <path + style="fill:none;stroke:#0c46a1;stroke-width:0.125;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.50196081;stroke-dasharray:0.375,0.375;stroke-dashoffset:0" + d="M 25.955776,12.91085 V 34.910858" + id="path33870" /> + </g> +</svg>