From e8ae644d587e5f7d8c4572d86221ab1ee2f9678b Mon Sep 17 00:00:00 2001 From: Jacquin Antoine Date: Mon, 13 Jul 2026 23:10:14 +0200 Subject: [PATCH] docs: add visual odometry technical guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover phase correlation pipeline, log-polar rotation estimation, de-rotation pass, Tracker integration, motion-blur handling, FoV calibration, and limitations. 💘 Generated with Crush Assisted-by: Crush:/models/Qwen3.6-27B-uncensored-heretic-v2-Native-MTP-Preserved-Q4_K_M.gguf --- dwarfctl/ODOMETRY.md | 233 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 dwarfctl/ODOMETRY.md diff --git a/dwarfctl/ODOMETRY.md b/dwarfctl/ODOMETRY.md new file mode 100644 index 0000000..a379655 --- /dev/null +++ b/dwarfctl/ODOMETRY.md @@ -0,0 +1,233 @@ +# Visual Odometry — Guide technique + +L'odomĂ©trie visuelle de dwarfctl estime le pointage du tĂ©lescope entre deux +images de la camĂ©ra wide en utilisant la **corrĂ©lation de phase par FFT**. +Pas besoin de reconnaissance d'Ă©toiles, de plate solving ou d'alignement +polaire — ça fonctionne sur n'importe quelle scĂšne texturĂ©e (ciel, horizon, +nuages, paysage diurne). + +--- + +## Concept fondamental + +Quand le tĂ©lescope effectue un petit mouvement (pan ou tilt), le contenu de +l'image subit un **dĂ©calage quasi-pur** dans le plan image : + +- Pan (azimut) → dĂ©calage horizontal +- Tilt (altitude) → dĂ©calage vertical +- Rotation azimutale prĂšs du zĂ©nith → rotation du champ (field rotation) + +La corrĂ©lation de phase rĂ©cupĂšre ce dĂ©calage avec une prĂ©cision sub-pixell, +qui se convertit en degrĂ©s via le champ de vision (FoV) de la camĂ©ra. + +--- + +## Pipeline de corrĂ©lation de phase + +``` +Image 1 ─┐ ┌─→ dx, dy (pixels) ─→ degrĂ©s + ├→ FFTÂČ â†’ CPS → IFFT ├─→ pic de corrĂ©lation +Image 2 ─┘ └─→ confiance [0, 1] +``` + +### DĂ©tails du pipeline + +1. **Conversion grayscale + downsampling** — les images JPEG/PNG sont rĂ©duites + Ă  une grille carrĂ©e de taille `size` (256 par dĂ©faut). Le downsampling + utilise une moyenne de bloc pour Ă©viter le aliasing. + +2. **FenĂȘtrage Hann 2D + suppression du DC** — le composant continu (moyenne + de l'image) est soustrait, puis une fenĂȘtre de Hann 2D est appliquĂ©e pour + rĂ©duire les fuites spectrales aux bords. + +3. **FFTÂČ (transformĂ©e 2D)** — FFT sĂ©parable : d'abord sur chaque ligne, + puis sur chaque colonne. ImplĂ©mentation radix-2 Cooley-Tukey itĂ©rative + avec permutation en bit-reversal. + +4. **Spectre de puissance croisĂ©e normalisĂ©** — `conj(F1) * F2 / |F1*F2|` + ne garde que la phase diffĂ©rentielle. Le pic de l'IFT donne le dĂ©calage. + +5. **Recherche de pic + raffinement sub-pixel** — le pic est localisĂ©, puis + interpolĂ© paraboliquement sur chaque axe pour une prĂ©cision ~0.1px. + +6. **Confiance** — hauteur du pic relative Ă  la moyenne de la surface de + corrĂ©lation. ClamĂ© Ă  [0, 1]. En dessous de 0.15, le Tracker rejette + le frame (flou de mouvement, texture insuffisante, etc.). + +--- + +## Estimation de rotation — mĂ©thode log-polaires + +La rotation du champ (dominante prĂšs du zĂ©nith sur une monture az-el) est +estimĂ©e sĂ©parĂ©ment via la **corrĂ©lation de phase en coordonnĂ©es log-polaires** : + +``` +Image ─→ FFTÂČ â”€â†’ |magnitude| ─→ filtrage HP ─→ resampling log-polaires ─→ PC +``` + +### Pourquoi ça marche + +Une rotation dans le domaine spatial devient une rotation dans le domaine +frĂ©quentiel. En convertissant le spectre de magnitude en coordonnĂ©es +log-polaires, cette rotation devient un **dĂ©calage linĂ©aire** le long de +l'axe angulaire. La corrĂ©lation de phase rĂ©cupĂšre ce dĂ©calage, qui se +convertit directement en degrĂ©s : + +``` +angle = -dy * 360° / nAngles +``` + +oĂč `nAngles = 256` (rĂ©solution angulaire) et `nRadii = 128`. + +Le filtrage passe-haut gaussien supprime le pic DC central qui domine le +spectre et fausserait la corrĂ©lation. + +### Calibration FoV + +Le resampling log-polaires utilise une interpolation bilinĂ©aire. Les +paramĂštres `maxR`, `logMinR` et `logMaxR` sont dĂ©rivĂ©s des dimensions de +l'image FFT. + +--- + +## DĂ©rotation — deux passes pour la prĂ©cision + +Quand la rotation dĂ©passe 0.3°, la translation est corrompue par l'aliasing +induit par la rotation. Le pipeline en deux passes rĂ©sout ce problĂšme : + +``` +Pass 1: rotation (log-polaires) → rotDeg +Pass 2: dĂ©rotation de g2 → corrĂ©lation de translation sur images alignĂ©es +``` + +La fonction `rotateGray` applique une rotation inverse autour du centre +de l'image, avec interpolation bilinĂ©aire. Les bords sont clampĂ©s pour +Ă©viter les dĂ©passements. + +--- + +## Tracker — intĂ©grateur cumulatif + +Le `Tracker` accumule les rotations mesurĂ©es frame par frame pour maintenir +une orientation cumulative relative au premier frame : + +### États + +- **Priming** — le premier frame est enregistrĂ© comme rĂ©fĂ©rence (origine 0,0). + Aucune mesure n'est faite. +- **Tracking** — chaque nouveau frame est comparĂ© au prĂ©cĂ©dent, le rĂ©sultat + est intĂ©grĂ©. + +### Variables intĂ©grĂ©es + +| Variable | Description | +|----------|-------------| +| `panDeg` | Pan cumulatif (droite positive), degrĂ©s | +| `tiltDeg` | Tilt cumulatif (haut positif), degrĂ©s | +| `rotDeg` | Rotation de champ cumulative (CCW positive), degrĂ©s | +| `confidence` | EMA exponentielle de la confiance par frame (alpha=0.2) | +| `frames` | Nombre de frames intĂ©grĂ©s | + +### Rejet de frames + +Les frames avec une confiance infĂ©rieure Ă  0.15 sont rejetĂ©s — ils ne +s'intĂšgrent pas dans l'orientation. Le frame de rĂ©fĂ©rence est quand mĂȘme +mis Ă  jour pour Ă©viter l'accumulation de dĂ©calage. + +### Reset + +`Tracker.Reset()` remet toutes les variables Ă  zĂ©ro et dĂ©prime le tracker. +Le prochain `Update()` deviendra la nouvelle origine. + +--- + +## Configuration + +### Taille de grille FFT (`size`) + +| Taille | RĂ©solution | CoĂ»t CPU | Usage | +|--------|-----------|----------|-------| +| 256 | ~0.1px | Bas | DĂ©faut, suffit pour la plupart des cas | +| 512 | ~0.05px | ~4x plus | Quand la prĂ©cision sub-pixel est critique | +| 1024 | ~0.025px | ~16x plus | Rarement nĂ©cessaire | + +La taille doit ĂȘtre une puissance de deux (contrainte de l'algorithme +radix-2). + +### Champ de vision (FoV) + +Les valeurs par dĂ©faut (`8.0°` × `6.5°`) correspondent au DWARF Mini +affichĂ©, mais le FoV rĂ©el rapportĂ© par le firmware peut varier largement +(voir `analysis/DEVICE_MODELS.md`). **Calibrer avec un mouvement moteur +connu** : slew d'un angle connu, comparer avec la mesure odometry. + +```bash +# Override FoV via daemon HTTP API +curl -X POST http://127.0.0.1:7777/fov -d '{"h": 10.2, "v": 8.1}' +``` + +### Intervalle de capture + +Le daemon capture un frame toutes les `--interval` secondes (dĂ©faut 5s). +Un intervalle trop court peut capturer des frames avec flou de mouvement +pendant les slew. Le daemon gĂšre cela via le pause automatique. + +--- + +## Gestion du flou de mouvement dans le daemon + +Le daemon met en pause automatiquement la capture odometry pendant les +mouvements de moteur : + +``` +/slew → pause immĂ©diate +/stop → pause + dĂ©lai de settling (3s par dĂ©faut) + ↓ +attente → reprise auto +``` + +Le dĂ©lai de settling (`SettleDelay` dans `daemon.Config`) permet aux +moteurs de s'arrĂȘter complĂštement avant la prochaine capture. Sans ce +dĂ©lai, les frames flous corrompent la corrĂ©lation de phase. + +--- + +## Limitations + +1. **Texture requise** — un ciel uniforme sans nuages ni Ă©toiles donne + une corrĂ©lation faible (< 0.05). La camĂ©ra wide voit le ciel et + l'horizon simultanĂ©ment, ce qui aide. + +2. **Mouvement limitĂ©** — le dĂ©calage entre frames ne doit pas dĂ©passer + ~1/4 de la taille de la grille. Des mouvements brusques peuvent + perdre la corrĂ©lation. + +3. **Pas de position absolue** — l'odomĂ©trie mesure des rotations + **relatives** au premier frame. Pour connaĂźtre la position absolue, + il faut soit la calibration astro (EQ calibrate), soit un encodeur. + +4. **DĂ©rive cumulative** — chaque mesure introduit une petite erreur qui + s'accumule. Plus le nombre de frames est Ă©levĂ©, plus l'orientation + cumulative dĂ©rive. Reset pĂ©riodiquement ou recalibre via la calibration + astro du tĂ©lescope. + +5. **Monture az-el uniquement** — conçu pour une monture alt-az statique. + AprĂšs calibration EQ, le mapping moteur → axe change et les directions + pan/tilt ne correspondent plus aux axes physiques. + +--- + +## Architecture code + +``` +odometry/ +├── fft.go FFT 1D/2D radix-2 Cooley-Tukey itĂ©ratif +├── odometry.go Estimate() — pipeline principal + helpers (window, DC, parabola) +├── rotation.go EstimateRotation() — log-polaires + phase correlation +├── rotate.go rotateGray() — rotation inverse bilinĂ©aire +└── tracker.go Tracker — intĂ©grateur cumulatif avec rejet de frames +``` + +Le code est entiĂšrement en Go standard — aucune dĂ©pendance externe pour +l'odomĂ©trie. Le seul binaire externe requis est `ffmpeg` pour la capture +RTSP (via `internal/rtspgrab`).