Files
dwarf-go/dwarfctl/ODOMETRY.md
Jacquin Antoine e8ae644d58 docs: add visual odometry technical guide
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
2026-07-13 23:10:14 +02:00

8.1 KiB
Raw Blame History

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

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.

# 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).