Skip to content
Draft
Show file tree
Hide file tree
Changes from 88 commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
d2e524e
ADD: Added KSPTracker for offline trackering
Ashp116 Jun 16, 2025
b83da07
ADD: Added IOU calcuations of bboxes
Ashp116 Jun 16, 2025
53b8c91
ADD: Added a function to build a directed graph from detections
Ashp116 Jun 16, 2025
cb557cb
ADD: Added K-Shortest Paths algorithm
Ashp116 Jun 16, 2025
b082cd0
ADD: Added a function to process tracks from source to sink
Ashp116 Jun 16, 2025
0c260e3
BUG: Fixed the reset function
Ashp116 Jun 16, 2025
1adc1cf
BUG: Fixed IOU calculation, can_connected_nodes(), and update_detecti…
Ashp116 Jun 16, 2025
f24c25a
FIX: Fixes for bugs found for the KSP tracker issues during testing
Ashp116 Jun 16, 2025
1aba6c2
MISC: Added comments for functions and fixed formatting
Ashp116 Jun 17, 2025
db52617
MISC: Removed unused imports
Ashp116 Jun 17, 2025
31b5a5b
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 17, 2025
206d532
MISC: Fixed formatting
Ashp116 Jun 17, 2025
0544904
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 17, 2025
f5dcaf3
MISC: Add type annotation for pre-commit
Ashp116 Jun 17, 2025
9d5d37d
MISC: Add networkx to dependency
Ashp116 Jun 19, 2025
0452a2f
FIX: Switch to sv.box_iou_batch
Ashp116 Jun 19, 2025
f3b849b
FIX: process_tracks returned None
Ashp116 Jun 19, 2025
614b2fb
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 19, 2025
0e6bc41
UPDATE: Updated the way the directed acyclic graph is built
Ashp116 Jun 19, 2025
f82a112
UPDATE: Updated process_tasks
Ashp116 Jun 19, 2025
0543291
FIX: Removed the width and height parameters
Ashp116 Jun 19, 2025
1235657
FIX: Updated xyxy array type
Ashp116 Jun 19, 2025
c85e0b8
FIX: Fixes after testing
Ashp116 Jun 19, 2025
49a6b2e
UPDATE: Update KSP
Ashp116 Jun 19, 2025
1f965e6
ADD: Added Docstrings
Ashp116 Jun 19, 2025
1c0626a
Merge branch 'feature/offline-tracker-ksp' into fix/ksp-tracker
Ashp116 Jun 19, 2025
3ab72d9
Merge pull request #1 from Ashp116/fix/ksp-tracker
Ashp116 Jun 19, 2025
8f9e00b
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 19, 2025
0e37b38
FIX: precommit fix
Ashp116 Jun 19, 2025
cc1f5d7
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 19, 2025
1f43fc5
UPDATE: Made process_tracks return list of detections
Ashp116 Jun 19, 2025
2cf26d6
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 19, 2025
11b68a4
UPDATE: removed all unused occurrences of 'max_gap'
Ashp116 Jun 19, 2025
6b8fbc3
UPDATE: Updated KSP solver
Ashp116 Jun 20, 2025
5134e5e
ADD: Docstrings
Ashp116 Jun 20, 2025
c0a0269
UPDATE: Updated graph to be a member variable
Ashp116 Jun 20, 2025
31715af
UPDATE: Docstrings
Ashp116 Jun 20, 2025
c8535ff
BUG: Diagnosing untracked detections
Ashp116 Jun 20, 2025
35404f2
BUG: Debugging KSP thru visuals
Ashp116 Jun 21, 2025
01c8cf1
BUG: Bellman Ford path visualization
Ashp116 Jun 22, 2025
e113c24
BUG: Same edge weights
Ashp116 Jun 22, 2025
0aaec74
BUG: Debugging KSP...
Ashp116 Jun 23, 2025
764c776
PROGRESS: Able to get less tracer switchs
Ashp116 Jun 23, 2025
ccd77af
CHECKPOINT: Changing the entirely
Ashp116 Jun 25, 2025
af750b6
UPDATE: Changed the entire logic for KSP
Ashp116 Jun 25, 2025
c764107
FIX: Testing and changing got something relative to expectation?
Ashp116 Jun 28, 2025
1113154
MISC: Clean up!
Ashp116 Jun 28, 2025
daa90dc
UPDATE: Tracker with the least change in detection position track a d…
Ashp116 Jun 28, 2025
4e79595
MISC: Docstrings
Ashp116 Jun 28, 2025
1a3a37a
Merge branch 'roboflow:main' into fix/ksp-disjoints
Ashp116 Jun 28, 2025
253a158
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 28, 2025
ab49445
Merge pull request #2 from Ashp116/fix/ksp-disjoints
Ashp116 Jun 28, 2025
23672e4
MISC: Precommit
Ashp116 Jun 28, 2025
13f2de2
MISC: Precommit
Ashp116 Jun 28, 2025
18e52c9
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 28, 2025
704f476
MISC: Precommit
Ashp116 Jun 28, 2025
077167b
Merge branch 'feature/offline-tracker-ksp' of https://github.com/Ashp…
Ashp116 Jun 28, 2025
789d8cf
MISC: Precommit
Ashp116 Jun 28, 2025
a23cf66
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 28, 2025
2aa2e0a
UPDATE: Added num_of_tracks param
Ashp116 Jun 28, 2025
dd2b4ff
Merge branch 'feature/offline-tracker-ksp' of https://github.com/Ashp…
Ashp116 Jun 28, 2025
9b70a31
UPDATE: Added tqdm and small changes
Ashp116 Jul 1, 2025
12b924a
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 1, 2025
b6ce7a2
UPDATE: Changes reflecting the comments from the code review
Ashp116 Jul 2, 2025
286f946
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 2, 2025
89e792f
Pre-commit
Ashp116 Jul 2, 2025
9aadfa8
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 2, 2025
2de57cc
Debug: Debugging the KSP solve
Ashp116 Jul 2, 2025
04dc790
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 2, 2025
23efbe9
Debug: Debugging the KSP ith itr
Ashp116 Jul 2, 2025
47066b7
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 2, 2025
aa5bae4
Debug: Debugging the KSP solve
Ashp116 Jul 2, 2025
589c1dc
Debug: Change base penalty
Ashp116 Jul 2, 2025
9c7b994
UPDATE: Changed base_penalty to path_overlap_penalty hyper params
Ashp116 Jul 2, 2025
69bc783
UPDATE: Docstrings
Ashp116 Jul 2, 2025
203d936
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 2, 2025
eb61e98
Pre-commit
Ashp116 Jul 2, 2025
e5397ad
FIX: process_tracks docstrings
Ashp116 Jul 2, 2025
048de8c
FIX: process_tracks docstrings
Ashp116 Jul 2, 2025
7dcc03c
Pre-commit check
Ashp116 Jul 2, 2025
97eda16
Pre-commit
Ashp116 Jul 2, 2025
4a14d05
UPDATE: Updates referring to the code review
Ashp116 Jul 3, 2025
44d78aa
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 3, 2025
875acd5
Pre-commit
Ashp116 Jul 3, 2025
19fad3b
ADD: Added documentation and changed folder name
Ashp116 Jul 4, 2025
e6020c4
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 4, 2025
b80eca7
OOPS: The ksp files were not added
Ashp116 Jul 4, 2025
d82f341
UPDATE: Updated mkdocs
Ashp116 Jul 4, 2025
6fe4324
UPDATE: Changes for code review
Ashp116 Jul 5, 2025
0a6869d
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 5, 2025
35c06e0
ADD: Added doors
Ashp116 Jul 9, 2025
ee98e51
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 9, 2025
ceb0218
ADD: Added customizable doors and frame doors
Ashp116 Jul 9, 2025
1bf7d8e
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 9, 2025
f521b47
UPDATE: Removed debug path lens
Ashp116 Jul 9, 2025
8f1cc52
UPDATE: Docstrings
Ashp116 Jul 9, 2025
ab51903
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 9, 2025
1ecd0f3
Precommit
Ashp116 Jul 9, 2025
297c919
Merge branch 'feature/offline-tracker-ksp' of https://github.com/Ashp…
Ashp116 Jul 9, 2025
55eed45
Precommit
Ashp116 Jul 9, 2025
5029b88
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 9, 2025
b62d140
Precommit
Ashp116 Jul 9, 2025
784fbb8
UPDATE: Updated docs
Ashp116 Jul 9, 2025
35cea66
UPDATE: Editable hyper parameters for entry and exit costs
Ashp116 Jul 13, 2025
7eeb8b8
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 13, 2025
b8d8289
BUG: Debugging the detections vs tracker Id
Ashp116 Jul 13, 2025
a9866bd
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jul 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions docs/trackers/core/ksp/tracker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
---
comments: true
---

# KSP

[![IEEE](https://img.shields.io/badge/IEEE-10.1109/TPAMI.2011.21-blue.svg)](https://doi.org/10.1109/TPAMI.2011.21)
[![PDF (Unofficial)](https://img.shields.io/badge/PDF-Stanford--Preprint-red.svg)](http://vision.stanford.edu/teaching/cs231b_spring1415/papers/Berclaz-tracking.pdf)
[![colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-track-objects-with-sort-tracker.ipynb)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix this colab link. Can you please raise a corresponding PR on roboflow/notebooks?


## Overview

**KSP Tracker** (K-Shortest Paths Tracker) is an offline, tracking-by-detection method that formulates multi-object tracking as a global optimization problem over a directed graph. Each object detection is represented as a node, and feasible transitions between detections are modeled as edges weighted by spatial and temporal consistency. By solving a K-shortest paths problem, the tracker extracts globally optimal trajectories that span the entire sequence.

Unlike online trackers, which make frame-by-frame decisions, KSP Tracker leverages the full temporal context of a video to achieve greater robustness against occlusions, missed detections, and fragmented tracks. This makes it especially suitable for applications where high tracking accuracy is required, such as surveillance review, sports analytics, or autonomous system evaluation. However, the reliance on global optimization introduces higher computational cost and requires access to the full sequence before tracking can be performed.

## Examples

=== "inference"

```python hl_lines="2 6 11 15"
import supervision as sv
from trackers import KSPTracker
from inference import get_model
import numpy as np

tracker = KSPTracker()
model = get_model(model_id="yolo11x")
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.TOP_LEFT)

def get_model_detections(frame: np.ndarray):
result = model.infer(frame)[0]
return sv.Detections.from_inference(result)

tracked_dets = tracker.track(
source_path="<INPUT_VIDEO_PATH>",
get_model_detections=get_model_detections
)

frame_idx_to_dets = {i: tracked_dets[i] for i in range(len(tracked_dets))}

def annotate_frame(frame: np.ndarray, i: int) -> np.ndarray:
detections = frame_idx_to_dets.get(i, sv.Detections.empty())
detections.tracker_id = detections.tracker_id or np.zeros(len(detections), dtype=int)
labels = [f"{tid}" for tid in detections.tracker_id]
ann = box_annotator.annotate(frame.copy(), detections)
return label_annotator.annotate(ann, detections, labels=labels)

sv.process_video(
source_path="<INPUT_VIDEO_PATH>",
target_path="<OUTPUT_VIDEO_PATH>",
callback=annotate_frame,
)
```

=== "rf-detr"

```python hl_lines="2 6 11 14"
import supervision as sv
from trackers import KSPTracker
from rfdetr import RFDETRBase
import numpy as np

tracker = KSPTracker()
model = RFDETRBase()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.TOP_LEFT)

def get_model_detections(frame: np.ndarray):
return model.predict(frame)

tracked_dets = tracker.track(
source_path="<INPUT_VIDEO_PATH>",
get_model_detections=get_model_detections
)

frame_idx_to_dets = {i: tracked_dets[i] for i in range(len(tracked_dets))}

def annotate_frame(frame: np.ndarray, i: int) -> np.ndarray:
detections = frame_idx_to_dets.get(i, sv.Detections.empty())
detections.tracker_id = detections.tracker_id or np.zeros(len(detections), dtype=int)
labels = [f"{tid}" for tid in detections.tracker_id]
ann = box_annotator.annotate(frame.copy(), detections)
return label_annotator.annotate(ann, detections, labels=labels)

sv.process_video(
source_path="<INPUT_VIDEO_PATH>",
target_path="<OUTPUT_VIDEO_PATH>",
callback=annotate_frame,
)
```

=== "ultralytics"

```python hl_lines="2 6 11 16"
import supervision as sv
from trackers import KSPTracker
from ultralytics import YOLO
import numpy as np

tracker = KSPTracker()
model = YOLO("yolo11m.pt")
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.TOP_LEFT)

def get_model_detections(frame: np.ndarray):
result = model(frame, imgsz=1280, verbose=False)[0]
detections = sv.Detections.from_ultralytics(result)
return detections[detections.class_id == 0] if not detections.is_empty() else detections

tracked_dets = tracker.track(
source_path="<INPUT_VIDEO_PATH>",
get_model_detections=get_model_detections
)

frame_idx_to_dets = {i: tracked_dets[i] for i in range(len(tracked_dets))}

def annotate_frame(frame: np.ndarray, i: int) -> np.ndarray:
detections = frame_idx_to_dets.get(i, sv.Detections.empty())
detections.tracker_id = detections.tracker_id or np.zeros(len(detections), dtype=int)
labels = [f"{tid}" for tid in detections.tracker_id]
ann = box_annotator.annotate(frame.copy(), detections)
return label_annotator.annotate(ann, detections, labels=labels)

sv.process_video(
source_path="<INPUT_VIDEO_PATH>",
target_path="<OUTPUT_VIDEO_PATH>",
callback=annotate_frame,
)
```

=== "transformers"

```python hl_lines="3 7 13 27"
import torch
import supervision as sv
from trackers import KSPTracker
from transformers import RTDetrV2ForObjectDetection, RTDetrImageProcessor
import numpy as np

tracker = KSPTracker()
processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_v2_r18vd")
model = RTDetrV2ForObjectDetection.from_pretrained("PekingU/rtdetr_v2_r18vd")
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.TOP_LEFT)

def get_model_detections(frame: np.ndarray):
inputs = processor(images=frame, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)

h, w, _ = frame.shape
results = processor.post_process_object_detection(
outputs,
target_sizes=torch.tensor([(h, w)]),
threshold=0.5
)[0]

return sv.Detections.from_transformers(results, id2label=model.config.id2label)

tracked_dets = tracker.track(
"<INPUT_VIDEO_PATH>",
get_model_detections=get_model_detections
)

frame_idx_to_dets = {i: tracked_dets[i] for i in range(len(tracked_dets))}

def annotate_frame(frame: np.ndarray, i: int) -> np.ndarray:
detections = frame_idx_to_dets.get(i, sv.Detections.empty())
detections.tracker_id = detections.tracker_id or np.zeros(len(detections), dtype=int)
labels = [f"{tid}" for tid in detections.tracker_id]
ann = box_annotator.annotate(frame.copy(), detections)
return label_annotator.annotate(ann, detections, labels=labels)

sv.process_video(
source_path="<INPUT_VIDEO_PATH>",
target_path="<OUTPUT_VIDEO_PATH>",
callback=callback,
)
```

## API

::: trackers.core.ksp.tracker.KSPTracker
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,5 @@ nav:
- Trackers:
- SORT: trackers/core/sort/tracker.md
- DeepSORT: trackers/core/deepsort/tracker.md
- KSP: trackers/core/ksp/tracker.md
- ReID: trackers/core/reid/reid.md
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ metrics = [
"tensorboard>=2.19.0",
"wandb>=0.19.11",
]
ksptracker = [
"networkx>=3.2.1",
]

[dependency-groups]
dev = [
Expand Down
16 changes: 16 additions & 0 deletions trackers/core/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from typing import Callable, List, Optional

import numpy as np
import supervision as sv
Expand All @@ -22,3 +23,18 @@ def update(self, detections: sv.Detections, frame: np.ndarray) -> sv.Detections:
@abstractmethod
def reset(self) -> None:
pass


class BaseOfflineTracker(ABC):
@abstractmethod
def reset(self) -> None:
pass

@abstractmethod
def track(
self,
source_path: str,
get_model_detections: Callable[[np.ndarray], sv.Detections],
num_of_tracks: Optional[int] = None,
) -> List[sv.Detections]:
pass
Empty file added trackers/core/ksp/__init__.py
Empty file.
Loading