diff --git a/.vscode/ltex.dictionary.de-DE.txt b/.vscode/ltex.dictionary.de-DE.txt index 721102105a62782e58304259c27e29c6a98c4504..d9df59545ad706c098a9e325c76d241c93339856 100644 --- a/.vscode/ltex.dictionary.de-DE.txt +++ b/.vscode/ltex.dictionary.de-DE.txt @@ -51,3 +51,8 @@ if For-Schleife Debuggmöglichkeiten Hough +Klassifizung +Gradientenwinkel +Klassifizierungsschritt +fehlklassifizierte +Headerdatei diff --git a/.vscode/ltex.hiddenFalsePositives.de-DE.txt b/.vscode/ltex.hiddenFalsePositives.de-DE.txt index e26a6e952aada689cd032359150e38f645ccd86a..4b7fb4d6e668eb1bfd40d681fda3cd358892787e 100644 --- a/.vscode/ltex.hiddenFalsePositives.de-DE.txt +++ b/.vscode/ltex.hiddenFalsePositives.de-DE.txt @@ -22,3 +22,6 @@ {"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QJede \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q kann eigene Informationen als sogenannte Dummies veröffentlichen und andere, parallel laufende Dummies können diese abonnieren.\\E$"} {"rule":"DOPPELTES_AUSRUFEZEICHEN","sentence":"^\\QROS's ??\\E$"} {"rule":"GERMAN_SPELLER_RULE","sentence":"^\\QDieses kann unter dem \\E(?:Dummy|Ina|Jimmy-)[0-9]+\\Q /img/temp abgerufen und live angeschaut werden.\\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":"^\\QDer Unterschied liegt in der Funktion edgeDetectionClassification().\\E$"} diff --git a/Bachelorarbeit.pdf b/Bachelorarbeit.pdf index 402464fbfce5b54eba19f02f9e592dc4aca61577..723a10e9b51cac91bb82ace374e64d8c34c38e2c 100644 Binary files a/Bachelorarbeit.pdf and b/Bachelorarbeit.pdf differ diff --git a/chap/ausblick.tex b/chap/ausblick.tex index ba3eca63f4626eea8c22750547d26a2c381f598f..55c7536c8bb64ae75605b1158a6663c58ae37ae7 100644 --- a/chap/ausblick.tex +++ b/chap/ausblick.tex @@ -4,6 +4,7 @@ des Programmes noch Weiterentwicklungsmöglichkeiten. Dadruch könnte die Erfassungsgenauigkeit insbesondere in schwierigen Situationen erhöt und weite er Informationen gewonnen werden. + \subsubsection{Mehr Orientierungsklassen} Derzeit wird bei der Klassifizierung der Kantenpixel lediglich in vier unterschiedliche Klassen eingeteilt. Das kann insbesondere bei Kurven @@ -13,6 +14,7 @@ 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 Teileweise durch den vorherigen Punkt mit abgedeckt, bedarf aber trotzdem einer weiteren Erklärung. Derzeit @@ -24,8 +26,6 @@ Genauigkeit bei der Klassifizierung wäre ebenfalls denkbar. - - \subsubsection{Datenübergabe an weiter 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 @@ -36,4 +36,8 @@ Diese müsste bei Bedarf eingerichtet werden. - \todo[inline]{noch was?} + \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. diff --git a/chap/fazit.tex b/chap/fazit.tex index 4179e97ceaa469f9da8be8145dca4b807c099e84..d002b5468a63945e81ab1fba4d05eedf5c6daa3a 100644 --- a/chap/fazit.tex +++ b/chap/fazit.tex @@ -21,3 +21,6 @@ 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. + + 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. diff --git a/chap/implementation.tex b/chap/implementation.tex index 9e25ab0ac8ac6e6f743889f2d4ab6608e5c30e63..ba5765bc9fdf02093c37ed89c8ec06ca7186610e 100644 --- a/chap/implementation.tex +++ b/chap/implementation.tex @@ -397,6 +397,8 @@ bereits auf einem leistungsfähigen Entwickler-PC Laufzeiten von $>0,25\,\s$ hat, wird diese im \gls{C++} implementiert. Dies wird die Performance deutlich verbessern. + \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 die \gls{ROS Node} wir mit dem Namen \lstinline{lane_marker_detection} inizialisiert 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, @@ -678,6 +680,166 @@ \end{table} - % \section{Optimierung durch eigene Implementierung des Canny-Edge-Detectors} + \section{Optimierung\todo{oder "Versuchte 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 + 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. + + + \subsection{Anpassung des Quellcodes} + + Da ohne \gls{OpenCV} das Bild nicht mehr in eine praktische Matrixform umgewandelt wird, muss ein eigener Datentyp definiert werden. + Dieser speichert die Daten im selben Format wie der verwendete \gls{ROS} Datentyp, sodass keine Umwandlung notwendig ist. Der Datentyp + 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. + + 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. + + \begin{lstlisting}[ + float, + style=example, + caption={Eigener Datentyp zum Ablegen von Bildern}, + label=code: image class, + language=C++ + ] + class Image { + public: + std::vector<uint8_t> data; + int width; int height; + + Image(int width, int height): data(width*height, 0), width(width), height(height) {}; + Image(sensor_msgs::Image img): data(img.data), width(img.width), height(img.height) {}; + + uint8_t& operator[](pnt const& p) { return data[p.y*width + p.x]; } + const uint8_t& operator[](pnt const& p) const { return data[p.y*width + p.x]; } + }; + \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}). + + Zusätzlich wird außerdem die Klassifizierung durchgeführt. Da im ersten Schritt die Sobel-Gradienten ohnehin berechnet werden, können hier + ohne viel Zusatzaufwand auch die Gradientenwinkel mit bestimmt werden. Der \autoref{code: own canny 1} zeigt diesen Schritt. - % \subsection{Performance Betrachtung} + \begin{lstlisting}[ + float, + style=example, + caption={Bestimmung der Sobel-Gradienten im \gls{canny}}, + label=code: own canny 1, + language=C++ + ] + // Compute gradient magnitude and orientation + for (int u=1; u < image.height-1; u++) { + for (int v=1; v < image.width-1; v++) { + int dx=0, dy=0; + for (int y=0; y<3; y++){ + for (int x=0; x<3; x++){ + dx += SOBEL_X[y*3+x] * image[pnt(v+y-1,u+x-1)]; + dy += SOBEL_Y[y*3+x] * image[pnt(v+y-1,u+x-1)]; + } + } + gards[pnt(v,u)] = static_cast<int>(sqrt(dx*dx+dy*dy)); + agls[pnt(v,u)] = static_cast<float>(atan2(dy,dx) / 3.1415*180.0); + } + } + \end{lstlisting} + + Im zweiten Schritt werden nun zusätzlich zur Unterdrückung von Pixeln, welche kein lokales Maximum sind, auch die Klassen bestimmt und + abgespeichert. Da dieser Code sehr viele verschachtelte und gedoppelte \lstinline{if}-Abfragen aufweist, wird er in + \autoref{code: own canny 2} vereinfach gezeigt. + + + \begin{lstlisting}[ + float, + style=example, + caption={Klassifizierung und Unterdrückung von Nicht-Lokalen-Maxima}, + label=code: own canny 2, + language=C++ + ] + // Non-maximum suppression and classification + for (int u=1; u < image.height-1; u++) { + for (int v=1; v < image.width-1; v++) { + int grad = gradients[pnt(v,u)]; + int arc = angles[pnt(v,u)]; + + if (arc < 0) + uint8_t negative = 0x10; + arc = fabsf(arc); + + if ( /* is in a specific angle range */ ) { + canny_edges[pnt(v,u)] = negative|/* CLASSIFICATION */; + if ( /* is not local maximum */) + gradients[pnt(v,u)] = 0; + } + } + } + \end{lstlisting} + + 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 + repräsentiert. + + \begin{lstlisting}[ + float, + style=example, + caption={Grenzwertbildung und Ergebnissrückgabe}, + label=code: own canny 3, + language=C++ + ] + // thresholding + uint8_t T1 = 40; + for (int u=1; u < image.height-1; u++) + for (int v=1; v < image.width-1; v++) { + pnt p(v,u); + canny_edges[p] = gradients[p] < T1 ? 0 : canny_edges[p]; + } + return canny_edges; + \end{lstlisting} + + + \subsection{Performance Betrachtung} + + Die wichtigste Frage ist nun, wie sich diese Implementierung im Gegensatz zum Ansatz mit \gls{OpenCV} verhält. Dazu wurden wieder die + CPU-Auslastung mittels \lstinline{jtop} erfasst und die Durchlaufzeit des Algorithmus für jedes Bild gemessen. + + \begin{figure} + \includegraphics[width=.6\textwidth, trim={0 0 12px 31px}, clip]{img/jtop_cameraUndistortDetection_own.png} + \caption{CPU Auslastung des JetBots mit laufender Kamera, Entzerrung und Markierungserkennung mit eigener Implementierung} + \label{fig: jtop markings own} + \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. + + \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. + + \begin{table} + \caption{Gemessene Laufzeit bei 10 Durchläufen der \gls{Callback}} + \begin{tabular}{r|S} + Durchlauf Nr. & \multicolumn{1}{c}{gemessene Laufzeit} \\\hline + 1 & 22,266302 \,\ms \\ + 2 & 22,063851 \,\ms \\ + 3 & 22,126713 \,\ms \\ + 4 & 22,595543 \,\ms \\ + 5 & 23,369521 \,\ms \\ + 6 & 22,278288 \,\ms \\ + 7 & 24,207147 \,\ms \\ + 8 & 22,546738 \,\ms \\ + 9 & 22,096768 \,\ms \\ + 10 & 22,201099 \,\ms \\ + \end{tabular} + \end{table} diff --git a/img/jtop_cameraUndistortDetection_own.png b/img/jtop_cameraUndistortDetection_own.png new file mode 100644 index 0000000000000000000000000000000000000000..5c4274c5366c803a8d58a9bf42e3c4c8b5ae32bc Binary files /dev/null and b/img/jtop_cameraUndistortDetection_own.png differ