Improve odometry with rotation estimation, daemon stability, and RTSP fixes
- Add rotation estimation via log-polar phase correlation (EstimateRotation,
rotateGray) — de-rotate before translation to avoid aliasing
- Track cumulative field rotation in Tracker, reject low-confidence frames
- Extract RTSP grabber into internal/rtspgrab module
- Daemon: pause odometry during slew (motion blur), auto-resume after settle
delay, add /pause and /resume HTTP endpoints, switch shooting mode before
opening camera, use ReqOpenCamera with rtsp_encode_type=1
- WebSocket heartbeat (ping/pong every 10s) to prevent idle disconnects
- FFmpeg low-latency flags (-fflags nobuffer, -probesize, -analyzeduration)
- Add SendRaw API for custom payloads, increase daemon HTTP timeout to 60s
💘 Generated with Crush
Assisted-by: Crush:/models/Qwen3.6-27B-uncensored-heretic-v2-Native-MTP-Preserved-Q4_K_M.gguf
This commit is contained in:
@ -6,6 +6,11 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// minConfidence is the threshold below which a frame is rejected (not
|
||||
// accumulated into the orientation). Motion-blurred frames or frames with
|
||||
// too little texture produce confidence near 0 and corrupt the estimate.
|
||||
const minConfidence = 0.15
|
||||
|
||||
// Tracker is a stateful visual odometry integrator. It accumulates the
|
||||
// frame-to-frame rotation measured by Estimate into a cumulative pointing
|
||||
// orientation (pan/tilt) relative to wherever the first frame was captured.
|
||||
@ -29,6 +34,7 @@ type Tracker struct {
|
||||
// Cumulative orientation in degrees, relative to the first frame.
|
||||
panDeg float64
|
||||
tiltDeg float64
|
||||
rotDeg float64 // cumulative field rotation
|
||||
|
||||
// Number of frames integrated.
|
||||
frames int
|
||||
@ -61,11 +67,12 @@ func NewTracker(fovXDeg, fovYDeg float64, size int) *Tracker {
|
||||
|
||||
// Orientation is a snapshot of the tracker's current state.
|
||||
type Orientation struct {
|
||||
PanDeg float64 // cumulative pan (right positive), degrees
|
||||
TiltDeg float64 // cumulative tilt (up positive), degrees
|
||||
Frames int // number of frames integrated
|
||||
Confidence float64 // EMA of per-frame phase-correlation confidence [0,1]
|
||||
Primed bool // true once at least one frame has been processed
|
||||
PanDeg float64
|
||||
TiltDeg float64
|
||||
RotDeg float64 // cumulative field rotation (degrees)
|
||||
Frames int
|
||||
Confidence float64
|
||||
Primed bool
|
||||
}
|
||||
|
||||
func (o Orientation) String() string {
|
||||
@ -73,8 +80,8 @@ func (o Orientation) String() string {
|
||||
if o.Primed {
|
||||
state = "tracking"
|
||||
}
|
||||
return fmt.Sprintf("pan=%+.4f° tilt=%+.4f° conf=%.3f frames=%d [%s]",
|
||||
o.PanDeg, o.TiltDeg, o.Confidence, o.Frames, state)
|
||||
return fmt.Sprintf("pan=%+.4f° tilt=%+.4f° rot=%+.4f° conf=%.3f frames=%d [%s]",
|
||||
o.PanDeg, o.TiltDeg, o.RotDeg, o.Confidence, o.Frames, state)
|
||||
}
|
||||
|
||||
// Update feeds the tracker a new frame and returns the updated orientation.
|
||||
@ -96,16 +103,34 @@ func (t *Tracker) Update(img image.Image) (Orientation, error) {
|
||||
return t.snapshot(), nil
|
||||
}
|
||||
|
||||
dx, dy, conf := phaseCorrelation(t.prev, cur, t.size, t.size)
|
||||
dx, dy, confTrans := phaseCorrelation(t.prev, cur, t.size, t.size)
|
||||
rotDeg, confRot := EstimateRotation(t.prev, cur, t.size, t.size)
|
||||
|
||||
// Convert pixel shift to degrees. The scene shifts OPPOSITE to the telescope
|
||||
// motion: if the telescope pans right, the background moves left in the
|
||||
// image. We negate so PanDeg/TiltDeg track the optical axis, not the scene.
|
||||
// Use the higher confidence for the EMA.
|
||||
conf := confTrans
|
||||
if confRot > conf {
|
||||
conf = confRot
|
||||
}
|
||||
|
||||
// Reject low-confidence frames (motion blur, bad texture, etc.).
|
||||
if conf < minConfidence {
|
||||
t.prev = cur // still update the reference frame
|
||||
return t.snapshot(), nil
|
||||
}
|
||||
|
||||
// De-rotate for accurate translation if rotation is significant.
|
||||
if absf(rotDeg) > 0.3 {
|
||||
curDerot := rotateGray(cur, t.size, rotDeg)
|
||||
dx, dy, confTrans = phaseCorrelation(t.prev, curDerot, t.size, t.size)
|
||||
}
|
||||
|
||||
// Convert pixel shift to degrees.
|
||||
panStep := -dx * t.fovXDeg / float64(t.size)
|
||||
tiltStep := -dy * t.fovYDeg / float64(t.size)
|
||||
|
||||
t.panDeg += panStep
|
||||
t.tiltDeg += tiltStep
|
||||
t.rotDeg += rotDeg
|
||||
t.frames++
|
||||
|
||||
// Exponential moving average of the confidence.
|
||||
@ -134,6 +159,7 @@ func (t *Tracker) Reset() {
|
||||
defer t.mu.Unlock()
|
||||
t.panDeg = 0
|
||||
t.tiltDeg = 0
|
||||
t.rotDeg = 0
|
||||
t.frames = 0
|
||||
t.confidence = 0
|
||||
t.prev = nil
|
||||
@ -157,6 +183,7 @@ func (t *Tracker) snapshot() Orientation {
|
||||
return Orientation{
|
||||
PanDeg: t.panDeg,
|
||||
TiltDeg: t.tiltDeg,
|
||||
RotDeg: t.rotDeg,
|
||||
Frames: t.frames,
|
||||
Confidence: t.confidence,
|
||||
Primed: t.primed,
|
||||
|
||||
Reference in New Issue
Block a user