diff --git a/Bachelorarbeit.pdf b/Bachelorarbeit.pdf index 362f9352222ae9f8ddfc139676aa681d7e3f17c2..504e60f51727b6fbc9742d3c0db8dba7712a8174 100644 Binary files a/Bachelorarbeit.pdf and b/Bachelorarbeit.pdf differ diff --git a/chap/kalibrierung.tex b/chap/kalibrierung.tex index aaeb0fb5b1f2af394e53f808db23aca4aedbe73c..e2a0dba2d0b86330412f3c6c5050b3d3f760ffcb 100644 --- a/chap/kalibrierung.tex +++ b/chap/kalibrierung.tex @@ -91,165 +91,153 @@ Zur Durchführung der Kalibrierung wir ein Python-Script erstellt, um die 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}. - \todo{Dependencies?} - - % Das Script benötigt einige Bibliotheken, welche auf dem System vorhanden sein müssen. Um sie zu installieren, kann der Befehl - % \lstinline{pip3 install numpy opencv-python pyyaml} verwendet werden. Sind alle Voraussetzungen erfüllt, können die Bibliotheken in das Script - % importiert werden. - - % \begin{lstlisting}[ - % float, - % style=example, - % caption=Für das Kaliebrierscript benötigte Biblioteken, - % language=Python - % ] - % # * ----- Package imports: ----- * - % import glob - % import pathlib - - % import cv2 as cv - % import numpy as np - % import yaml - % \end{lstlisting} - - 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 Anzahle 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 - Kalibriermuster in \autoref{fig: kalibriermuster} grün markiert. - \begin{figure} - \includegraphics[width=.4\textwidth]{img/kalibrieren_PATTER.png} - \caption{Schachbrett Kalibriermuster mit markierten inneren Kreuzungen} - \label{fig: kalibriermuster} - \end{figure} + Außerdem wird eine \gls{ROS Nodelet} erstellt, welches die Kalibrierung auf den Video-Stream anwendet und korrigierte Bilder publischt. - 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}[ - float, - style=example, - caption=Definiteion der Größe des Kalibriermuster, - language=Python - ] - # define the grid pattern to look for - PATTERN = (7,7) - \end{lstlisting} - - Entsprechend der Anleitung \cite{OpenCV:CameraCalibration} werden benötigte Variablen initialisiert (siehe \autoref{code: kali var init}). - - \begin{lstlisting}[ - float, - style=example, - caption=Initialisierung von Variablen für die Kalibreirung, - language=Python, - label=code: kali var init - ] - # termination criteria - criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001) - # prepare object points, like (0,0,0), (1,0,0),...,(6,5,0) - objp = np.zeros((PATTERN[0]*PATTERN[1],3), np.float32) - objp[:,:2] = np.mgrid[0:PATTERN[0],0:PATTERN[1]].T.reshape(-1,2) - # Arrays to store object points and image points from all the images. - objpoints = [] # 3d point in real world space - imgpoints = [] # 2d points in image plane. - \end{lstlisting} - - Nun werden alle im aktuellen Ordner befindlichen Bilder eingelesen und in einer Liste abgespeichert. Jedes Listenelement wird eingelesen und - in ein Schwarzweißbild umgewandelt. Dieses wird dann an die \gls{OpenCV} Funktion \lstinline{findChessboardCorners()} übergeben, welche die - Kreuzungspunkten findet und zurückgibt. - - \begin{lstlisting}[ - float, - style=example, - caption=Finden un Verarbeiten der Kalibrierbilder, - language=Python - ] - # get all images in current directory - folder = pathlib.Path(__file__).parent.resolve() - images = glob.glob(f'{folder}/*.png') - - # loop over all images: - for fname in images: - img = cv.imread(fname) - gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) - - # Find the chess board corners - ret, corners = cv.findChessboardCorners(gray, PATTERN, None, flags=cv.CALIB_CB_ADAPTIVE_THRESH) - \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 - ü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 - Liste der gefundenen Bildpunkte angehängt. - - \begin{lstlisting}[ - float, - style=example, - caption=Abspeichern der Gefundenen Bildpunkte, - language=Python - ] - # If found, add object points, image points - if ret == True: - objpoints.append(objp) - corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria) - imgpoints.append(corners) - \end{lstlisting} - - Jetzt kann die eigentliche Kalibrierung mittels der \gls{OpenCV} Funktion \lstinline{calibrateCamera()} durchgeführt werden. Diese nimmt die - zuvor erstellten Listen von Objektkoordinaten und Bildpunkten und löst damit die in \autoref{sec: intrinsic} beschriebenen Gleichungen. Als - Ergebnis liefert sie die Kameramatrix $K$ und die Verzerrungskoeffizienten $D_{coeff}$ zurück. \cite{OpenCV:CameraCalibration} - - \begin{lstlisting}[ - float, - style=example, - caption=Ermitteln der Kalibrierwerte mittels OpenCV, - language=Python - ] - # get calibration parameters: - ret, K, D_coeff, _, _ = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) - \end{lstlisting} - - Der gesamte Code wird nun auf einen Datensatz von Bilder angewandt, um die Ergebnisse für den vorliegenden Roboter zu erhalten. Der Datensatz - ist auf dem GitLab Server unter der URL \url{https://lab.it.hs-hannover.de/p9r-rxm-u1/videodrive_ws/-/wikis/uploads/6c853b3f41964eccd6671954a07ad5ed/intrinsicCalibration_down4.zip} - abgelegt. Damit ergeben sich die folgenden Kalibrierungsergebnisse. - - \begin{align*} - k_1 &= -0,42049309612684654 \\ - k_2 &= 0,3811654512587829 \\ - p_1 &= -0,0018273837466050299 \\ - p_2 &= -0,006355252159438178 \\ - k_3 &= -0,26963105010742416 \\ - K &= - \begin{pmatrix} - 384,65 & 0 & 243,413 \\ - 0 & 384,31 & 139,017\\ - 0 & 0 & 1 \\ - \end{pmatrix} - \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 - 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 - \emph{Region of interes}, also den Bildbereich der nur relevante (nicht leere) Pixel enthält. - - Mit dieser zusätzlichen Matrix kann nun die \gls{OpenCV} Funktion \lstinline{undistort()} auf das Bild angewandt werden. Diese Produziert das - entzerrte Bild mit leeren Pixeln in den Bereichen, wo keine Informationen im Originalbild vorlagen. Um diese leeren Pixel zu entfernen wird - das Bild auf die \gls{ROI} reduziert. - - \medskip - In \autoref{fig: intrinsik schritte} ist die Entzerrung des Beispielbildes mit dem Zwischenschritt mit Leerpixeln gezeigt. - \begin{figure} - \includegraphics[width=\textwidth]{img/kalibrieren_schritte.png} - \caption{Schritte der intrinsischen Kalibrierung} - \label{fig: intrinsik schritte} - \end{figure} + \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 Anzahle 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 + Kalibriermuster in \autoref{fig: kalibriermuster} grün markiert. + + \begin{figure} + \includegraphics[width=.4\textwidth]{img/kalibrieren_PATTER.png} + \caption{Schachbrett Kalibriermuster mit markierten inneren Kreuzungen} + \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 + Kalibriermustergröße von $7 \!\times\! 7$ wird im Code als Konstante definiert: + + \begin{lstlisting}[ + float, + style=example, + caption=Definiteion der Größe des Kalibriermuster, + language=Python + ] + # define the grid pattern to look for + PATTERN = (7,7) + \end{lstlisting} + + Entsprechend der Anleitung \cite{OpenCV:CameraCalibration} werden benötigte Variablen initialisiert (siehe \autoref{code: kali var init}). + + \begin{lstlisting}[ + float, + style=example, + caption=Initialisierung von Variablen für die Kalibreirung, + language=Python, + label=code: kali var init + ] + # termination criteria + criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001) + # prepare object points, like (0,0,0), (1,0,0),...,(6,5,0) + objp = np.zeros((PATTERN[0]*PATTERN[1],3), np.float32) + objp[:,:2] = np.mgrid[0:PATTERN[0],0:PATTERN[1]].T.reshape(-1,2) + # Arrays to store object points and image points from all the images. + objpoints = [] # 3d point in real world space + imgpoints = [] # 2d points in image plane. + \end{lstlisting} + + Nun werden alle im aktuellen Ordner befindlichen Bilder eingelesen und in einer Liste abgespeichert. Jedes Listenelement wird eingelesen und + in ein Schwarzweißbild umgewandelt. Dieses wird dann an die \gls{OpenCV} Funktion \lstinline{findChessboardCorners()} übergeben, welche die + Kreuzungspunkten findet und zurückgibt. + + \begin{lstlisting}[ + float, + style=example, + caption=Finden un Verarbeiten der Kalibrierbilder, + language=Python + ] + # get all images in current directory + folder = pathlib.Path(__file__).parent.resolve() + images = glob.glob(f'{folder}/*.png') + + # loop over all images: + for fname in images: + img = cv.imread(fname) + gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) + + # Find the chess board corners + ret, corners = cv.findChessboardCorners(gray, PATTERN, None, flags=cv.CALIB_CB_ADAPTIVE_THRESH) + \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 + ü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 + Liste der gefundenen Bildpunkte angehängt. + + \begin{lstlisting}[ + float, + style=example, + caption=Abspeichern der Gefundenen Bildpunkte, + language=Python + ] + # If found, add object points, image points + if ret == True: + objpoints.append(objp) + corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria) + imgpoints.append(corners) + \end{lstlisting} + + Jetzt kann die eigentliche Kalibrierung mittels der \gls{OpenCV} Funktion \lstinline{calibrateCamera()} durchgeführt werden. Diese nimmt die + zuvor erstellten Listen von Objektkoordinaten und Bildpunkten und löst damit die in \autoref{sec: intrinsic} beschriebenen Gleichungen. Als + Ergebnis liefert sie die Kameramatrix $K$ und die Verzerrungskoeffizienten $D_{coeff}$ zurück. \cite{OpenCV:CameraCalibration} + + \begin{lstlisting}[ + float, + style=example, + caption=Ermitteln der Kalibrierwerte mittels OpenCV, + language=Python + ] + # get calibration parameters: + ret, K, D_coeff, _, _ = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) + \end{lstlisting} + + Der gesamte Code wird nun auf einen Datensatz von Bilder angewandt, um die Ergebnisse für den vorliegenden Roboter zu erhalten. Der Datensatz + ist auf dem GitLab Server unter der URL \url{https://lab.it.hs-hannover.de/p9r-rxm-u1/videodrive_ws/-/wikis/uploads/6c853b3f41964eccd6671954a07ad5ed/intrinsicCalibration_down4.zip} + abgelegt. Damit ergeben sich die folgenden Kalibrierungsergebnisse. + + \begin{align*} + k_1 &= -0,42049309612684654 \\ + k_2 &= 0,3811654512587829 \\ + p_1 &= -0,0018273837466050299 \\ + p_2 &= -0,006355252159438178 \\ + k_3 &= -0,26963105010742416 \\ + K &= + \begin{pmatrix} + 384,65 & 0 & 243,413 \\ + 0 & 384,31 & 139,017\\ + 0 & 0 & 1 \\ + \end{pmatrix} + \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 + 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 + \emph{Region of interes}, also den Bildbereich der nur relevante (nicht leere) Pixel enthält. + + Mit dieser zusätzlichen Matrix kann nun die \gls{OpenCV} Funktion \lstinline{undistort()} auf das Bild angewandt werden. Diese Produziert das + entzerrte Bild mit leeren Pixeln in den Bereichen, wo keine Informationen im Originalbild vorlagen. Um diese leeren Pixel zu entfernen wird + das Bild auf die \gls{ROI} reduziert. + + \medskip + In \autoref{fig: intrinsik schritte} ist die Entzerrung des Beispielbildes mit dem Zwischenschritt mit Leerpixeln gezeigt. + + \begin{figure} + \includegraphics[width=\textwidth]{img/kalibrieren_schritte.png} + \caption{Schritte der intrinsischen Kalibrierung} + \label{fig: intrinsik schritte} + \end{figure} + + + \subsection{Anwenden der Kalibrierung in einem \gls{ROS Nodelet}} \section{Extrinsische Kalibrierung} \label{sec: extrensic}