Skip to content

Latest commit

 

History

History
365 lines (277 loc) · 13.8 KB

quickstart-python.md

File metadata and controls

365 lines (277 loc) · 13.8 KB

Sky Tracker SDK — Python Quickstart

Version 1.1.0 has three profiles: default, correlation and legacy-adaptive. Coming from 1.0.0, see the 1.1.0 release notes: pure is gone and NanoTrack moved behind ml=True. Coming from 0.2.x, see also the 1.0.0 release notes.

Prerequisites

  • Python 3.10 or later
  • OpenCV for Python: pip install opencv-python
  • sky_tracker.pyd (Windows) or sky_tracker.so (Linux) built from the C++ Python binding
  • Your evaluation licence key

1. Set your licence key

# PowerShell
$env:SKY_TRACKER_LICENSE_KEY = "<your-token>"
rem Windows CMD
set SKY_TRACKER_LICENSE_KEY=<your-token>
# Linux / macOS
export SKY_TRACKER_LICENSE_KEY=<your-token>

The Tracker constructor reads this variable at import time. A missing or invalid key raises RuntimeError immediately.


2. Run the smoke test

The quickest check — runs 30 frames of the sample video and reports confidence:

python cpp_tracker\tools\smoke_python_sdk.py `
  --module-dir cpp_tracker\build-python\Release `
  --video sample.mp4 `
  --bbox 469,409,26,38 `
  --frames 30

Expected output:

python_sdk_smoke passed frames=30 active=29 max_confidence=0.891

If the module directory is the default (cpp_tracker/build-python/Release) and video_20.mp4 exists in the repo root, all flags are optional:

python cpp_tracker\tools\smoke_python_sdk.py

3. Frame-by-frame API

import os
import sys
import cv2

# ── Load the module ──────────────────────────────────────────────────────────
MODULE_DIR = r"cpp_tracker\build-python\Release"
if os.name == "nt":
    os.add_dll_directory(MODULE_DIR)   # Windows: load OpenCV DLLs next to the .pyd
sys.path.insert(0, MODULE_DIR)

import sky_tracker  # noqa: E402  (must come after path setup)

# ── Open video ───────────────────────────────────────────────────────────────
cap = cv2.VideoCapture("sample.mp4")
fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
dt  = 1.0 / fps

# ── Lock on to the target ────────────────────────────────────────────────────
ok, first_frame = cap.read()
assert ok, "could not read first frame"

tracker = sky_tracker.Tracker("default")   # profiles: default, correlation, legacy-adaptive
tracker.lock(first_frame, bbox=(469, 409, 26, 38))   # (x, y, width, height)

# ── Process frames ───────────────────────────────────────────────────────────
frame = first_frame
for i in range(120):
    if i > 0:
        ok, frame = cap.read()
        if not ok:
            break

    result = tracker.update(frame, dt)

    print(
        f"frame={result.frame:4d}  state={result.state:12s}  "
        f"cx={result.cx:6.1f}  cy={result.cy:6.1f}  "
        f"conf={result.confidence:.3f}"
    )

    # Optional: get annotated frame with cyan HUD overlay
    # annotated = result.annotate()
    # cv2.imshow("sky_tracker", annotated)
    # cv2.waitKey(1)

cap.release()

4. Native multi-target API

Use sky_tracker.MultiTracker when the caller provides several selected targets. The SDK assigns stable target_id values from the initial bbox order.

multi = sky_tracker.MultiTracker("default")
multi.lock(first_frame, [
    (469, 409, 26, 38),
    (520, 390, 24, 34),
])

for i in range(120):
    ok, frame = cap.read()
    if not ok:
        break

    for result in multi.update(frame, dt):
        print(
            f"target={result.target_id} state={result.state} "
            f"bbox={result.bbox()} suppressed={result.suppressed}"
        )

Target proposals are assigned one-to-one before tracker state is committed. A clear duplicate loser coasts with suppressed=True, state="lost", and suppressed_by_id pointing to the owner. Indistinguishable claims fail closed with reason="coast-ambiguous-owner" and suppressed_by_id=0, avoiding an arbitrary target-order tie-break.

During close interactions, translation continues while appearance learning and adaptive bbox geometry updates are frozen. Outside an interaction, legacy-adaptive foreground geometry and default candidate geometry are aspect/scale constrained, clipped to the assigned ownership cell, and kept separate from the identity search bbox.

Command-line example:

python cpp_tracker\examples\track_multi_video.py `
  --video video_20.mp4 `
  --bbox 469,409,26,38 `
  --bbox 520,390,24,34 `
  --csv cpp_tracker\build-python\video_20_multi_sdk.csv

5. Finding moving objects: MotionTracker

sky_tracker.MotionTracker needs no bbox. It finds whatever moves in each frame and follows each object with its own stable id: drones and birds against the sky, or meteors. Use it with a static or slowly moving camera.

tracker = sky_tracker.MotionTracker(mode="bright-diff", moving_only=True)

while True:
    ok, frame = cap.read()
    if not ok:
        break
    for track in tracker.update(frame, dt=1 / fps):
        if track.state == "confirmed":
            print(track.id, track.center(), f"{track.speed:.0f} px/s")

Modes:

  • "background" (the default): background subtraction. It tolerates slow changes in light; the first frames are spent learning the background.
  • "diff": frame differencing. Faster; static camera only.
  • "bright-diff": what got brighter since the last frame. It boxes a meteor's head rather than its trail. Static camera only. Free: it needs no licence key, so meteor and sky-event tools can use it without one.

moving_only=True reports an object once it has moved 3 px, and drops it after 1 frame without a detection. That removes twinkling stars and sensor noise. region=(x, y, w, h) looks only at part of the frame, for example the sky above the horizon; results stay in frame coordinates. Call reset() after a scene cut.


6. API reference

sky_tracker.Tracker(profile, *, ml=False, ml_every=1)

Profile Use case
"default" Recommended speed/quality balance; uses the former Pi4-target policy
"correlation" An improved correlation-filter tracker, no neural network; single target, Tracker only
"legacy-adaptive" Template tracker with correlation assist (strict checks, camera-motion compensation) and scheduled foreground box fitting

"correlation" needs no OpenCV dnn and no model files; its confidence is 1 while tracking and 0 once it has lost the target. ml=True (with "correlation" only) adds NanoTrack, a small neural tracker, and arbitrates between the two, on every frame by default (exactly 1.0.0's correlation). ml_every=N runs NanoTrack on every Nth frame while the two agree, and on every frame while they disagree or the target is lost: about half the cost at N = 3, but on fast footage the box can jump between checks. With ml=True the constructor raises when the module was built without NanoTrack or cannot find its models; sky_tracker.has_nanotrack tells you which builds have it (the Linux and macOS wheels do). The default aliases pi4-target, raspi4-target and pi-target are still accepted. Accuracy and speed per profile are in the benchmarks.

You can also pass a TrackerConfig object:

cfg = sky_tracker.TrackerConfig()
cfg.profile = "correlation"
cfg.ml = True
cfg.ml_every = 1
tracker = sky_tracker.Tracker(cfg)

tracker.lock(frame, bbox) / tracker.initialize(frame, bbox)

Seeds the tracker on the first frame. bbox is (x, y, width, height) in pixels.

tracker.update(frame, dt=-1.0) → TrackResult

Process the next frame. Pass dt in seconds for deterministic video processing. If omitted, the tracker measures wall-clock time automatically.

sky_tracker.MultiTracker(profile)

Native selected-target multi-tracker. Use lock(frame, bboxes) with bboxes=[(x, y, width, height), ...], then call update(frame, dt) once per frame. It returns a list of TrackResult objects.

sky_tracker.MotionTracker(*, mode="background", min_area=3, max_area=5000, diff_threshold=25, hit_threshold=3, coast_frames=10, association_distance=60, moving_only=False, box_padding=0, region=None)

Finds and follows everything that moves (section 5). min_area and max_area bound a detection's area in pixels, and diff_threshold (1-254) is the per-pixel change for diff and bright-diff. An object becomes confirmed after hit_threshold frames, and ends after coast_frames frames without a detection. association_distance is how far, in pixels, an object may be from its predicted position and still keep its id. box_padding grows every box by that many pixels on each side.

update(frame, dt=-1.0) takes a grey or BGR frame, the same size every time, and returns a list of MotionTrack in id order, tentative ones included. reset() forgets the background and every object; new ids continue above the old ones. Invalid options raise ValueError.

MotionTrack fields: id, state (tentative, confirmed or lost), cx, cy, bbox_x, bbox_y, bbox_w, bbox_h, vx and vy (pixels/second), speed (pixels/second), age (frames since found), hits (frames detected), misses (consecutive frames without a detection) and prediction_only. Methods bbox() and center() return tuples.

TrackResult fields

Field Type Description
frame int Frame counter since lock()
lost bool True when the tracker has lost the target
state str "confirmed", "tentative", or "lost"
cx, cy float Target centre, pixels
bbox_x, bbox_y, bbox_w, bbox_h float Bounding box
confidence float 0–1 template match confidence
speed float Speed in pixels/second
hits, misses int Track quality counters
reason str Internal tracking decision label
target_id int Stable selected-target id, same as id
anchor_appearance float Appearance similarity to the immutable initialization anchor
suppressed bool True when this target coasts due to ambiguous ownership or another target owning the observation
suppressed_by_id int Owning target id, or 0 for an ambiguous claim / no owner
interacting bool True while this target is in a guarded close interaction
model_learning_suppressed bool True when appearance and correlation learning are frozen
geometry_update_suppressed bool True when bbox width/height adaptation is frozen
geometry_ownership_constrained bool True when a geometry measurement was clipped to the target's ownership cell
geometry_confidence float Owned geometry measurement confidence, or 0 when no measurement was available
prediction_only bool True when bbox/center are a tentative velocity coast, not an accepted observation
bbox() tuple (x, y, w, h) shorthand
center() tuple (cx, cy) shorthand
annotate() ndarray Copy of the frame with cyan HUD drawn

Prediction-only results never update appearance or geometry. Treat them as a short-gap display/prediction signal, not as a confirmed image observation.


7. Writing a CSV from Python

import csv

with open("results.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["frame", "state", "cx", "cy", "confidence"])
    for i in range(120):
        ok, frame = cap.read()
        if not ok:
            break
        r = tracker.update(frame, dt)
        writer.writerow([r.frame, r.state, r.cx, r.cy, r.confidence])

8. Live camera input

For USB/V4L2 cameras, pass an integer camera id to OpenCV and keep the tracker API unchanged:

import cv2
import sky_tracker

cap = cv2.VideoCapture(0)
ok, frame = cap.read()
assert ok, "could not read camera frame"

bbox = cv2.selectROI("select target", frame, fromCenter=False)
cv2.destroyAllWindows()

tracker = sky_tracker.Tracker("default")
tracker.lock(frame, bbox)

while True:
    ok, frame = cap.read()
    if not ok:
        break
    result = tracker.update(frame)
    cv2.imshow("sky_tracker", result.annotate())
    if cv2.waitKey(1) == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()

On Raspberry Pi OS 64-bit, install the Linux ARM64 wheel and use the same frame-by-frame API. If the official Pi camera is not available as /dev/video0, use Picamera2 to capture BGR frames and pass those frames to tracker.update(...).


9. Sending evaluation results back

Share your CSV and any annotated frames with us. The reason field in each row is especially useful for diagnosing tracking failures.


Known limits (evaluation build)

  • The Python module is a .pyd / .so binary built against a specific OpenCV version. If you see import errors, ensure opencv-python matches the version used to build the module.
  • On Windows, call os.add_dll_directory(MODULE_DIR) before import sky_tracker so the loader finds OpenCV DLLs next to the module.
  • annotate() returns a full-resolution copy of the frame — avoid calling it on every frame in throughput-critical paths.
  • The licence key is checked when Tracker() is constructed. If the key expires mid-session the current tracker instance keeps running; the next Tracker() construction will fail.
  • A 7-day trial key (SKT2.…) is also confirmed with the licence server over HTTPS when Tracker() is first constructed in a process, so a trial needs an internet connection and 1.1.1 or later. Bought and student keys never contact the network.