From 576a9ac30b231ed5059db6bc2d51b872d7d17f96 Mon Sep 17 00:00:00 2001
From: Jan Wille <jan.wille@stud.hs-hannover.de>
Date: Tue, 9 Aug 2022 21:45:23 +0200
Subject: [PATCH] kalibreirung: Node implentierung

---
 chap/kalibrierung.tex | 133 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 132 insertions(+), 1 deletion(-)

diff --git a/chap/kalibrierung.tex b/chap/kalibrierung.tex
index 238ad7f..47a1497 100644
--- a/chap/kalibrierung.tex
+++ b/chap/kalibrierung.tex
@@ -269,7 +269,138 @@
 
 				Mit dem verwendeten Datensatz ergibt sich ein Reprojektions-Fehler von $0,049$, was genau genug für diesen Anwendungsfall ist.
 
-		\subsection{Anwenden der Kalibrierung in einem \gls{ROS Nodelet}}
+
+		\subsection{Anwenden der Kalibrierung in einer 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
+			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.
+
+			\begin{figure}
+				\includegraphics[scale=.9]{svg/Topics_undistorter.pdf}
+				\caption{Beziehungen der entzerrer Node zu bestehenden Nodes}
+				\label{fig: topics graph undistoreter node}
+			\end{figure}
+
+
+			\subsubsection{Initialisieren der Node}
+
+				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.
+				\todo{Code hierzu?}
+
+				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 aus der Konfigurationsdatei gelesen. \autoref{code: intrinsic einlesen YAML} zeigt den Ablauf. Es ist sinnvoll,
+				dies bereits in der \lstinline{main()} Funktion durchzuführen, um die \gls{Callback} zu entlasten und dort Rechenzeit einzusparen.
+
+				\begin{lstlisting}[
+					float,
+					style=example,
+					caption=Einlesen der Kalibrierungsergebnisse aus einer YAML-Datei,
+					label=code: intrinsic einlesen YAML,
+					language=C++
+				]
+					// open YAML-file and get config
+					std::string configFilePath = "./tools/calibration/calibration.yaml";
+					YAML::Node full_config = YAML::LoadFile(configFilePath);
+					YAML::Node camera_config = full_config["cameras"]["default"];
+
+					// read distorion coeffitiones and convert to OpenCV vector
+					auto distortion_YAML = camera_config["intrinsic"]["distortion"] .as<std::vector<double>>();
+					cv::Mat distortion ( distortion_YAML );
+
+					// read camera matrix and convert to OpenCV matrix
+					auto cameraMatrix_YAML = camera_config["intrinsic"]["matrix"] .as<std::vector<std::vector<double>>>();
+					cv::Mat cameraMatrix = toMat( cameraMatrix_YAML );
+
+					// read image size
+					cv::Size imageSize(
+						full_config["images"]["size"]["width"].as<int>(),
+						full_config["images"]["size"]["height"].as<int>()
+					);
+				\end{lstlisting}
+
+				Mit diesen Werten können nun \emph{Mappings} erzeugt werden, welche die geometrische Beziehung zwischen einem Pixel im Originalbild und
+				einem 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.
+
+				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
+				$\alpha$. Für $\alpha=0$ ist die zurückgegebene Matrix so gewählt, dass das entzerrte Bild möglichst wenig unbekannte Pixel enthält. Das
+				bedeutet aber, dass einige Pixel des Originalbildes außerhalb des neuen Bildbereiches liegen und vernachlässigt werden. Mit $\alpha=1$
+				enthält das entzerrte Bild alle Pixel des Originalbildes, allerdings bleiben einige Pixel schwarz. Da die Funktion zusätzlichen eine
+				\gls{ROI} liefert, welches den Bildausschnitt ohne schwarze Pixel beschreibt, wird hier $\alpha=1$ verwendet. Die veröffentlichten Bilder
+				werden zwar auf die \gls{ROI} reduziert, aber die vorhanden Informationen werdenden grundsätzlich erhalten und bei Bedarf kann das
+				Programm einfach angepasst werden, um die vollständigen Bilder zu veröffentlichen.
+
+				\begin{lstlisting}[
+					float,
+					style=example,
+					caption=Bestimmen der Pixel-Mappings zu Entzerrung,
+					language=C++
+				]
+					// get scaled camera matrix
+					auto scaledCameraMatrix = cv::getOptimalNewCameraMatrix(cameraMatrix, distortion, imageSize, 1, imageSize, &ROI);
+
+					// calculate undistortion mappings
+					cv::initUndistortRectifyMap(cameraMatrix, distortion, cv::Mat(), scaledCameraMatrix, imageSize, CV_16SC2, rectifyMapX, rectifyMapY);
+				\end{lstlisting}
+
+
+			\subsubsection{Callback-Funktion zur Handhabung der Einzelbilder}
+
+				Die \gls{Callback} \lstinline{callback_undistort_image()} wurde während der Initialisierung an das \gls{Topic} \lstinline{/img/raw}
+				angehängt und wird nun für jedes dort veröffentlichte Bild aufgerufen. Der \autoref{code: undistort callback} zeigt eine vereinfachte
+				Version der Implementierung, ohne Umwandlung in ein Schwarzweißbild und ohne Laufzeitmessung.
+
+				Da das Bild als \gls{ROS} eigener Datentyp übergeben wird, muss es zuerst in ein mit \gls{OpenCV} kompatibles Format umgewandelt
+				werden. Die dazu notwendigen Funktionen sind im \gls{ROS}-Paket \lstinline{cv_bridge} zur Verfügung gestellt. Dessen Funktion
+				\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 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
+				Schwarz-Weiß Version erzeugt und diese als \lstinline{\img\gray} veröffentlicht.
+
+				\begin{lstlisting}[
+					float,
+					style=example,
+					caption=Vereinfachte Version der \gls{Callback} zur Durchführung der Entzerrung,
+					label=code: undistort callback,
+					language=C++
+				]
+					void callback_undistort_image(sensor_msgs::Image original) {
+						cv::Mat undistoredImage;
+
+						// convert from ROS msg-type to opencv matrix
+						cv_bridge::CvImagePtr imagePtr = cv_bridge::toCvCopy(original);
+
+						// apply the calculated maps to undistort the image
+						cv::remap(imagePtr->image, undistoredImage, rectifyMapX, rectifyMapY, cv::INTER_LINEAR);
+
+						// crop relevant section from image
+						undistoredImage = undistoredImage(ROI);
+
+						// publish images
+						cv_bridge::CvImage colorImage(std_msgs::Header(), "rgb8", undistoredImage);
+						pub_colorImage->publish(colorImage.toImageMsg());
+					}
+				\end{lstlisting}
+
+
+			\subsubsection{Performance Betrachtung}
+
+				\begin{figure}
+					\includegraphics[width=.6\textwidth, trim={0 0 12px 31px}, clip]{img/jtop_camera.png}
+					\caption{CPU Auslastung des JetBots mit laufender Kamera und entzerrer \gls{ROS Node}}
+					\label{fig: jtop cam+undist}
+				\end{figure}
+
+				Runtime: $\approx 6\,\ms$
+
 
 
 	\section{Extrinsische Kalibrierung} \label{sec: extrensic}
-- 
GitLab