Skip to content

Feat/e2 pitch kalman#33

Open
eng-EslamEzzat wants to merge 9 commits into
viplix3:mainfrom
AromaStudiosDevelopment:feat/E2-pitch-kalman
Open

Feat/e2 pitch kalman#33
eng-EslamEzzat wants to merge 9 commits into
viplix3:mainfrom
AromaStudiosDevelopment:feat/E2-pitch-kalman

Conversation

@eng-EslamEzzat

Copy link
Copy Markdown

No description provided.

aramadan0096 and others added 9 commits May 8, 2026 17:35
Add a second public BoTSORT::track() entry point that accepts a parallel
vector of FeatureVector embeddings alongside the detections. When supplied,
the embeddings are used verbatim to construct each Track and to drive the
appearance-aware association branches, regardless of whether the legacy
TrackerParams::reid_enabled flag is set or an internal ReIDModel was
loaded. This lets a downstream pipeline that already runs Re-ID on GPU
(e.g. an upstream OSNet stage) feed the tracker without forcing the
internal _extract_features() path, which round-trips each detection
through host cv::Mat patches.

Behaviour preservation:
- The legacy track(detections, frame) overload is now a thin delegator
  that calls the new overload with an empty features vector. All
  existing semantics (low-conf segregation, KF predict, GMC, two-stage
  association, lost-track aging, duplicate removal) are unchanged.
- TrackerParams::reid_enabled retains its original meaning: it gates
  internal _extract_features() only.
- Empty features + reid_enabled -> internal extraction (legacy path).
- Empty features + !reid_enabled -> motion-only tracking (legacy path).
- Non-empty features -> appearance-aware association using the supplied
  embeddings; the internal model is not required.
- Mismatched features.size() vs detections.size() throws
  std::invalid_argument.

A cached _distance_metric member replaces the prior
_reid_model->get_distance_metric() call sites; it is populated from the
loaded model when present, otherwise defaults to "cosine" so external
embeddings have a metric to use without an internal model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds BoTSORT::set_gmc_enabled(bool) so the host pipeline can toggle
camera motion compensation between successive track() calls. The
existing constructor-time gmc_config still controls whether the GMC
algorithm is built at all; this setter only enables/disables the
per-frame apply step.

Motivation: TrackerModule's adaptive-CMC gate skips CMC on stable
broadcast scenes (no panning/zooming) to recover the v1.7 Sprint 4
tracker p99 budget (7.4 ms -> <5 ms target). The host detects stability
from cross-frame track centroid drift; this setter is the actuator.

The set_gmc_enabled(true) call is a no-op when _gmc_algo is null
(GMC was never configured at construction), so callers never need to
inspect the construction state to keep the call safe.
For host pipelines that run a single shared GMC pass upstream and
dispatch detections across multiple BoTSORT instances per frame
(e.g. OpticXI's PerClassTrackerBank). When the caller supplies a
HomographyMatrix, the internal _gmc_algo->apply() pass is skipped
and the caller-supplied matrix is applied via Track::multi_gmc to
both tracked + unconfirmed tracks. The existing two overloads stay
unchanged.
For OpticXI Phase E.2 pitch-plane Kalman state. Runs in parallel with the
existing 8-D pixel KalmanFilter; the host decides per-frame which gate
to use. Non-finite measurement guard returns state unchanged.
For OpticXI Phase E.2 pitch-plane Kalman. Adds _pitch_mean (4-D),
_pitch_covariance (4x4), _pitch_kf_initialized (bool); public
activate_pitch / predict_pitch / update_pitch / pitch_kf_initialized /
pitch_mean / pitch_covariance. State lazily activates on the first
matched detection where the host's homography is valid.
… params

Code-review fix on E.2.2:
  - activate_pitch docstring claimed 'no-op if initialized', but the
    implementation unconditionally re-inits (legitimate re-activate
    after long occlusion). Docstring updated to match behavior; callers
    wanting seed-if-empty should use update_pitch (which lazy-activates).
  - activate_pitch and update_pitch took non-const PitchKalmanFilter&
    even though PitchKalmanFilter::init/predict/update are all const.
    Changed to const PitchKalmanFilter& (matches predict_pitch).
  - Renamed structured-binding locals from mean/cov to pkf_mean/pkf_cov
    to avoid shadowing Track's public 'mean' member.
For OpticXI Phase E.2. Adds:
  - ctor param pitch_kalman_enabled (default false -> bit-identical to pre-E.2)
  - 4th track() overload taking per-detection optional metre foot-points
  - predict_pitch pass for tracks with initialized metre state
  - new fuse_motion_pitch helper (additive Mahalanobis tightening)
  - update_pitch / activate_pitch on matched detections that carry a
    metre measurement
…on_pitch

Code-review fixes on E.2.3:
  - _frame_metre_measurements is now reset by an RAII guard so _track_impl
    can throw without leaving the borrowed pointer dangling into a popped
    caller stack frame.
  - fuse_motion_pitch collects valid metre measurements once and calls
    PitchKalmanFilter::gating_distance once per track instead of once per
    (track, detection) cell. Per-frame Cholesky count drops from
    O(tracks x dets x passes) to O(tracks x passes) — ~25x for typical
    22-player scenes.
The host pipeline (PerClassTrackerBank) needs the current frame counter to
compute per-track lost-duration (current_frame - bt->frame_id) for
downstream consumers like the E.3 ROI module's recency gate.
Pure additive — no behavior change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants