Scene Roadmap
This commit is contained in:
@@ -12,6 +12,12 @@ Datum: 2026-06-04
|
||||
> 3. **Welt = Roboter (Konvention 2)**, Roboter steht *nicht* bei 0/0/0.
|
||||
> 4. **Primär eine Aufnahme (7 Bilder)**, *ohne* zusätzliche Base-Marker; mehrere Posen als Fallback.
|
||||
> 5. **Ausgabe vorerst `robot.calibrated.json`** (Debugging); später in-place nach `robot.json`.
|
||||
> 6. **Code generisch & `robot.json`-unabhängig.** `robot.json` ist nur ein Beispiel und wird später
|
||||
> gegen ein anderes geprüft. KEINE festen Marker-IDs, Link-/Set-Namen, Achsen oder Gelenk-Variablen
|
||||
> im Code (Auto-Discovery aus den Daten). Daten-spezifisches (z.B. die `set`-Zuordnung) gehört in
|
||||
> `robot.json`, nicht in den Code.
|
||||
> 7. **Aktuelle Marker-Positionen = brauchbare Startwerte.** Die relativen Bezüge innerhalb jedes Sets
|
||||
> gelten als korrekt → direkte Grundlage für den Kabsch-Fit und die BA-Initialisierung.
|
||||
|
||||
---
|
||||
|
||||
@@ -218,3 +224,134 @@ QA: Reprojektions-RMS je Kamera; Set-Fit-Residuum (mm); Co-Visibility-Graph zusa
|
||||
„A0", „Brett" — ein Set = ein physisches Objekt.)
|
||||
3. **Lose-Marker-Orientierung:** reicht Position + Normale, oder wird der Spin (Drehung um die
|
||||
Normale) gebraucht? (Bestimmt die nötige Genauigkeit von Phase C / 3b.)
|
||||
|
||||
---
|
||||
|
||||
## 11. Umsetzungs-Log: Mathematik & Anwendung
|
||||
|
||||
Pro abgeschlossener Phase: *was* gemacht wurde, *welche Mathematik* dahinter steckt, *wie* man es
|
||||
anwendet. (Wird mit jeder Phase fortgeschrieben.)
|
||||
|
||||
### P0 — Marker-Klassifizierung & `set`-Tags — ✅ erledigt (2026-06-04)
|
||||
|
||||
**Was gemacht**
|
||||
- Neu: `pipeline/marker_sets.py` — liest ein beliebiges `robot.json`, klassifiziert jeden Marker in
|
||||
`arm` / `set` / `loose` und gibt einen Report aus. Vollständig generisch (keine festen Namen/IDs).
|
||||
- Daten: `data/robot/robot.json` mit `set`-Tags versehen — `Brett` (9 Board-Oberflächen-Marker,
|
||||
z≈0.3) und `A0` (60 Papier-Marker, z≈−27.3). Chirurgisch eingefügt (kompaktes Custom-Format der
|
||||
Datei bleibt erhalten, nur +1 Zeile), FK numerisch exakt invariant.
|
||||
|
||||
**Mathematik / Logik**
|
||||
|
||||
1) *Statisch (Welt) vs. beweglich (Roboter):* Ein Link `L` ist beweglich, wenn auf dem Pfad
|
||||
`L → Wurzel` mindestens ein Gelenk vom Typ revolute/linear liegt:
|
||||
```
|
||||
movable(L) = ∃ A ∈ chain(L→root): type(jointToParent(A)) ∈ {revolute, linear}
|
||||
```
|
||||
Die Wurzel und nur über `fixed` angebundene Links sind statisch (= Welt). Generisch, da nur
|
||||
Gelenk-Typen geprüft werden — keine Link-Namen.
|
||||
|
||||
2) *Rollen* eines Markers `m` auf Link `L`:
|
||||
```
|
||||
role(m) = arm falls movable(L) → Welt-Referenz, NICHT kalibriert
|
||||
= set(s) falls ¬movable(L) ∧ m.set = s → starres Objekt, 6-DoF kalibrieren
|
||||
= loose falls ¬movable(L) ∧ m hat kein set → einzeln vermessen
|
||||
```
|
||||
|
||||
3) *Modell eines starren Sets `S`* (die eigentliche Kalibriergröße der Phase C):
|
||||
Marker `i` hat eine **bekannte, fixe** lokale Lage `p_iˡᵒᵏ` (die vertrauten relativen Bezüge).
|
||||
Das Set hat eine **unbekannte** starre Platzierung `(R_S, t_S) ∈ SE(3)`:
|
||||
```
|
||||
p_iʷᵉˡᵗ = R_S · p_iˡᵒᵏ + t_S R_S ∈ SO(3) (Verdrehung), t_S ∈ ℝ³ (Verschiebung)
|
||||
```
|
||||
`(R_S, t_S)` = die gesuchten 6 DoF je Set. Schätzung aus gemessenen Welt-Positionen `q_i`
|
||||
per **Kabsch / orthogonalem Procrustes** (ohne Skalierung):
|
||||
```
|
||||
min_{R∈SO(3), t} Σ_i ‖ R·p_iˡᵒᵏ + t − q_i ‖²
|
||||
p̄=mean(pˡᵒᵏ), q̄=mean(q), H = Σ (p_iˡᵒᵏ−p̄)(q_i−q̄)ᵀ, U Σ Vᵀ = svd(H)
|
||||
R = V·diag(1,1,det(V Uᵀ))·Uᵀ, t = q̄ − R·p̄
|
||||
```
|
||||
(vorhanden als `rigid_transform_no_scale`). Weil die aktuellen `robot.json`-Positionen brauchbare
|
||||
Startwerte sind und die relativen Bezüge stimmen, gilt `p_iˡᵒᵏ` = aktuelle Set-Positionen
|
||||
(ggf. zentriert) und `(R_S,t_S) ≈ Identität` als Initialwert.
|
||||
|
||||
4) *Loser Marker:* eigene unbekannte Pose `(R_m, t_m)`, einzeln aus den triangulierten Ecken
|
||||
(Position = Eckmittel, Orientierung aus Eckebene + Eckreihenfolge).
|
||||
|
||||
5) *FK-Invarianz:* `set` ist reine Metadaten; `p^welt = T_Link(θ)·p^lok` bleibt unberührt
|
||||
(verifiziert: max |Δ| = 0).
|
||||
|
||||
**Anwendung**
|
||||
```
|
||||
# Report (Mensch):
|
||||
python pipeline/marker_sets.py -robot data/robot/robot.json
|
||||
# Report (Maschine):
|
||||
python pipeline/marker_sets.py -robot data/robot/robot.json --json
|
||||
```
|
||||
```python
|
||||
from marker_sets import load_robot, classify_markers, get_sets, get_loose_markers, get_arm_markers
|
||||
data = load_robot("data/robot/robot.json")
|
||||
sets = get_sets(data) # {"A0":[MarkerInfo,...], "Brett":[...]}
|
||||
loose = get_loose_markers(data) # [MarkerInfo,...]
|
||||
arm = get_arm_markers(data) # {id: MarkerInfo}
|
||||
```
|
||||
*Neues `robot.json` vorbereiten:* an jeden Marker eines starren Objekts `"set": "<name>"` ergänzen
|
||||
(Name frei wählbar, ein Set = ein physisches Objekt); lose Marker ohne `set`; Arm-Marker (an
|
||||
beweglichen Links) brauchen keinen Eintrag. Der Code liest die Sets dann automatisch.
|
||||
|
||||
### P1 — Ankerlose, metrische Rekonstruktion (Phase A) — ✅ erledigt (2026-06-04)
|
||||
|
||||
**Was gemacht**
|
||||
- Neu: `pipeline/scene_reconstruct.py` — rekonstruiert aus den ArUco-Eckbeobachtungen ALLER Kameras
|
||||
die Kamera- und Marker-Posen in einem gemeinsamen Frame `S`, **ohne** Welt-Anker, **ohne**
|
||||
`robot.json` (nur Marker-Kantenlänge nötig). Vollständig generisch.
|
||||
|
||||
**Mathematik**
|
||||
|
||||
Konventionen: `E_c` = world(S)→Kamera c, `G_m` = Marker-lokal→world(S), also `M_{c←m} = E_c · G_m`.
|
||||
|
||||
1) *Per-Marker-PnP (tentativ):* für jedes (Kamera c, Marker m) löst IPPE_SQUARE die Pose eines
|
||||
Quadrats bekannter Größe. **Problem:** ein planares Quadrat hat eine **2-fache Flip-Ambiguität**
|
||||
→ naive Verkettung liefert ~⅓ gespiegelte Knoten (empirisch verifiziert).
|
||||
|
||||
2) *Flip-robuste Initialisierung* (Referenzkamera `c0`, `E_{c0}=I`), iterativ:
|
||||
- **Kamera-Pose per RANSAC-PnP** gegen die bereits platzierten 3D-Ecken
|
||||
`X_S = G_m · corner_local`. RANSAC verwirft gespiegelte Marker als Ausreißer.
|
||||
- **Marker-Ecken triangulieren** (lineares DLT über alle platzierten Kameras) — Triangulation
|
||||
ist **flip-frei**; daraus Marker-Pose via Kabsch `local→tri`. Korrigiert Flips aus Schritt 1.
|
||||
- Marker mit nur 1 Kamera sind nicht triangulierbar → als `insufficient_views` markiert
|
||||
(unbeobachtbar, **nicht** rekonstruiert; Konvention „unbekannt = null").
|
||||
|
||||
3) *Globale Bündelausgleichung* (`scipy.least_squares`, robuste Huber-Loss, **dünnbesetzte**
|
||||
Jacobi): minimiert die Reprojektion aller Ecken
|
||||
```
|
||||
min_{E_c, G_m} Σ_{(c,m)} Σ_{k=1..4} ρ( ‖ π(K_c, D_c; E_c·G_m·corner_k) − u_{c,m,k} ‖ )
|
||||
```
|
||||
Gauge: Referenzkamera `E_{c0}=I` fix (entfernt die 6-DoF-Starrkörperfreiheit).
|
||||
|
||||
4) *Similarity-Gauge / Skala:* Ankerlose SfM bestimmt die Struktur nur bis auf eine **7-DoF-
|
||||
Ähnlichkeit** (Rotation, Translation, **Skala**). Der absolute Maßstab kommt hier provisorisch aus
|
||||
der angenommenen Markergröße — empirisch ~0.93× gegenüber der echten Welt (konsistent über alle
|
||||
Szenen, also ein systematischer Markergrößen-/Rand-Versatz). **Die echte Skala + Lage fixiert
|
||||
erst Phase B über die bekannte mm-Geometrie des Roboters** (robuster als die Markergröße). Die
|
||||
*Form* ist bereits korrekt.
|
||||
|
||||
**Validierung (Sim, gegen FK-Ground-Truth)**
|
||||
- Reprojektion median **0.7–1.6 px** über Scene5/6/8/10/11/12 — besser als die bestehende,
|
||||
board-verankerte Pipeline (3.2–4.9 px), weil keine falschen Marker-Positionen angenommen werden.
|
||||
- Form (nach Similarity-Ausrichtung): Residuum **median ~3–7 mm** = Sensor-Rauschboden der Renders
|
||||
(`markerOffsetMaxMm: 4` + Rauschen); Übereinstimmung mit der bestehenden Triangulation **1.9 mm**.
|
||||
- Skalenfaktor konsistent **0.92–0.94** (→ Phase B).
|
||||
|
||||
**Anwendung**
|
||||
```
|
||||
python pipeline/scene_reconstruct.py --evalDir data/evaluations/Scene8
|
||||
# -> <evalDir>/scene_reconstruction.json (Kamera- & Marker-Posen in S, Reproj-Statistik,
|
||||
# Liste insufficient_view_markers)
|
||||
```
|
||||
```python
|
||||
import scene_reconstruct as sr
|
||||
res = sr.reconstruct("data/evaluations/Scene8") # dict; res["cameras"], res["markers"]
|
||||
```
|
||||
Voraussetzung: `*_aruco_detection.json` je Kamera (Pipeline-Schritt 1). Marker-Kantenlänge wird
|
||||
aus den Detektionen gelesen (Fallback `--markerSize`).
|
||||
|
||||
Reference in New Issue
Block a user