Stabilo stabilizes video frames and tracked object trajectories against a chosen reference frame, using robust homography or affine registration with optional exclusion masks for dynamic regions. It is the registration engine behind Geo-trax π and is tuned with Stabilo-Optimize π―.
π¬ An accelerated preview of Stabilo's capabilities. Watch the full 14-second 4K demo on YouTube.
- π― Anchored, not smoothed: every frame and every tracked box is aligned to one chosen reference frame, so coordinates stay comparable across the whole video, not just between neighbouring frames.
- π Masks that ignore moving objects: exclude vehicles, pedestrians, or any dynamic region from registration, using five mask geometries (boxes, oriented boxes, four-point boxes, polygons, circles).
- π§ Classical and learned features in one API: 6 classical detectors (ORB, SIFT, RootSIFT, BRISK, KAZE, AKAZE) and 5 learned ones (XFeat, DISK, DeDoDe, KeyNet, LoFTR), paired with classical (BF, FLANN) or learned (LightGlue) matchers, see Feature Detectors and Matchers, all swappable with a single flag.
- β‘ Two acceleration paths: OpenCV CUDA for the classical pipeline (
--gpu) and any PyTorch device for the learned models (--device cuda|mps); see Configuration. - π¦ Library and CLI:
stabilo videoandstabilo tracksneed no code; theStabilizerclass embeds into your own pipeline in a handful of lines. - π¬ Battle-tested: powers the georeferenced trajectory extraction in Geo-trax, benchmarked ground-truth-free by Stabilo-Optimize.
It is recommended to create and activate a Python virtual environment (Python >= 3.11 and <= 3.13) first:
python3.11 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activateAlternatives: conda or uv
conda create -n stabilo python=3.11 -y
conda activate stabilouv (fastest; use uv pip install below):
uv venv --python 3.11
source .venv/bin/activate # On Windows: .venv\Scripts\activateInstall from PyPI:
pip install stabiloThis installs everything, including the learning-based detectors and matchers (kornia + torch). The first time a learned detector or matcher (xfeat, disk, dedode, keynet, loftr, lightglue) is used, its pretrained weights are downloaded into torch's cache directory (~/.cache/torch/hub/checkpoints on Linux/macOS, %USERPROFILE%\.cache\torch\hub\checkpoints on Windows; override with the TORCH_HOME env var) and reused from there on every later run, offline.
Install from local source (for development)
git clone https://github.com/rfonod/stabilo.git
cd stabilo && pip install -e '.[dev]'Drop -e for a non-editable install, and [dev] if you do not need pytest, ruff, and matplotlib.
data/video.mp4 is a short drone clip bundled for immediate testing, along with per-frame vehicle boxes in data/video.txt. See data/README.md for the reference outputs.
# Stabilize the video against frame 0, saving the result and a side-by-side visualization
stabilo video data/video.mp4 --save --save-viz --output data/out/
# Stabilize the vehicle trajectories, using the same boxes as exclusion masks
stabilo tracks data/video.mp4 --save --save-viz --output data/out/Both write into the git-ignored data/out/, so you can diff your results against the reference files committed in data/ without overwriting them. Run stabilo -h, stabilo video -h, or stabilo tracks -h for the full option list.
Stabilo ships classical OpenCV detectors and learning-based detectors/matchers from kornia as core functionality. The learning-based options download pretrained weights on first use (see Installation) and benefit from a GPU (--device cuda or --device mps).
detector_name |
Type | Descriptor | Compatible matchers | Speed (CPU) | Rotation invariant | Memory | Acceleration |
|---|---|---|---|---|---|---|---|
orb (default) |
classical, binary | 256-bit | bf, flann |
very fast | β | low | OpenCV CUDA (--gpu) |
sift |
classical, float | 128-d | bf, flann |
medium | β | low | β |
rsift |
classical, float | 128-d (RootSIFT) | bf, flann |
medium | β | low | β |
brisk |
classical, binary | 512-bit | bf, flann |
fast | β | low | β |
kaze |
classical, float | 64-d | bf, flann |
slow | β | low | β |
akaze |
classical, binary | 486-bit | bf, flann |
fast | β | low | β |
xfeat |
learned, sparse | 64-d float | bf, flann |
fast | β | low | torch (--device) |
disk |
learned, sparse | 128-d float | bf, flann, lightglue |
medium | β | high | torch (--device) |
dedode |
learned, sparse | 256-d float | bf, flann, lightglue |
slow | β | high | torch (--device) |
keynet |
learned det. + HardNet desc. | 128-d float | bf, flann, lightglue |
medium | β | high | torch (--device) |
loftr |
learned, detector-free | dense matching | built-in (matcher ignored) | slow | β | very high | torch (--device) |
Three matchers are available: bf (brute force) and flann work with every detector, while the learned lightglue pairs with disk, dedode, and keynet. loftr is detector-free: it matches an image pair directly, so matcher_name and filter_type are ignored. Full details in docs/usage.md.
Important
Two caveats apply to the learning-based detectors. They are worth understanding before you pick one.
- Rotation.
xfeat,disk,dedode, andloftrare upright models: matching collapses once the current frame is rotated more than roughly 30Β° from the reference frame. Footage with large in-plane rotation (an orbiting or yawing drone, for example) needs a classical detector orkeynet, which estimates a keypoint orientation. This is a property of the pretrained models, not of stabilo. - Memory. They are far hungrier than the classical detectors, and cost grows with the processed frame size. At the default
--downsample-ratio 0.5, a 4K frame is still 2 MP, which can exhaust system memory;loftris the worst offender because its cost grows quadratically with the pixel count. Lower--downsample-ratiofor large inputs. Stabilo warns when the processed frame size looks risky, but does not cap it. See Choosing a downsample ratio.
π Full Feature Overview
- Video stabilization: warp every frame onto a chosen anchor frame (
stabilo video ... -rf 100) with a projective or affine model (--transformation-type affine). See Library Usage. - Trajectory stabilization: transform per-frame detections or tracks into the reference coordinate system (
stabilo tracks ... --boxes-enc xywha), reading and writingyolo,pascal,coco,xywha, orfourencodings. - Exclusion masks: suppress keypoints on dynamic objects, in five geometries fed by seven input encodings (
--mask-enc yolo|pascal|coco|xywha|four|polygon|circle), with an adjustable safety margin (--mask-margin-ratio 0.15). Disable with--no-mask. See Masking behaviour. - Classical detectors: ORB, SIFT, RootSIFT, BRISK, KAZE, AKAZE (
--detector-name rsift), with BF or FLANN matching (--matcher-name flann) and cross-check, Lowe's ratio, or distance filtering (--filter-type ratio). - Learning-based detectors: XFeat, DISK, DeDoDe, KeyNet, and the detector-free LoFTR via kornia (
--detector-name disk), optionally paired with the learned LightGlue matcher (--matcher-name lightglue). Pretrained weights download once into torch's cache and are reused offline. Mind the rotation and memory caveats above; pair them with a lower--downsample-ratio. - Robust estimation: MAGSAC++ by default, plus DEGENSAC, GC-RANSAC, LO-RANSAC, PROSAC, RHO, LMEDS, RANSAC (
--ransac-method 36), with tunable threshold, iterations, and confidence (--ransac-epipolar-threshold 1.5). See RANSAC methods. - Pre-processing: CLAHE contrast enhancement (
--clahe) and detection-time downsampling for speed (--downsample-ratio 0.25), with keypoints rescaled back to full resolution. - Acceleration: OpenCV CUDA for the classical pipeline (
--gpu, seedocs/cuda.md) and CUDA/MPS/CPU for the learned models (--device mps). - Visualization: live or recorded side-by-side rendering of matches, inliers, masks, and box trajectories (
--viz,--save-viz,--no-lines,--tail-length 60). See Visualisation mode. - Benchmarking:
benchmark=Truefalls back to the identity matrix and silences warnings for clean batch sweeps; Stabilo-Optimize π― wraps this into a ground-truth-free evaluation. - Threshold analysis:
scripts/find_threshold_models.pyfits themax_featuresto detector-threshold models used by BRISK, KAZE, and AKAZE, so detectors can be compared at equal keypoint budgets.
π Planned Enhancements
- Bi-directional Matching: Implementing bi-directional matching to enhance the robustness of keypoint matching.
- Additional Feature Detectors: Adding support for more feature detectors and matchers to provide users with a wider range of options for stabilization.
π Related Projects
Stabilo integrates with and complements several specialized tools:
-
Geo-trax π β End-to-end framework for extracting georeferenced vehicle trajectories from drone imagery. Uses Stabilo as its core stabilization engine to align video frames and vehicle tracks to a common reference frame.
-
Stabilo-Optimize π― β Benchmarking and hyperparameter optimization framework for Stabilo. Evaluates stabilization performance through ground truth-free assessment using random perturbations. Used to fine-tune Stabilo parameters.
-
HBB2OBB π¦ β Converts horizontal bounding boxes to oriented bounding boxes using SAM segmentation models. Can be used alongside Stabilo when object orientation is needed for downstream analysis.
Every tunable parameter lives in one self-contained YAML file, stabilo/cfg/default.yaml, grouped by stage: feature detection, matching and filtering, transformation estimation, exclusion masks, pre-processing, hardware acceleration, detector-specific settings, and diagnostics. The shipped defaults are tuned for high-altitude bird's-eye-view drone imagery.
The same keys are accepted as Stabilizer(**kwargs) arguments, as CLI flags, and as entries in a custom YAML file:
stabilo video data/video.mp4 --save -o data/out/ --detector-name disk --matcher-name lightglue
stabilo video data/video.mp4 --save -o data/out/ --custom-config custom.yamlPrecedence is explicit CLI flags > custom config file > built-in defaults: a value in --custom-config fills in anything you didn't pass on the command line, but an explicit CLI flag always wins.
Two independent, mutually exclusive acceleration knobs are worth calling out:
| Option | Applies to | Notes |
|---|---|---|
gpu / --gpu |
classical detectors | OpenCV CUDA for detection, matching, and warping. Needs a CUDA-enabled OpenCV build (docs/cuda.md); currently orb only. RANSAC always runs on CPU. |
device / --device |
learning-based detectors/matchers | PyTorch device (auto, cpu, cuda, mps). auto picks cuda > mps > cpu. Ignored by the classical detectors. |
βοΈ Inspect, copy, and customize the config
Manage the bundled configuration with the stabilo config command:
stabilo config show # print the default configuration
stabilo config copy # write an editable copy to ./custom.yaml
stabilo config copy -o ~/myproject # write it somewhere elseCopy it, edit it, then pass it with --custom-config:
stabilo config copy
# edit custom.yaml ...
stabilo video data/video.mp4 --save -o data/out/ --custom-config custom.yamlA full description of every key, with valid ranges, is in docs/usage.md.
from stabilo import Stabilizer
stabilizer = Stabilizer() # see Configuration for kwargs
stabilizer.set_ref_frame(ref_frame, ref_mask) # anchor frame (+ optional exclusion mask)
stabilizer.stabilize(cur_frame, cur_mask) # register any other frame against it
stabilized_frame = stabilizer.warp_cur_frame() # the warped frame
stabilized_boxes = stabilizer.transform_cur_boxes() # the transformed boxes
matrix = stabilizer.get_cur_trans_matrix() # current -> reference transformMasks and boxes accept five formats: xywh (axis-aligned), xywha (oriented), four (four corner points), polygon, and circle.
π§© More examples: oriented boxes, polygons, and circles
import numpy as np
# Oriented bounding boxes (OBBs): [x_center, y_center, width, height, angle_degrees]
obb_boxes = np.array([
[100, 150, 50, 30, 45],
[200, 200, 60, 40, 90],
])
stabilizer.set_ref_frame(ref_frame, obb_boxes, box_format='xywha')
stabilizer.stabilize(cur_frame, cur_boxes, box_format='xywha')
# Convert the result to another format on the way out
transformed = stabilizer.transform_cur_boxes(out_box_format='four')
# Polygon masks: flattened [x1, y1, ..., xN, yN] rows, or (N, 2) point arrays
polygon_masks = np.array([
[60, 60, 120, 60, 120, 120, 60, 120],
[200, 200, 260, 210, 240, 270, 190, 260],
])
stabilizer.set_ref_frame(ref_frame, polygon_masks, box_format='polygon')
# Circular masks: [x_center, y_center, radius]
circle_masks = np.array([
[120, 120, 25],
[360, 240, 40],
])
stabilizer.stabilize(cur_frame, circle_masks, box_format='circle')
# Transform an arbitrary pixel coordinate into reference-frame coordinates
ref_point = stabilizer.get_cur_trans_matrix() @ np.array([x, y, 1])polygon and circle are masking-only; they cannot be transformed back out. Full format specifications are in docs/usage.md.
When a CLI command runs, stabilo performs a best-effort check for a newer release on PyPI and prints a one-line notice if one is available. Set
STABILO_DISABLE_UPDATE_CHECK=1to disable it.
docs/usage.md: detailed usage guide covering the workflow, mask formats, every configuration key, the detector and matcher reference, RANSAC methods, visualization, benchmarking, and the CLI.docs/cuda.md: building OpenCV with CUDA and enabling--gpu.scripts/README.md: the threshold-analysis research tool.
If you use Stabilo in your research, software, or product, please cite the following resources appropriately:
-
Preferred Citation: Please cite the associated article for any use of the Stabilo package, including research, applications, and derivative work:
@article{fonod2025advanced, title = {Advanced computer vision for extracting georeferenced vehicle trajectories from drone imagery}, author = {Fonod, Robert and Cho, Haechan and Yeo, Hwasoo and Geroliminis, Nikolas}, journal = {Transportation Research Part C: Emerging Technologies}, volume = {178}, pages = {105205}, year = {2025}, publisher = {Elsevier}, doi = {10.1016/j.trc.2025.105205}, url = {https://doi.org/10.1016/j.trc.2025.105205} }
-
Repository Citation: If you reference, modify, or build upon the Stabilo software itself, please also cite the corresponding Zenodo release:
@software{fonod2026stabilo, author = {Fonod, Robert}, license = {MIT}, month = jul, title = {Stabilo: A Comprehensive Python Library for Video and Trajectory Stabilization with User-Defined Masks}, url = {https://github.com/rfonod/stabilo}, doi = {10.5281/zenodo.12117092}, version = {1.4.0}, year = {2026} }
Contributions from the community are welcome! If you encounter any issues or have suggestions for improvements, please open a GitHub Issue or submit a pull request.
This project is distributed under the MIT License. See the LICENSE file for more details.
