Real-time multi-object tracking for Rust
Disclaimer: This is an unofficial Rust port of Python's norfair object tracking library. This project is NOT affiliated with, endorsed by, or associated with Tryolabs or the original norfair development team. All credit for the original design and algorithms goes to the original norfair authors.
norfair-rs is a Rust implementation of the norfair multi-object tracking library, bringing real-time object tracking capabilities to Rust applications with:
- Detector-agnostic design: Works with any object detector (YOLO, Faster R-CNN, custom models)
- Rust-native performance: Zero-cost abstractions, no GC, maximum speed
- Type-safe API: Compile-time validation of tracking configurations
- Comprehensive Tests: 289+ Rust tests + 98 Python tests ensuring correctness and equivalence with the original norfair library
- Drop-In-Replacement: Python bindings with
uv add norfair_rsandimport norfair_rs as norfair
- norfair - Original Python implementation by Tryolabs
- norfair-go - Go port of norfair (sibling project)
- Flexible Distance Functions: IoU, Euclidean, Manhattan, Frobenius, Keypoint Voting, and more
- Multiple Filtering Options: Optimized Kalman filter, full filterpy-equivalent Kalman, or no filtering
- Re-identification (ReID): Full support for object re-identification with
reid_hit_counterlifecycle - Thread-safe: Concurrent-safe ID generation and tracking
- Python Callable Support: Use custom Python distance functions directly with the Tracker
Cross-language performance comparison (IoU distance, OptimizedKalmanFilter):
| Scenario | Frames | Detections | norfair | norfair-go | norfair-rs (python) | norfair-rs (rust) |
|---|---|---|---|---|---|---|
| Small | 100 | 446 | 4,700 fps | 243,000 fps | 107,000 fps | 296,000 fps |
| Medium | 500 | 9,015 | 540 fps | 31,000 fps | 27,000 fps | 89,000 fps |
| Large | 1,000 | 44,996 | 101 fps | 3,800 fps | 11,000 fps | 41,000 fps |
| Stress | 2,000 | 179,789 | — | 547 fps | 5,200 fps | 18,500 fps |
Speedup norfair-rs (rust) vs norfair: 60-180x depending on scenario complexity Speedup norfair-rs (python) vs norfair: 20-50x (drop-in replacement)
Benchmarks run on Apple M3 Pro. See examples/benchmark/ for reproduction scripts.
Play around with the norfair (python) vs norfair-rs (rust) comparison tool using:
uv run examples/compare_norfair_py_rs.pyAdd to your Cargo.toml:
[dependencies]
norfair-rs = "0.3"norfair-rs provides Python bindings that work as a drop-in replacement for the original norfair library, with 20-50x better performance:
uv add norfair-rs
# or: pip install norfair-rsThen simply change your import:
# Before (original norfair)
from norfair import Detection, Tracker
# After (norfair-rs - same API, much faster!)
from norfair_rs import Detection, TrackerMost of your existing norfair code should work unchanged. See the benchmark results for performance comparisons.
Python API Compatibility
Compatible with norfair:
- ✅
Detection,TrackedObject,Tracker- Same API - ✅ Custom Python callable distance functions
- ✅ Custom Python callable
reid_distance_function - ✅
create_keypoints_voting_distance(),create_normalized_mean_euclidean_distance() - ✅ All built-in distance functions via
get_distance_by_name() - ✅ Full ReID support with
reid_hit_counterlifecycle - ✅ Filter factories:
OptimizedKalmanFilterFactory,FilterPyKalmanFilterFactory,NoFilterFactory - ✅
TranslationTransformationfor camera motion
Not yet implemented: The following features from Python norfair are not available in norfair-rs:
- Video I/O -
Videoclass (requires OpenCV) - Drawing -
draw_boxes(),draw_points(),draw_tracked_objects(),Drawer,Paths(requires OpenCV) - Homography -
HomographyTransformation,MotionEstimator(requires OpenCV) - Utilities -
FixedCamera,get_cutout(),print_objects_as_table() - Scipy - Scipy vectorised distance functions and some other features.
Most of these features require OpenCV bindings which are not yet implemented. Core tracking works fully.
use norfair_rs::{Detection, Tracker, TrackerConfig};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Create tracker with IoU distance function
let mut config = TrackerConfig::from_distance_name("iou", 0.5);
config.hit_counter_max = 30;
config.initialization_delay = 3;
let mut tracker = Tracker::new(config)?;
// 2. For each frame
for frame in iter_video_frames() {
// 2.1 Generate detections from your object detector
let detections: Vec<Detection> = detect_objects(&frame)
.iter()
.map(|bbox| {
// Bounding box format: [x1, y1, x2, y2]
Detection::from_slice(
&[bbox.x, bbox.y, bbox.x + bbox.w, bbox.y + bbox.h],
1, 4 // 1 row, 4 columns
).unwrap()
})
.collect();
// 2.2 Update tracker, returning current tracked objects with stable IDs
let tracked_objects = tracker.update(detections, 1, None);
// 2.3 Use tracked objects (draw, analyze, etc.)
for obj in tracked_objects {
if let Some(id) = obj.id {
draw_box(&frame, &obj.estimate, id);
}
}
}
Ok(())
}Python Norfair Equivalent
Here's how the same tracking workflow looks in the original Python norfair library:
Python:
# OLD: from norfair import Detection, Tracker
from norfair_rs import Detection, Tracker
# Create tracker
tracker = Tracker(
distance_function="iou",
distance_threshold=0.5,
hit_counter_max=30,
initialization_delay=3,
)
# Process frames
for frame in iter_video_frames():
# Get detections from your detector
detections = [
Detection(points=np.array([[x1, y1, x2, y2]]))
for x1, y1, x2, y2 in detect_objects(frame)
]
# Update tracker
tracked_objects = tracker.update(detections=detections)
# Use tracked objects
for obj in tracked_objects:
draw_box(frame, obj.estimate, obj.id)Key Differences:
- Rust: Explicit configuration structs vs Python kwargs
- Rust: Error handling with
Result<T, E>returns - Rust: Uses
nalgebramatrices instead of numpy arrays - Rust: Zero-cost abstractions with compile-time guarantees
Both implementations provide the same core functionality with Rust offering better performance.
use norfair_rs::{TrackerConfig, filter::OptimizedKalmanFilterFactory};
use norfair_rs::distances::distance_by_name;
let mut config = TrackerConfig::new(distance_by_name("euclidean"), 50.0);
// Tracking behavior
config.hit_counter_max = 15; // Frames to keep tracking without detection
config.initialization_delay = 3; // Detections required to initialize
config.pointwise_hit_counter_max = 4; // Per-point tracking threshold
config.detection_threshold = 0.5; // Minimum detection confidence
config.past_detections_length = 4; // History for re-identification
// Re-identification (optional)
config.reid_distance_function = Some(distance_by_name("euclidean"));
config.reid_distance_threshold = 100.0;
config.reid_hit_counter_max = Some(50);
// Kalman filter
config.filter_factory = Box::new(OptimizedKalmanFilterFactory::new(
4.0, // R (measurement noise)
0.1, // Q (process noise)
10.0, // P (initial covariance)
0.0, // pos_variance
1.0, // vel_variance
));Built-in distance functions available via distance_by_name():
| Name | Description | Use Case |
|---|---|---|
"euclidean" |
L2 distance between points | Single-point tracking |
"iou" |
1 - Intersection over Union | Bounding box tracking |
"mean_euclidean" |
Average L2 across all points | Multi-keypoint tracking |
"mean_manhattan" |
Average L1 across all points | Grid-aligned tracking |
"frobenius" |
Frobenius norm of difference | Matrix comparison |
Custom distance functions can be implemented via the Distance trait.
Three filter types are available:
use norfair_rs::filter::{
OptimizedKalmanFilterFactory, // Fast, simplified Kalman (default)
FilterPyKalmanFilterFactory, // Full filterpy-compatible Kalman
NoFilterFactory, // No prediction (detection-only)
};Tracker- Main tracking engine that maintains object identities across framesDetection- Input from object detector (bounding boxes, keypoints, or arbitrary points)TrackedObject- Output object with stable ID, position estimate, and tracking metadataTrackerConfig- Configuration for tracker behaviorTrackedObjectFactory- Thread-safe ID generation
use norfair_rs::camera_motion::TranslationTransformation;
// Compensate for camera movement
let transform = TranslationTransformation::new([dx, dy]);
let tracked = tracker.update(detections, 1, Some(&transform));[dependencies]
norfair = { git = "...", features = ["opencv"] }| Feature | Description |
|---|---|
opencv |
Enable video I/O, drawing, and homography transforms |
norfair-rs is licensed under the BSD 3-Clause License.
This Rust port is based on the original norfair by Tryolabs (BSD 3-Clause). Their well-designed, detector-agnostic architecture made this port possible. Internal modules include code adapted from several Python libraries—see THIRD_PARTY_LICENSES.md for complete attribution.
Citation: If using this library in research, please cite the original norfair paper as described here.
Contributing: Issues and pull requests welcome!
