The app display value (8.0×6.5) is a digitally-cropped view. The firmware
reports the true physical FoV via GetDeviceState. Update DefaultFoVH/V,
README, and ODOMETRY docs accordingly.
💘 Generated with Crush
Assisted-by: Crush:/models/Qwen3.6-27B-uncensored-heretic-v2-Native-MTP-Preserved-Q4_K_M.gguf
8.0 KiB
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
-
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. -
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.
-
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.
-
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. -
Recherche de pic + raffinement sub-pixel — le pic est localisé, puis interpolé paraboliquement sur chaque axe pour une précision ~0.1px.
-
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 (42.65° × 24.45°) correspondent au FoV physique réel
rapporté par le firmware (GetDeviceState), pas à la valeur d'affichage de
l'app (8.0×6.5 qui est un crop numériquement zoomé).
# 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
-
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.
-
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.
-
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.
-
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.
-
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).