Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
7fd8ec8
ocsort with piotr's review changes
AlexBodner Jan 29, 2026
01cd73d
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jan 29, 2026
481de00
trackers/core/ocsort
AlexBodner Jan 29, 2026
3a90628
fixed pre commit
AlexBodner Jan 29, 2026
81203f7
fixed pre commit
AlexBodner Jan 29, 2026
27a2322
Merge branch 'feat/core/ocsort-release' of https://github.com/roboflo…
AlexBodner Jan 29, 2026
56bd50b
trying original kalman filter
AlexBodner Jan 30, 2026
8598547
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jan 30, 2026
e153183
trying fix
AlexBodner Jan 30, 2026
3cfb483
Merge branch 'feat/core/ocsort-release' of https://github.com/roboflo…
AlexBodner Jan 30, 2026
0db6ab1
trying fix
AlexBodner Jan 30, 2026
c6e2fb0
trying fix
AlexBodner Jan 30, 2026
03faa93
added F initialization
AlexBodner Jan 30, 2026
d16ecfb
possible fix by transforming cosine_sim
AlexBodner Feb 5, 2026
81fed51
changed indexing fro initializing new tracklets when ocr unmatched an…
AlexBodner Feb 5, 2026
fa42061
now in frames before minimum consecutive frames we output an id
AlexBodner Feb 5, 2026
f801c78
now in frames before minimum consecutive frames we output an id
AlexBodner Feb 5, 2026
457841c
now in frames before minimum consecutive frames we output an id
AlexBodner Feb 5, 2026
e02996a
bug fix regarding last commits
AlexBodner Feb 6, 2026
25e6f5a
bug fix regarding last commits
AlexBodner Feb 6, 2026
ac1b490
possbile fix by activating track id when just matured
AlexBodner Feb 6, 2026
89de476
now doing batch calculation of direction consistency, faster tracking
AlexBodner Feb 6, 2026
bc74315
nice docs in utils
AlexBodner Feb 6, 2026
ac3258f
trying independence on original KF
AlexBodner Feb 6, 2026
2625c2d
fixed pre commit, deleted 2nd kalman filter
AlexBodner Feb 9, 2026
475c07b
added XYXY and XCTCSR possible reps
AlexBodner Feb 9, 2026
146907d
code looking good
AlexBodner Feb 9, 2026
e94b207
added metrics
AlexBodner Feb 9, 2026
517b1e1
trying interpolation in xyxy system
AlexBodner Feb 10, 2026
0da45f5
rollback interpolation in xyxy system
AlexBodner Feb 10, 2026
0480a18
kf annotations fix and removed example
AlexBodner Feb 11, 2026
82c7345
merge develop
AlexBodner Feb 11, 2026
7e2cfaf
added oc-sort final numbers
AlexBodner Feb 11, 2026
2cfd0e7
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Feb 11, 2026
990bdd2
readme with final numbers, utils with _ and ocsort in docs/api
AlexBodner Feb 11, 2026
6e6f309
Merge branch 'feat/core/ocsort-release' of https://github.com/roboflo…
AlexBodner Feb 11, 2026
cf5ae7e
readme with final numbers, utils with _ and ocsort in docs/api
AlexBodner Feb 11, 2026
80753fb
added headers and changed parameters name high_conf_det_threshold -> …
AlexBodner Feb 11, 2026
8e07a25
rollback parameter name changed
AlexBodner Feb 11, 2026
a967b61
removed typings from docstring
AlexBodner Feb 11, 2026
be508ab
added change from PR to prevent in place mutation:
AlexBodner Feb 12, 2026
58f793e
trying to add delta_t
AlexBodner Feb 16, 2026
f1ac163
trying fix to delta_t: velocity not computed until second match, fram…
AlexBodner Feb 17, 2026
44ec83d
reverted <= change
AlexBodner Feb 17, 2026
fc8a2fd
aligned to original initialization of time_since_update and changes i…
AlexBodner Feb 17, 2026
154c830
added headers to utils
AlexBodner Feb 19, 2026
c506b72
Merge branch 'develop' into feat/core/ocsort-release
AlexBodner Feb 20, 2026
09a62cb
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Feb 20, 2026
2f593e1
modular state representation
AlexBodner Feb 20, 2026
fd68de9
Merge branch 'feat/core/ocsort-release' of https://github.com/roboflo…
AlexBodner Feb 23, 2026
4b9bfba
changed state representations to KF classes
AlexBodner Feb 23, 2026
c9ef80b
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Feb 23, 2026
b6f1d2a
Merge branch 'develop' into feat/core/ocsort-release
SkalskiP Feb 23, 2026
03b6c0e
register OCSORTTracker for CLI and public API
SkalskiP Feb 23, 2026
7b66fe9
remove duplicate headers and forbidden comments in utils
SkalskiP Feb 23, 2026
765f1ba
Use mkdocstrings-compatible single backticks instead of Sphinx-style
SkalskiP Feb 23, 2026
31b5be1
wrap OCSORTTracker docstring to fit 88-char line limit
SkalskiP Feb 24, 2026
39c7fd3
docs: improve docstrings for mkdocs compatibility and clarity
SkalskiP Feb 24, 2026
4abffc6
- Add functions to trackers/__init__.py exports
SkalskiP Feb 24, 2026
ee5fb54
Applying changes requested by Piotr
AlexBodner Feb 24, 2026
cce1f47
Applying changes requested by Piotr
AlexBodner Feb 24, 2026
81b34db
Fixed pre commit
AlexBodner Feb 24, 2026
7a95de0
try removing activate tracklets in activate_or_kill_tracklets as it s…
AlexBodner Feb 24, 2026
ae1fc86
completely corrected activate_or_kill_tracklets to kill_tracklets aft…
AlexBodner Feb 24, 2026
b35e353
changed BaseKalmanFilter to BaseStateEstimator and same for other typ…
AlexBodner Feb 24, 2026
081c4e2
initial commit adding abstract BaseTraclet and inheriting it in ocsor…
AlexBodner Feb 25, 2026
d7ac41d
sort might be done
AlexBodner Feb 25, 2026
2eb7f19
tracker changed by tracklet in byte
AlexBodner Feb 25, 2026
e1ba0e8
before merge
AlexBodner Feb 26, 2026
390e628
Merge remote-tracking branch 'origin/develop' into feat/core/refactor…
AlexBodner Feb 26, 2026
ef048d5
possible optimization by copying detections only once per update
AlexBodner Feb 26, 2026
1908f32
changes from merge
AlexBodner Feb 26, 2026
7dc0a2f
merged piotr optimizations
AlexBodner Mar 3, 2026
5704ce4
version with tracklets ready to test, bytetrack values might change b…
AlexBodner Mar 3, 2026
8a78611
small bug fix in sort, passed detections instead of confidence
AlexBodner Mar 4, 2026
8f9cc44
trying oc-sort style initialization in bytetrack
AlexBodner Mar 4, 2026
b33f3d1
rollback trying oc-sort style initialization in bytetrack, now it use…
AlexBodner Mar 4, 2026
bb3199e
aligned format
AlexBodner Mar 5, 2026
50e5117
default in sort is XYXY now
AlexBodner Mar 10, 2026
9a41629
now sort tracklets initialize number_of_successful_consecutive_updates
AlexBodner Mar 10, 2026
6207d09
initializing parameters like old version
AlexBodner Mar 10, 2026
8adae83
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Mar 10, 2026
1428b73
Now SORT uses number_of_succesful_updates (not consecutive)
AlexBodner Mar 10, 2026
2f398e6
Now SORT uses number_of_succesful_updates (not consecutive)
AlexBodner Mar 10, 2026
ae46434
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Mar 10, 2026
9369c6b
Now SORT uses number_of_succesful_updates (not consecutive)
AlexBodner Mar 10, 2026
e7e5663
Merge branch 'feat/core/refactor-tracklet-kalman-filter' of https://g…
AlexBodner Mar 10, 2026
c2f2daf
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Mar 10, 2026
3c54462
fix bytetrack broken because of usage of sort utility
AlexBodner Mar 11, 2026
56975d1
Merge branch 'feat/core/refactor-tracklet-kalman-filter' of https://g…
AlexBodner Mar 11, 2026
ea622dc
Merge remote-tracking branch 'origin/develop' into feat/core/refactor…
AlexBodner Mar 12, 2026
d849ab8
updated numbers
AlexBodner Mar 12, 2026
6ed1c8b
Merge remote-tracking branch 'origin/develop' into feat/core/refactor…
AlexBodner Mar 13, 2026
c22babb
changed kalman filter name in tracklet to state_estimator
AlexBodner Mar 13, 2026
668cf30
added first version of docs for custom kalmans
AlexBodner Mar 13, 2026
15e95ba
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Mar 13, 2026
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
10 changes: 10 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,14 @@ Try trackers in your browser with our [Hugging Face Playground](https://huggingf

[:simple-googlecolab: Run Google Colab](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-track-objects-with-bytetrack-tracker.ipynb)

- **How to Track Objects with OC-SORT**

---

[![](url-to-image)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-track-objects-with-ocsort-tracker.ipynb)

End-to-end example showing how to run RF-DETR detection with the OC-SORT tracker.

[:simple-googlecolab: Run Google Colab](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-track-objects-with-ocsort-tracker.ipynb)

</div>
12 changes: 12 additions & 0 deletions docs/javascripts/mathjax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
window.MathJax = {
tex: {
inlineMath: [["\\(", "\\)"], ["$", "$"]],
displayMath: [["\\[", "\\]"], ["$$", "$$"]],
processEscapes: true,
processEnvironments: true,
},
options: {
ignoreHtmlClass: "^((?!arithmatex).)*$",
processHtmlClass: "arithmatex",
},
};
240 changes: 240 additions & 0 deletions docs/learn/state-estimators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
# State Estimators

Every tracker in `trackers` uses a Kalman filter to predict where objects will appear in the next frame. The **state estimator** controls how bounding boxes are represented inside that filter. Different representations make different assumptions about object motion, and picking the right one can improve tracking quality without changing anything else.

**What you'll learn:**

- What state estimators are and why they matter
- How `XYXYStateEstimator` and `XCYCSRStateEstimator` represent bounding boxes
- When to use each representation
- How to swap the state estimator in any tracker

---

## Install

Get started by installing the package.

```text
pip install trackers
```

For more options, see the [install guide](install.md).

---

## What Is a State Estimator?

A state estimator wraps a Kalman filter and defines how bounding boxes are encoded into the filter's state vector. The Kalman filter then predicts the next position of each tracked object and corrects that prediction when a new detection arrives.

Two representations are available:

| Estimator | State Dimensions | Representation | Aspect Ratio |
| :--------------------: | :--------------: | :---------------------------------------------------- | :-----------: |
| `XYXYStateEstimator` | 8 | Top-left and bottom-right corners + their velocities | Can change |
| `XCYCSRStateEstimator` | 7 | Center point, area, their velocities and aspect ratio | Held constant |

They accept `[x1, y1, x2, y2]` bounding boxes on input and produce `[x1, y1, x2, y2]` bounding boxes on output. The difference is entirely in how the filter models motion internally.

---

## XYXY — Corner-Based

`XYXYStateEstimator` tracks the four corner coordinates independently. Each corner gets its own velocity term, giving the filter 8 state variables:

```
State: [x1, y1, x2, y2, vx1, vy1, vx2, vy2]
Measure: [x1, y1, x2, y2]
```

The transition matrix $F$ defines how the state evolves from one frame to the next.

State order: $[x_1, y_1, x_2, y_2, v_{x_1}, v_{y_1}, v_{x_2}, v_{y_2}]$

$$
F =
\begin{bmatrix}
1 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 & 0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 & 0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 & 0 & 0 & 0 & 1 \\
0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\
0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\
0 & 0 & 0 & 0 & 0 & 0 & 0 & 1
\end{bmatrix}
$$

Equivalent update equations:

```text
x1' = x1 + vx1
y1' = y1 + vy1
x2' = x2 + vx2
y2' = y2 + vy2
vx1' = vx1
vy1' = vy1
vx2' = vx2
vy2' = vy2
```

| Row | Meaning |
| :-- | :------------------------------------------------------- |
| 1-4 | Each corner coordinate is updated by adding its velocity |
| 5-8 | Velocities persist unchanged from frame to frame |

Because each corner moves freely, the box width and height can change between frames. This makes XYXY a natural fit when objects change shape — due to camera perspective, non-rigid motion, or inconsistent detections.

**In Trackers, this is the default** for `ByteTrackTracker` and `SORTTracker`.

---

## XCYCSR — Center-Based

`XCYCSRStateEstimator` tracks the box center, area (scale), and aspect ratio. Only the center and scale get velocity terms; aspect ratio is treated as constant. This gives 7 state variables:

```
State: [x_center, y_center, scale, aspect_ratio, vx, vy, vs]
Measure: [x_center, y_center, scale, aspect_ratio]
```

The transition matrix $F$ shows the key difference: the aspect ratio is propagated without a velocity term.

State order: $[x_c, y_c, s, r, v_x, v_y, v_s]$

$$
F =
\begin{bmatrix}
1 & 0 & 0 & 0 & 1 & 0 & 0 \\
0 & 1 & 0 & 0 & 0 & 1 & 0 \\
0 & 0 & 1 & 0 & 0 & 0 & 1 \\
0 & 0 & 0 & 1 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 & 1 & 0 & 0 \\
0 & 0 & 0 & 0 & 0 & 1 & 0 \\
0 & 0 & 0 & 0 & 0 & 0 & 1
\end{bmatrix}
$$

Equivalent update equations:

```text
x_center' = x_center + vx
y_center' = y_center + vy
scale' = scale + vs
aspect_ratio' = aspect_ratio
vx' = vx
vy' = vy
vs' = vs
```

| Row | Meaning |
| :-- | :-------------------------------------------------------- |
| 1-3 | Center position and scale follow constant-velocity motion |
| 4 | Aspect ratio is copied forward unchanged |
| 5-7 | Velocities persist unchanged from frame to frame |

The aspect ratio `r = w / h` is carried forward unchanged. This acts as a regularizer — the filter resists sudden shape changes. It works well for rigid objects whose proportions stay consistent, like pedestrians walking or cars on a highway.

**This is the default** for `OCSORTTracker`, matching the original OC-SORT paper.

---

## When to Use Each

| Scenario | Recommended | Why |
| :------------------------------------------- | :--------------------: | :--------------------------------------------------------- |
| Pedestrians, vehicles, rigid objects | `XCYCSRStateEstimator` | Constant aspect ratio stabilizes predictions |
| Non-rigid or deformable objects | `XYXYStateEstimator` | Corners move independently to track shape changes |
| Noisy detections with fluctuating box sizes | `XCYCSRStateEstimator` | Aspect ratio constraint absorbs size noise |
| Strong perspective changes (camera pan/zoom) | `XYXYStateEstimator` | Box proportions shift with viewpoint; corners adapt freely |
| Default choice when unsure | `XYXYStateEstimator` | More general, fewer assumptions |

---

## Swapping the Estimator

All trackers accept a `state_estimator_class` parameter. Import the class you want and pass it to the constructor.

=== "ByteTrack with XCYCSR"

```python
from trackers import ByteTrackTracker
from trackers.utils.state_representations import XCYCSRStateEstimator

tracker = ByteTrackTracker(
state_estimator_class=XCYCSRStateEstimator,
)
```

=== "OC-SORT with XYXY"

```python
from trackers import OCSORTTracker
from trackers.utils.state_representations import XYXYStateEstimator

tracker = OCSORTTracker(
state_estimator_class=XYXYStateEstimator,
)
```

=== "SORT with XCYCSR"

```python
from trackers import SORTTracker
from trackers.utils.state_representations import XCYCSRStateEstimator

tracker = SORTTracker(
state_estimator_class=XCYCSRStateEstimator,
)
```

Everything else stays the same — detection, association, and visualization work identically regardless of which estimator you choose.

---

## Full Example

Run ByteTrack with both estimators on the same video and compare the results side by side.

```python
import cv2

import supervision as sv
from inference import get_model
from trackers import ByteTrackTracker
from trackers.utils.state_representations import (
XCYCSRStateEstimator,
XYXYStateEstimator,
)

model = get_model("rfdetr-nano")

tracker_xyxy = ByteTrackTracker(
state_estimator_class=XYXYStateEstimator,
)
tracker_xcycsr = ByteTrackTracker(
state_estimator_class=XCYCSRStateEstimator,
)

cap = cv2.VideoCapture("source.mp4")
while True:
ret, frame = cap.read()
if not ret:
break

result = model.infer(frame)[0]
detections = sv.Detections.from_inference(result)

tracked_xyxy = tracker_xyxy.update(detections.copy())
tracked_xcycsr = tracker_xcycsr.update(detections.copy())

# Compare tracker_id assignments, box smoothness, etc.
print(f"XYXY IDs: {tracked_xyxy.tracker_id}")
print(f"XCYCSR IDs: {tracked_xcycsr.tracker_id}")
```

---

## Takeaway

The state estimator is a single-line change that controls how the Kalman filter models bounding box motion. Use `XCYCSRStateEstimator` when objects keep a consistent shape, and `XYXYStateEstimator` when shape varies or you want fewer assumptions. Try it on your case, the best choice depends on the scene.
10 changes: 5 additions & 5 deletions docs/trackers/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Sports broadcast tracking with fast motion, camera pans, and similar-looking tar

| Tracker | HOTA | IDF1 | MOTA |
| :-------: | :------: | :------: | :------: |
| SORT | 70.9 | 68.9 | 95.7 |
| SORT | 70.8 | 68.9 | 95.5 |
| ByteTrack | **73.0** | **72.5** | **96.4** |
| OC-SORT | 71.7 | 71.4 | 95.0 |

Expand Down Expand Up @@ -152,7 +152,7 @@ Long sequences with dense interactions and partial occlusions. Tests long-term I
| Tracker | HOTA | IDF1 | MOTA |
| :-------: | :------: | :------: | :------: |
| SORT | **84.2** | **78.2** | **98.2** |
| ByteTrack | 84.0 | 78.1 | 97.8 |
| ByteTrack | 84.0 | 78.1 | **98.2** |
| OC-SORT | 82.9 | 77.9 | 96.8 |

Tuned configuration for each tracker.
Expand All @@ -166,9 +166,9 @@ Long sequences with dense interactions and partial occlusions. Tests long-term I

ByteTrack:
lost_track_buffer: 30
track_activation_threshold: 0.5
minimum_consecutive_frames: 2
minimum_iou_threshold: 0.1
track_activation_threshold: 0.2
minimum_consecutive_frames: 1
minimum_iou_threshold: 0.05
high_conf_det_threshold: 0.5

OC-SORT:
Expand Down
6 changes: 6 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ extra_css:

extra_javascript:
- javascripts/pycon_copy.js
- javascripts/mathjax.js
- https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js
- javascripts/cli_builder_framework.js
- javascripts/command_builder.js

Expand All @@ -75,6 +77,9 @@ markdown_extensions:
pygments_lang_class: true
# Enables inline code highlighting
- pymdownx.inlinehilite
# Enables LaTeX-style math in Markdown
- pymdownx.arithmatex:
generic: true
# Allows including content from other files
- pymdownx.snippets
# Enables nested code blocks and custom fences
Expand Down Expand Up @@ -120,6 +125,7 @@ nav:
- Download Datasets: learn/download.md
- Evaluate Trackers: learn/evaluate.md
- Detection Quality Matters: learn/detection-quality.md
- State Estimators: learn/state-estimators.md
- Trackers:
- Comparison: trackers/comparison.md
- SORT: trackers/sort.md
Expand Down
Loading
Loading