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

intrinsische kalibrierung

parent 1ad1aaf1
No related branches found
No related tags found
No related merge requests found
\chapter{Kamera Kalibrierung} \label{chap: kalibrierung} \chapter{Kamera Kalibrierung} \label{chap: kalibrierung}
Damit der später beschriebene Fahrspurerkennung möglichst zuverlässig funktioniert und möglichst reproduzierbar ist, wird eine Kalibrierung
vorgenommen. Das Vergehen dazu und die Ergebnisse sind im folgenden Kapitel dokumentiert.
\section{Notwendigkeit einer intrinsische Kalibrierung} \label{sec: intrinsic} \section{Intrinsische Kalibrierung} \label{sec: intrinsic}
\subsection{Optische Verzerrung} 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 natürlich alle parallel verlaufen, im Bild aber gekrümmt aussehen.
\begin{figure}
\includegraphics[width=.4\textwidth]{img/unkalibriert.png}
\caption{Unkalibriertes Kamerabild mit tonnenförmiger Verehrung}
\label{fig: kamerabild unkalibriert}
\end{figure}
\subsection{Radiale Verzerrung}
Die erste mögliche Art der Verzerrung ist die radiale Verzerrung. Diese ist die auffälligste Art der Verzerrung und wird häufig auch
\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.
\begin{figure} \begin{figure}
\subfigure[Tonnenförmige Verzerrung]{\includegraphics[page=1,width=.3\textwidth]{svg/Lens_distorsion.pdf}}
\subfigure[Verzerrungsfreies Bild]{\includegraphics[page=2,width=.3\textwidth]{svg/Lens_distorsion.pdf}}
\subfigure[Kissenförmige Verzerrung]{\includegraphics[page=3,width=.3\textwidth]{svg/Lens_distorsion.pdf}} \subfigure[Kissenförmige Verzerrung]{\includegraphics[page=3,width=.3\textwidth]{svg/Lens_distorsion.pdf}}
\subfigure[Verzerrungsfreies Bild]{\includegraphics[page=2,width=.3\textwidth]{svg/Lens_distorsion.pdf}}
\subfigure[Tonnenförmige Verzerrung]{\includegraphics[page=1,width=.3\textwidth]{svg/Lens_distorsion.pdf}}
\caption{Darstellung der optischen Verzerrung (nach \cite{wiki:LinsenVerzerung})} \caption{Darstellung der optischen Verzerrung (nach \cite{wiki:LinsenVerzerung})}
\label{fig: optische verzerrung} \label{fig: optische verzerrung}
\end{figure} \end{figure}
Mathematisch lässt dich die Veränderung eines Punktes durch die Verzierung wie in \autoref{eq: radiale verzerrung} beschrieben berechnen.
Dabei beschreiben $x$ und $y$ die unverzerrten Pixelkoordinaten, $k_1$, $k_3$ und $k_3$ die Verzerrungskoeffizienten.
Theoretisch existieren noch weitere Koeffizienten, aber in der Praxis haben sich die ersten drei als ausreichend herausgestellt.
\cite{Hanning:highPrecisionCamCalibration}
\section{Durchführung der intrinsischen Kalibrierung} \begin{equation}\label{eq: radiale verzerrung}
\begin{split}
x_{distored} &= x( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6 ) \\
y_{distored} &= y( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6 ) \\
\end{split}
\end{equation}
\pagebreak
\subsection{Tangentiale Verzerrung}
Die tangentiale Verzerrung entsteht durch kleine Ausrichtungsfehler im Linsensystem. Dadruch liegt die Linse nicht perfekt in der
Bildebene und der Bildmittelpunk sowie die Bildausrichtung können leicht verschoben sein.
\begin{figure} \begin{figure}
\includegraphics[width=.4\textwidth]{img/unkalibriert.png} \missingfigure{Sensor-Linse alignment}
\caption{Unkalibriertes Kamerabild mit tonnenförmiger Verehrung} \caption{Probleme in der ausrichtung von Sensor und Linse (nach \cite{Matlab:CameraCalibration})}
\label{fig: kamerabild unkalibriert}
\end{figure} \end{figure}
Mathematisch wird diese Verzerrung durch den folgenden Zusammenhang beschrieben. \cite{Hanning:highPrecisionCamCalibration}
\begin{equation}
\begin{split}
x_{distored} &= x + \left[2p_1xy + p_2(r^2 + 2x^2)\right] \\
y_{distored} &= y + \left[p_1(r^2 + 2y^2) + 2p_2xy\right] \\
\end{split}
\end{equation}
\bigskip
Durch beide Verzerrungsarten zusammen werden also durch fünf Parameter beschrieben, die sogenannten Verzerrungskoeffizienten. Historisch
begründet wird dabei $k_3$ an das Ende geschrieben, da dieses Parameter früher kaum berücksichtigt wurde.
\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 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
Uhrsprung 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
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 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} \begin{figure}
\includegraphics[width=.4\textwidth]{img/kalibrieren_PATTER.png} \includegraphics[width=.4\textwidth]{img/kalibrieren_PATTER.png}
\caption{Von OpenCV erkanntes Kalibriermuster} \caption{Schachbrett Kalibriermuster mit markierten inneren Kreuzungen}
\label{fig: kalibriermuster} \label{fig: kalibriermuster}
\end{figure} \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} \begin{figure}
\includegraphics[width=\textwidth]{img/kalibrieren_schritte.png} \includegraphics[width=\textwidth]{img/kalibrieren_schritte.png}
\caption{Schritte der intrinsischen Kalibrierung} \caption{Schritte der intrinsischen Kalibrierung}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment