\documentclass[11pt,a4paper]{article}

\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[ngerman]{babel}
\usepackage[margin=2.4cm]{geometry}
\usepackage{amsmath,amssymb,bm,mathtools}
\usepackage{booktabs}
\usepackage{xcolor}
\usepackage{listings}
\usepackage{graphicx}
\usepackage{enumitem}
\usepackage[hidelinks]{hyperref}

\definecolor{codebg}{rgb}{0.96,0.96,0.97}
\definecolor{kw}{rgb}{0.2,0.3,0.7}
\lstset{
  basicstyle=\ttfamily\small,
  backgroundcolor=\color{codebg},
  frame=single, rulecolor=\color{gray!40},
  breaklines=true, columns=fullflexible,
  keepspaces=true, showstringspaces=false,
  numberstyle=\tiny, xleftmargin=4pt, xrightmargin=4pt, aboveskip=6pt, belowskip=6pt,
}

\newcommand{\R}{\mathbb{R}}
\newcommand{\vx}{\mathbf{x}}
\newcommand{\vX}{\mathbf{X}}
\newcommand{\vu}{\mathbf{u}}
\newcommand{\vn}{\mathbf{n}}
\newcommand{\vp}{\mathbf{p}}
\newcommand{\vt}{\mathbf{t}}
\newcommand{\vq}{\mathbf{q}}
\newcommand{\va}{\hat{\mathbf{a}}}
\newcommand{\vY}{\mathbf{Y}}
\newcommand{\vv}{\mathbf{v}}
\newcommand{\vr}{\mathbf{r}}
\newcommand{\vd}{\mathbf{d}}
\newcommand{\norm}[1]{\left\lVert #1 \right\rVert}

\title{\textbf{Roboter-Pose-Erkennung aus Mehrkamera-ArUco-Aufnahmen}\\[2pt]
\large Pipeline, mathematische Grundlagen und Konfiguration}
\author{appRobotRendering --- technische Dokumentation}
\date{\today}

\begin{document}
\maketitle

\begin{abstract}
\noindent
Diese Dokumentation beschreibt eine Bildverarbeitungs-Pipeline, die aus
synchronen Fotos eines Roboterarms von mehreren kalibrierten Kameras die
Gelenkstellung (sieben Achsen $x,y,z,a,b,c,e$) rekonstruiert. Auf dem Roboter
und einer Referenzplatte (\emph{Board}) sind ArUco-Marker angebracht. Aus den
\emph{vier Eckpunkten} jedes Markers wird per Mehrsicht-Triangulation eine volle
6-DOF-Markerpose (Position \emph{und} Flächennormale) bestimmt; aus diesen
Beobachtungen werden über das Vorwärtskinematik-Modell die Gelenkwinkel
geschätzt. Es werden vier austauschbare Schätzverfahren beschrieben und an
simulierten Szenen mit bekannter Grundwahrheit validiert (mittlerer
Winkelfehler $0{,}25^\circ$, Positionsfehler $0{,}10$\,mm).
\end{abstract}

\tableofcontents
\bigskip

% =====================================================================
\section{Einführung und Problemstellung}
% =====================================================================
Gegeben sind $C$ kalibrierte Kameras, die denselben Roboter gleichzeitig
fotografieren. Der Roboter trägt eine kinematische Kette starrer Glieder
(\emph{Links}); auf den Gliedern und auf der fixen Bodenplatte sitzen ArUco-Marker
bekannter Größe. Das \emph{Board} ist die Wurzel der Kette und definiert das
Weltkoordinatensystem $W$. Gesucht ist der Gelenkvektor
\[
  \vq = (x, y, z, a, b, c, e) \in \R^7,
\]
mit linearen Achsen $x,e$ (in mm) und rotatorischen Achsen $y,z,a,b,c$ (in Grad).

Die zentrale Idee ist, dass ein ArUco-Marker \emph{kein Punkt} ist, sondern ein
ebenes Quadrat bekannter Kantenlänge mit vier detektierten Eckpunkten. Werden
diese vier Ecken über mehrere Kameras trianguliert, liefert jeder einzelne
Marker neben seiner Position auch eine \emph{gemessene} Orientierung. Dadurch
genügt bereits ein einziger sichtbarer Marker, um den Winkel des Gliedes zu
bestimmen, auf dem er sitzt.

% =====================================================================
\section{Notation und Koordinatensysteme}
% =====================================================================
Punkte im Weltsystem $W$ werden mit $\vX\in\R^3$ bezeichnet (Einheit mm).
Eine Kamera $c$ besitzt die Intrinsikmatrix $K_c\in\R^{3\times3}$, die
Verzeichnungskoeffizienten $D_c$ sowie die Extrinsik $(R_c, \vt_c)$ in
\emph{world-to-camera}-Konvention,
\[
  \vX^{\mathrm{cam}} = R_c\,\vX + \vt_c , \qquad R_c\in SO(3).
\]
Das Kamerazentrum im Weltsystem ist $\mathbf{C}_c = -R_c^{\top}\vt_c$. Die
normierte (intrinsikfreie) Projektion eines Weltpunkts lautet
\begin{equation}
  \tilde{\pi}(\vX;R_c,\vt_c)=
  \frac{1}{[\vX^{\mathrm{cam}}]_3}\begin{pmatrix}[\vX^{\mathrm{cam}}]_1\\[2pt][\vX^{\mathrm{cam}}]_2\end{pmatrix}\in\R^2 .
  \label{eq:proj}
\end{equation}
Eine beobachtete Pixelkoordinate $\vu$ wird mittels $\tilde{\vu}=\mathrm{undistort}(\vu;K_c,D_c)$
in dieselbe normierte Ebene überführt. Rotationen werden bei der Optimierung über
den Rodrigues-Vektor $\bm{\omega}\in\R^3$ mit $R=\exp([\bm{\omega}]_\times)$
parametrisiert.

% =====================================================================
\section{Pipeline-Architektur}
% =====================================================================
Die Verarbeitung erfolgt in vier Stufen (Skript \texttt{run/run\_pipeline.bat}):

\begin{center}
\begin{tabular}{@{}lll@{}}
\toprule
Stufe & Skript & Ausgabe \\
\midrule
1 & \texttt{1\_detect\_aruco\_observations.py} & \texttt{*\_aruco\_detection.json} (Ecken) \\
2 & \texttt{2\_estimate\_camera\_from\_observations.py} & \texttt{*\_camera\_pose.json} \\
3 & \texttt{3\_multiview\_bundle\_adjustment\_v4.py} & \texttt{aruco\_positions\_initial.json} \\
3b & \texttt{3b\_corner\_marker\_poses.py} & \texttt{aruco\_marker\_poses.json} \\
4 & \texttt{pose\_estimation.py} & \texttt{robot\_state.json} \\
\bottomrule
\end{tabular}
\end{center}

Stufe 3 (reine Center-Triangulation) wird für Visualisierung und Vergleich
beibehalten; die eigentliche Posenschätzung nutzt die in Stufe 3b erzeugten
Eckposen.

% =====================================================================
\section{Stufe 1: ArUco-Detektion}
% =====================================================================
Pro Bild liefert der OpenCV-ArUco-Detektor für jeden erkannten Marker $m$ die
vier Eckpunkte $\vu_{m,0},\dots,\vu_{m,3}$ im Uhrzeigersinn sowie deren
Mittelpunkt. Zusätzlich werden Qualitätsmaße (Fläche, Schärfe als
Laplace-Varianz, Kontrast, Randabstand, Kantenverhältnis) berechnet und eine
heuristische Konfidenz abgeleitet. Die Eckpunkte $\vu_{m,k}$ sind die
\emph{primären} Messungen; der Mittelpunkt ist nur ihr Mittelwert.

% =====================================================================
\section{Stufe 2: Kamera-Pose-Schätzung}
% =====================================================================
Die Board-Marker haben bekannte Weltpositionen $\vX_i$ (aus \texttt{robot.json}).
Aus ihren beobachteten Mittelpunkten wird die Pose jeder Kamera geschätzt.
Ein robuster Anfangswert ergibt sich aus \texttt{solvePnP} (IPPE-Square) je
Marker, gefolgt von einem starren Ausgleich der per-Marker-Kamerazentren.
Verfeinert wird mit Levenberg--Marquardt auf den normierten
Reprojektionsresiduen:
\begin{equation}
  \min_{\bm{\theta}}\ \sum_{i} \norm{\,\tilde{\vu}_i - \tilde{\pi}\!\big(\vX_i;\,R(\bm{\omega}),\vt\big)}^2,
  \qquad \bm{\theta}=(\bm{\omega},\vt)\in\R^6 .
  \label{eq:campose}
\end{equation}
Aus der Jacobi-Matrix am Optimum wird eine $6\times6$-Kovarianz und daraus die
Unsicherheit von Kamerazentrum und Orientierung propagiert. Bemerkung: Da die
Board-Marker nahezu koplanar sind ($z\approx0$), ist die Tiefe schlecht
konditioniert --- eine korrekte Intrinsik ist deshalb essenziell (siehe
Abschnitt~\ref{sec:limits}).

% =====================================================================
\section{Stufe 3b: Eck-Triangulation und Normalenschätzung}
% =====================================================================
\subsection{Mehrsicht-Triangulation (DLT)}
Sei $V_{m,k}\subseteq\{1,\dots,C\}$ die Menge der Kameras, die Ecke $k$ von
Marker $m$ sehen. Mit der normierten Projektionsmatrix $P_c=[\,R_c \mid \vt_c\,]$
(Zeilen $\vp_{c,1}^\top,\vp_{c,2}^\top,\vp_{c,3}^\top$) und der entzerrten
Beobachtung $\tilde{\vu}=(x,y)$ liefert jede Kamera zwei lineare Gleichungen in
den homogenen Punktkoordinaten $\vX_h$:
\begin{equation}
  x\,\vp_{c,3}^\top \vX_h - \vp_{c,1}^\top \vX_h = 0,\qquad
  y\,\vp_{c,3}^\top \vX_h - \vp_{c,2}^\top \vX_h = 0 .
\end{equation}
Das Stapeln über alle $c\in V_{m,k}$ ergibt $A\,\vX_h=\mathbf 0$; die Lösung ist
der rechte Singulärvektor zum kleinsten Singulärwert von $A$, gefolgt von der
Dehomogenisierung $\vX = \vX_h^{1:3}/\vX_h^{4}$. Dies wird für alle vier Ecken
ausgeführt und liefert $\vY_0,\dots,\vY_3$.

\subsection{Position und Normale}
Die Markerposition ist der Schwerpunkt $\bar{\vY}=\tfrac14\sum_k\vY_k$. Die
Normale ist der Normalenvektor der Ausgleichsebene durch die vier Ecken: mit
$M=[\vY_0-\bar{\vY};\dots;\vY_3-\bar{\vY}]\in\R^{4\times3}$ und der
Singulärwertzerlegung $M=U\Sigma V^\top$ ist
\begin{equation}
  \vn = \pm\,\vv_3 \quad(\text{kleinster Singulärvektor}),
\end{equation}
wobei das Vorzeichen über die ArUco-Eckreihenfolge so gewählt wird, dass $\vn$
zur Kamera weist (Konvention wie in der Grundwahrheit):
$\vn\leftarrow-\vn$, falls $\vn\cdot\big((\vY_1-\vY_0)\times(\vY_2-\vY_0)\big)>0$.
Die Validierung (\texttt{benchmark/stage0\_corner\_normals.py}) ergibt einen
Median-Normalenfehler von $1{,}2^\circ$ ($p_{90}=3{,}0^\circ$); die
Aufschlüsselung je Glied (Tabelle~\ref{tab:normals}) zeigt, dass selbst die
kleinen, weit entfernten Fingermarker eine Orientierung von rund $1^\circ$
liefern --- die Grundlage dafür, dass ein einzelner Marker seinen Gelenkwinkel
bestimmen kann.

\begin{table}[h]
\centering
\begin{tabular}{@{}lccc@{}}
\toprule
Glied & \# Marker & Normalenfehler Mittel [$^\circ$] & Max [$^\circ$] \\
\midrule
Arm1 & 3 & 0{,}64 & 0{,}94 \\
Arm2 & 4 & 0{,}78 & 1{,}04 \\
Ellbow & 4 & 0{,}96 & 1{,}99 \\
FingerA & 2 & 0{,}96 & 1{,}14 \\
FingerB & 2 & 1{,}43 & 1{,}44 \\
Board & 41 & 1{,}79 & 7{,}07 \\
\bottomrule
\end{tabular}
\caption{Genauigkeit der aus Ecken abgeleiteten Normale je Glied (Scene8).}
\label{tab:normals}
\end{table}

% =====================================================================
\section{Vorwärtskinematik}
% =====================================================================
Die Links werden topologisch (Wurzel zuerst) sortiert. Für Link $l$ mit Eltern-%
link $p$ gilt
\begin{equation}
  T_l \;=\; T_p \cdot \underbrace{\mathrm{Tr}(\mathbf{m}_l)\,R_{xyz}(\bm{\rho}_l)}_{\text{Mount}}
            \cdot \underbrace{\mathrm{Tr}(\mathbf{o}_l)\,R_{xyz}(\bm{\sigma}_l)}_{\text{Gelenkursprung}}
            \cdot Q_l(q_l),
\end{equation}
mit der Gelenkbewegung
\begin{equation}
  Q_l(q_l)=
  \begin{cases}
    R(\hat{\mathbf{s}}_l,\,q_l) & \text{revolut (Achse }\hat{\mathbf{s}}_l,\text{ Winkel }q_l),\\
    \mathrm{Tr}(\hat{\mathbf{s}}_l\, q_l) & \text{linear (Verschiebung }q_l),\\
    I & \text{fest.}
  \end{cases}
\end{equation}
Ein Marker mit lokaler Position $\vp_m^{\text{lok}}$ und lokaler Normale
$\vn_m^{\text{lok}}$ erscheint im Weltsystem als
\begin{equation}
  \vp_m(\vq)=T_{l(m)}(\vq)\begin{psmallmatrix}\vp_m^{\text{lok}}\\1\end{psmallmatrix},
  \qquad
  \vn_m(\vq)=R\big(T_{l(m)}(\vq)\big)\,\vn_m^{\text{lok}} .
  \label{eq:fkmarker}
\end{equation}
Die Kette dieses Roboters ist in Tabelle~\ref{tab:chain} zusammengefasst.
Bemerkenswert: \emph{Base}, \emph{Hand} und \emph{Palm} tragen keine eigenen
Marker, und beide Finger teilen sich die Variable $e$ (mit entgegengesetzten
Achsen --- ein Greifer).

\begin{table}[h]
\centering
\begin{tabular}{@{}llllc@{}}
\toprule
Link & Eltern & Gelenktyp & Variable & Achse \\
\midrule
Board & --- & (Wurzel) & --- & --- \\
Base & Board & linear & $x$ & $[1,0,0]$ \\
Arm1 & Base & revolut & $y$ & $[-1,0,0]$ \\
Ellbow & Arm1 & revolut & $z$ & $[-1,0,0]$ \\
Arm2 & Ellbow & revolut & $a$ & $[0,-1,0]$ \\
Hand & Arm2 & revolut & $b$ & $[1,0,0]$ \\
Palm & Hand & revolut & $c$ & $[0,-1,0]$ \\
FingerA & Palm & linear & $e$ & $[1,0,0]$ \\
FingerB & Palm & linear & $e$ & $[-1,0,0]$ \\
\bottomrule
\end{tabular}
\caption{Kinematische Kette: sieben Freiheitsgrade, davon zwei linear ($x,e$).}
\label{tab:chain}
\end{table}

% =====================================================================
\section{Pose-Estimation: Vier Schätzverfahren}
% =====================================================================
Die Beobachtungen sind die Eckposen $\{(\vY_m, \vn^{o}_m)\}_{m\in\mathcal M}$
aus Stufe 3b. Alle Verfahren parametrisieren über die \emph{Gelenkvariablen}
$\vq$ (nicht über Links), wodurch geteilte Variablen ($e$) und markerlose
Glieder ($x,b,c$) generisch behandelt werden. Das gemeinsame
Residuum eines Markers kombiniert Position und Normale,
\begin{equation}
  \vr_m(\vq)=\Big[\;\vp_m(\vq)-\vY_m\,;\;\; w_n\big(\vn_m(\vq)-\vn^{o}_m\big)\Big]\in\R^6,
  \label{eq:res}
\end{equation}
mit der Normalengewichtung $w_n$ (Parameter \texttt{normal\_weight}).

\subsection{Blockstruktur der Kette}
Für die sequenziellen Verfahren wird die Kette in Blöcke zerlegt: Von der Wurzel
fortschreitend werden die Variablen markerloser Gelenke gesammelt und mit der
Variable des nächsten markertragenden Gelenks zu einem Block zusammengefasst,
der aus den Markern dieses Gliedes (zuzüglich Geschwistern mit geteilter
Variable) geschätzt wird. Für diesen Roboter ergeben sich
\[
  B_1=\{x,y\}\ \text{(Arm1)},\quad B_2=\{z\}\ \text{(Ellbow)},\quad
  B_3=\{a\}\ \text{(Arm2)},\quad B_4=\{b,c,e\}\ \text{(Finger)} .
\]

\subsection{Verfahren A --- \texttt{sequential\_vector}}
Analytische Winkelbestimmung pro rotatorischem Gelenk mit $\ge2$ sichtbaren
Markern auf demselben Glied. Mit der Weltachse $\va$ des Gelenks (aus der FK bei
$q=0$) bildet man für Markerpaare $(i,j)$ den Modellvektor
$\vd^{\text{mod}}_{ij}=\vp_j(q{=}0)-\vp_i(q{=}0)$ (im Weltsystem, da der lokale
Linkframe durch die Elterngelenke bereits rotiert ist) und den Beobachtungs-%
vektor $\vd^{\text{obs}}_{ij}=\vY_j-\vY_i$. Nach Projektion senkrecht zur Achse,
$\vd^{\perp}=\vd-(\vd\cdot\va)\va$, ist der vorzeichenbehaftete Drehwinkel
\begin{equation}
  \varphi_{ij}=\operatorname{atan2}\!\Big(\va\cdot(\vd^{\text{mod},\perp}_{ij}\times \vd^{\text{obs},\perp}_{ij}),\;
  \vd^{\text{mod},\perp}_{ij}\cdot \vd^{\text{obs},\perp}_{ij}\Big).
\end{equation}
Der Gelenkwinkel ist das mit $w_{ij}=\norm{\vd^{\text{mod},\perp}_{ij}}\,\norm{\vd^{\text{obs},\perp}_{ij}}$
gewichtete \emph{zirkuläre} Mittel
$q^*=\operatorname{atan2}(\sum w\sin\varphi,\ \sum w\cos\varphi)$.
Lineare, markerlose oder Einzelmarker-Gelenke werden über den Block-FK-Löser
(Verfahren B) ergänzt. Sehr schnell, keine lokalen Minima, benötigt aber
$\ge2$ Marker pro Gelenk.

\subsection{Verfahren B --- \texttt{sequential\_fk}}
Blockweiser nichtlinearer Ausgleich entlang der Kette. Für Block $B$ mit
Variablen $\vq_B$ und Markern $\mathcal M_B$ werden die vorhergehenden Blöcke
$\vq_{<B}$ fixiert und
\begin{equation}
  \vq_B^*=\arg\min_{\vq_B}\ \sum_{m\in\mathcal M_B}\rho_\delta\!\big(\norm{\vr_m(\vq_{<B},\vq_B)}\big)
\end{equation}
mit Huber-Verlust $\rho_\delta$ gelöst (Levenberg--Marquardt). Da die führende
Variable großwinklig sein kann, wird ein \emph{Multi-Start} über
$\{0,60,\dots,300\}^\circ$ durchgeführt und die Lösung mit den kleinsten Kosten
gewählt. Funktioniert bereits mit einem einzelnen Marker je Glied, weil
Gleichung~\eqref{eq:res} auch die Normale einbezieht.

\subsection{Verfahren C --- \texttt{global\_ba}}
Globales Bündelausgleichsproblem über \emph{alle} Gelenkvariablen gemeinsam:
\begin{equation}
  \vq^*=\arg\min_{\vq\in\R^7}\ \sum_{m\in\mathcal M}\rho_\delta\!\big(\norm{\vr_m(\vq)}\big).
  \label{eq:globalba}
\end{equation}
Da alle Marker simultan einfließen, bestimmen die Fingermarker rückwärts auch
die markerlosen Gelenke $b$ (Hand) und $c$ (Palm) --- ein verdecktes Mittelglied
wird durch nachgelagerte Marker überbrückt. Als Startwert dient die Lösung von
Verfahren B, was lokale Minima bei großen Winkeln vermeidet. $\rho_\delta$
(Huber, Skala \texttt{huber\_delta\_mm}) dämpft Ausreißer-Marker.

\subsection{Verfahren D --- \texttt{hybrid} (Standard)}
Die empfohlene Kombination: sequenzieller Initialwert (B) gefolgt von globalem
Refinement (C). Sie vereint die Robustheit des blockweisen Vorgehens gegen
lokale Minima mit der Genauigkeit und der Lückenüberbrückung des globalen
Ausgleichs und ist im Benchmark durchgehend am stabilsten.

% =====================================================================
\section{Beobachtbarkeit und Konfidenz}
% =====================================================================
Aus dem Verhältnis sichtbarer Marker zu Variablen je Block,
$\kappa_B=\lvert\mathcal M_B^{\text{obs}}\rvert/\lvert\vq_B\rvert$, wird pro
Gelenk eine Konfidenz abgeleitet:
\[
  \text{\texttt{high}}\ (\kappa\ge2),\quad
  \text{\texttt{medium}}\ (\kappa\ge1),\quad
  \text{\texttt{low}}\ (\kappa<1,\ \text{unterbestimmt}),\quad
  \text{\texttt{none}}\ (0\ \text{Marker}).
\]
Sie steht in \texttt{robot\_state.json} und ist sicherheitsrelevant: Gelenke mit
\texttt{low}/\texttt{none} sollten nicht ungeprüft angefahren werden. In solchen
Fällen kann durch Drehen der Achse $a$ (Arm2) die Hand aus weiteren Perspektiven
sichtbar gemacht werden (Multi-Pose), womit die Beobachtbarkeit steigt.

% =====================================================================
\section{Konfiguration über robot.json}
% =====================================================================
\texttt{robot.json} beschreibt zugleich das kinematische Modell, die
Markerpositionen, die Rendering-Parameter und die Algorithmuseinstellungen.
Die wichtigsten Abschnitte:

\begin{itemize}[leftmargin=1.4em,itemsep=2pt]
\item \texttt{units} --- Längen-/Winkeleinheiten (mm / Grad).
\item \texttt{vision\_config} --- ArUco-Wörterbuch und \texttt{MarkerSize}.
\item \texttt{links} --- die kinematische Kette: pro Link \texttt{parent},
  \texttt{mountPosition/Rotation}, \texttt{jointToParent} (\texttt{type},
  \texttt{variable}, \texttt{axis}, \texttt{origin}) und \texttt{markers}
  (\texttt{id}, \texttt{position}, \texttt{normal}, \texttt{size}).
\item \texttt{renderingInfo} --- Kamera/Render-Defaults (\texttt{width},
  \texttt{height}, \texttt{dofFStop}, \dots) für den Blender-Generator.
\item \texttt{pose\_estimation} --- Algorithmussteuerung (siehe unten).
\item \texttt{robot\_test\_poses}, \texttt{test\_camera\_positions/targets} ---
  Vorgaben für den Szenengenerator.
\end{itemize}

Der Block \texttt{pose\_estimation} steuert Stufe 4:
\begin{lstlisting}[language=]
"pose_estimation": {
  "method": "hybrid",            // sequential_vector | sequential_fk | global_ba | hybrid
  "marker_observation": "corner_pose",  // oder "center_point"
  "use_normals": true,
  "normal_weight": 100.0,        // Gewicht w_n der Normalenresiduen
  "robust_loss": "huber",        // robuste Verlustfunktion
  "huber_delta_mm": 8.0,         // Skala delta des Huber-Verlusts
  "max_iterations": 200,
  "min_cameras_per_marker": 2,   // Mindestkameras zur Triangulation
  "per_link_method": {}          // optional: Verfahren je Glied
}
\end{lstlisting}

\noindent\textbf{Wichtige Optionen.}
\texttt{method} wählt das Verfahren (A--D). \texttt{normal\_weight} ist der
zentrale Stabilitäts-Parameter: höhere Werte stabilisieren sicht-arme Posen
deutlich, da die gemessene Normale stärker einbezogen wird; zu hohe Werte
($\gtrsim\!300$) lassen die Orientierung die Position überstimmen. Empirisch ist
$w_n=100$ ein robuster Kompromiss. \texttt{huber\_delta\_mm} bestimmt, ab welchem
Residuum (in mm) ein Marker als Ausreißer gedämpft wird.
\texttt{marker\_observation} schaltet zwischen den Eckposen (\texttt{corner\_pose},
empfohlen) und reinen Mittelpunkten (\texttt{center\_point}) um.

% =====================================================================
\section{Aufruf und Verwendung}
% =====================================================================
\textbf{Gesamte Pipeline} (Detektion bis Gelenkwinkel) für einen Bildordner:
\begin{lstlisting}[language=bash]
cd run
.\run_pipeline.bat ..\data\simulation\Scene8
# erzeugt data/evaluations/Scene8/robot_state.json
\end{lstlisting}

\textbf{Einzelne Stufen} (z.\,B. zum Experimentieren mit dem Verfahren):
\begin{lstlisting}[language=bash]
python pipeline/3b_corner_marker_poses.py --evalDir data/evaluations/Scene8 --robot data/robot/robot.json
python pipeline/pose_estimation.py data/evaluations/Scene8/aruco_marker_poses.json \
       -robot data/robot/robot.json --method hybrid -out data/evaluations/Scene8/robot_state.json
\end{lstlisting}

\textbf{Validierung gegen Grundwahrheit} und \textbf{Benchmark}:
\begin{lstlisting}[language=bash]
python benchmark/eval_pose.py data/evaluations/Scene8/robot_state.json data/simulation/Scene8/pose.json
python benchmark/run_benchmark.py --scenes 4 5 6 7 8 9 9a 9b 11 12
python benchmark/stage0_corner_normals.py --evalDir data/evaluations/Scene5 \
       --gt data/simulation/Scene5/render_a.json
\end{lstlisting}

\textbf{Szenengenerator} (Blender, erzeugt Bilder + npz + Grundwahrheit):
\begin{lstlisting}[language=bash]
cd run
.\run_sceneGenerator.bat --poses 8     # nur Pose 8 rendern
\end{lstlisting}

\textbf{Visualisierung.} \texttt{run/robot\_viewer.html} im Browser öffnen,
\texttt{robot.json} und \texttt{aruco\_marker\_poses.json} laden. Der
Observed-Normals-Pfeil zeigt die gemessene Normale, eingefärbt nach
Winkelabweichung zur Modellnormale (grün $<2^\circ$ / gelb $2$--$5^\circ$ / rot $>5^\circ$).

% =====================================================================
\section{Validierung und Ergebnisse}
% =====================================================================
\subsection{Methodik}
Die Validierung nutzt synthetische Szenen aus einem Blender-Generator
(\texttt{run/run\_sceneGenerator.bat}). Dieser rendert pro Pose $C=7$ Ansichten
und schreibt neben Bildern (\texttt{render\_*.png}) und Intrinsiken
(\texttt{render\_*.npz}) zwei Grundwahrheits-Dateien: \texttt{render\_*.json} mit
den wahren Marker-Weltposen und \texttt{pose.json} mit den wahren Gelenkwinkeln.
Drei Werkzeuge vergleichen die Rekonstruktion gegen diese Wahrheit:
\texttt{stage0\_corner\_normals.py} (Normalenfehler der Triangulation),
\texttt{9\_evaluateMarker.py} (Positions-/Normalenfehler je Marker und Glied)
sowie \texttt{eval\_pose.py} (Gelenkwinkelfehler). Der Treiber
\texttt{run\_benchmark.py} aggregiert über Szenen und Verfahren.

\subsection{Ergebnisse}
Über zehn simulierte Posen mit bekannter Grundwahrheit (eine fehlerhaft
gerenderte Pose ausgeschlossen) ergeben sich die folgenden mittleren
Gelenk-Winkelfehler. Die Standardabweichung über die Szenen ist das Maß für die
\emph{Stabilität}.

\begin{center}
\begin{tabular}{@{}lccccc@{}}
\toprule
Verfahren & Mittel [$^\circ$] & Std [$^\circ$] & Schlechtest. [$^\circ$] & Mittel [mm] & Schlechtest. [mm]\\
\midrule
\texttt{sequential\_vector} & 0{,}315 & 0{,}128 & 1{,}717 & 0{,}144 & 0{,}712\\
\texttt{sequential\_fk}     & 0{,}434 & 0{,}171 & 1{,}838 & 0{,}158 & 0{,}851\\
\texttt{global\_ba}         & 0{,}253 & 0{,}134 & 1{,}568 & 0{,}103 & 0{,}390\\
\textbf{\texttt{hybrid}}    & \textbf{0{,}253} & \textbf{0{,}134} & \textbf{1{,}568} & \textbf{0{,}103} & \textbf{0{,}390}\\
\bottomrule
\end{tabular}
\end{center}

Alle Verfahren liegen unter $0{,}5^\circ$ Mittelwert; \texttt{hybrid}/\texttt{global\_ba}
sind am genauesten und am stabilsten und rekonstruieren auch die markerlosen
Gelenke $b,c$ rein aus den Fingermarkern.

% =====================================================================
\section{Grenzen und Ausblick}
\label{sec:limits}
% =====================================================================
\begin{itemize}[leftmargin=1.4em,itemsep=2pt]
\item \textbf{Intrinsik.} Da die Board-Marker koplanar sind, ist die Kamerantiefe
  schlecht konditioniert; ein fehlerhaftes $f_y$ (z.\,B. ein früherer
  Brennweitenfehler von $\sim$15\,\% bei 16:9) verfälscht alle Höhen. Korrekte
  npz-Intrinsiken sind Voraussetzung.
\item \textbf{Sicht-arme Posen.} Sind zu wenige Marker eines Gliedes sichtbar,
  ist der zugehörige Winkel schlecht bestimmt (\texttt{confidence=low}). Abhilfe:
  Multi-Pose durch Drehen der Achse $a$.
\item \textbf{Adaptive Normalengewichtung.} Ein festes $w_n$ ist ein Kompromiss;
  eine pro-Marker nach Sichtqualität skalierte Gewichtung könnte Best- und
  Worst-Case gleichzeitig optimieren.
\item \textbf{Reale Kameras.} Stufe 1 nutzt derzeit dieselbe Intrinsik-npz für
  alle Bilder (Simulation: identische Kameras); reale Aufbauten benötigen je
  Kamera eine eigene Kalibrierung.
\end{itemize}

\end{document}
