API reference¶
The public API is the Stabilizer class, importable as from stabilo import Stabilizer. A typical session calls set_ref_frame() once, then stabilize() per frame, and reads the result with warp_cur_frame(), transform_cur_boxes(), or get_cur_trans_matrix(). The estimated matrix maps the current frame onto the reference frame.
The class attributes named VALID_* list the accepted values of the corresponding options, for example VALID_DETECTORS for detector_name, VALID_MATCHERS for matcher_name, and VALID_DEVICES for device. They are checked when the Stabilizer is created, so they are the de-facto parameter contract.
Stabilizer
¶
Stabilize video frames, or the bounding boxes of objects in them, against a reference frame.
Features are detected in the reference frame once and in every current frame, matched, and passed to a robust RANSAC-family estimator. The resulting matrix maps the current frame onto the reference frame; use it to warp the frame, to transform its boxes, or read it directly.
Create a stabilizer and build its detector, matcher, and estimator.
Every option is a keyword argument that defaults to its value in cfg/default.yaml, and
Stabilizer.configurable_keys() lists them. A keyword that is not one of them is ignored with
a warning naming the nearest valid key. Learned models download their weights on first use.
Other Parameters:
| Name | Type | Description |
|---|---|---|
detector_name |
str
|
Feature detector: 'orb', 'sift', 'rsift' (RootSIFT), 'brisk', 'kaze', 'akaze', or a learned one, 'xfeat', 'disk', 'dedode', 'keynet', 'loftr'. |
max_features |
int
|
Keypoints kept in the current frame, the strongest by response. |
ref_multiplier |
float
|
The reference frame keeps ref_multiplier x max_features keypoints. |
matcher_name |
str
|
Descriptor matcher: 'bf', 'flann', or 'lightglue' (with 'disk', 'dedode', or 'keynet' only). 'loftr' matches the image pair itself, so it ignores 'bf' and 'flann' and rejects 'lightglue'. |
filter_type |
str
|
Match filter: 'none', 'ratio' (Lowe's ratio test), or 'distance'. |
filter_ratio |
float
|
Lowe's ratio for 'ratio', or the distance-threshold ratio for 'distance'. |
match_query_frame |
str
|
Whose descriptors are the knnMatch query, 'reference' or 'current'. |
transformation_type |
str
|
'projective' (3x3 homography) or 'affine' (2x3, partial affine). |
ransac_method |
int
|
OpenCV robust-estimation flag, a value of VALID_RANSAC_METHODS_DICT (default 38, MAGSAC++). 'affine' supports only 4 (LMEDS) and 8 (RANSAC). |
ransac_epipolar_threshold |
float
|
Reprojection-error threshold, in the pixels named by ransac_threshold_space. |
ransac_threshold_space |
str
|
'full' to measure the threshold in full-resolution pixels, or 'processed' to measure it in pixels of the downsampled image, which keeps it fixed relative to keypoint localization noise as downsample_ratio changes. |
ransac_max_iter |
int
|
Maximum RANSAC iterations. |
ransac_confidence |
float
|
Required RANSAC confidence level. |
mask_use |
bool
|
Exclude the boxes passed to set_ref_frame() and stabilize() from feature detection. When False the boxes are discarded. |
mask_margin_ratio |
float
|
Fraction by which each excluded region is grown. |
clahe |
bool
|
Apply CLAHE contrast enhancement before detection. |
downsample_ratio |
float
|
Detect on a frame resized by this factor; keypoints are scaled back to full resolution. Use 1.0 for coordinate-exact work. |
gpu |
bool
|
OpenCV CUDA acceleration of detection, matching, and warping. Needs a CUDA-enabled OpenCV and a detector in VALID_GPU_DETECTORS. |
gpu_device_id |
int
|
CUDA device index used when gpu is True. |
device |
str
|
Torch device for the learned detectors and matchers: 'auto' (cuda, then mps, then cpu), 'cpu', 'cuda', or 'mps'. Independent of gpu. |
sift_enable_precise_upscale |
bool
|
SIFT and RootSIFT precise sub-pixel upscaling at octave -1. |
rsift_eps |
float
|
Epsilon added to the L1 norm in RootSIFT descriptor normalization. |
brisk_threshold |
int
|
BRISK AGAST detection threshold. |
kaze_threshold |
float
|
KAZE detector response threshold. |
akaze_threshold |
float
|
AKAZE detector response threshold. |
loftr_weights |
str
|
LoFTR pretrained weights, 'outdoor' or 'indoor'. |
loftr_confidence |
float
|
Minimum LoFTR correspondence confidence to keep. |
disk_weights |
str
|
DISK pretrained weights, 'depth' or 'epipolar'. |
dedode_detector_weights |
str
|
DeDoDe detector weights. |
dedode_descriptor_weights |
str
|
DeDoDe descriptor weights. |
viz |
bool
|
Retain keypoints, masks, and inliers for visualization. |
benchmark |
bool
|
Fall back to the identity matrix instead of the last known one when estimation fails, and skip the resolution warning and the update check. |
min_good_match_count_warning |
int
|
Warn when fewer good matches than this are found. |
min_inliers_match_count_warning |
int
|
Warn when fewer inliers than this are found. |
logger |
Logger
|
Logger that receives every message, instead of stabilo's own. It is not a configuration key. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an option is out of range, or options are incompatible (for example gpu=True with a learned detector, or 'affine' with an unsupported ransac_method). |
VALID_DETECTORS
class-attribute
instance-attribute
¶
VALID_DETECTORS = ['orb', 'sift', 'rsift', 'brisk', 'kaze', 'akaze', 'xfeat', 'disk', 'dedode', 'keynet', 'loftr']
DL_DETECTORS
class-attribute
instance-attribute
¶
SPARSE_DL_DETECTORS
class-attribute
instance-attribute
¶
LIGHTGLUE_FEATURE_NAMES
class-attribute
instance-attribute
¶
DL_PIXEL_BUDGETS
class-attribute
instance-attribute
¶
DL_PIXEL_BUDGETS = {'xfeat': 2000000, 'disk': 500000, 'dedode': 500000, 'keynet': 500000, 'loftr': 300000}
VALID_FILTER_TYPES
class-attribute
instance-attribute
¶
VALID_TRANSFORMATION_TYPES
class-attribute
instance-attribute
¶
VALID_MATCH_QUERY_FRAMES
class-attribute
instance-attribute
¶
VALID_RANSAC_THRESHOLD_SPACES
class-attribute
instance-attribute
¶
VALID_RANSAC_METHODS_DICT
class-attribute
instance-attribute
¶
VALID_RANSAC_METHODS_DICT = {'cv2.LMEDS': cv2.LMEDS, 'cv2.RANSAC': cv2.RANSAC, 'cv2.RHO': cv2.RHO, 'cv2.USAC_DEFAULT': cv2.USAC_DEFAULT, 'cv2.USAC_PARALLEL': cv2.USAC_PARALLEL, 'cv2.USAC_FAST': cv2.USAC_FAST, 'cv2.USAC_ACCURATE': cv2.USAC_ACCURATE, 'cv2.USAC_PROSAC': cv2.USAC_PROSAC, 'cv2.USAC_MAGSAC': cv2.USAC_MAGSAC}
RANSAC_METHOD_NAMES
class-attribute
instance-attribute
¶
RANSAC_METHOD_NAMES = {cv2.LMEDS: 'LMEDS', cv2.RANSAC: 'RANSAC', cv2.RHO: 'RHO', cv2.USAC_DEFAULT: 'USAC-Default (LO-RANSAC)', cv2.USAC_PARALLEL: 'USAC-Parallel (LO-RANSAC)', cv2.USAC_FAST: 'USAC-Fast (LO-RANSAC)', cv2.USAC_ACCURATE: 'USAC-Accurate (GC-RANSAC)', cv2.USAC_PROSAC: 'PROSAC', cv2.USAC_MAGSAC: 'MAGSAC++'}
VALID_AFFINE_RANSAC_METHODS
class-attribute
instance-attribute
¶
configurable_keys
classmethod
¶
Every key that can be passed to Stabilizer(...) or set in a configuration file.
These are exactly the keys of cfg/default.yaml, which is the single source of truth for
the options. logger is not among them: it is a constructor-only argument, popped
before the configuration is loaded. Exposed so a caller that forwards a user's
configuration can filter or check it against stabilo rather than keeping its own copy
of the list, which drifts as soon as stabilo gains an option.
describe_ransac_methods
classmethod
¶
Render RANSAC methods as 'code (Name)' strings, sorted by code.
set_ref_frame
¶
Set the frame that every later frame is aligned to, and detect its features.
Call it again to change the reference. The process exits if frame is None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
BGR image. |
required |
boxes
|
ndarray
|
Regions to exclude from feature detection, typically moving objects, one per row in box_format. Ignored when mask_use is False. |
None
|
box_format
|
str
|
'xywh', 'xywha', 'four', 'polygon', or 'circle'; see create_binary_mask(). |
'xywh'
|
stabilize
¶
Estimate the transformation that maps a frame onto the reference frame.
Read the result with warp_cur_frame(), transform_cur_boxes(), or get_cur_trans_matrix(). When estimation fails, the last successful matrix is kept (the identity when benchmark is True). The process exits if frame is None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
BGR image to stabilize. |
required |
boxes
|
ndarray
|
Regions to exclude from feature detection, one per row in box_format; these are also the boxes that transform_cur_boxes() maps. Ignored when mask_use is False, in which case transform_cur_boxes() returns None. |
None
|
box_format
|
str
|
'xywh', 'xywha', 'four', 'polygon', or 'circle'; see create_binary_mask(). |
'xywh'
|
process_frame
¶
Store a frame and its boxes, build its exclusion mask, and detect its features.
Called by set_ref_frame() and stabilize(). Returns True; the process exits if frame is None.
get_features_and_descriptors
¶
Detect keypoints in a frame and describe them, honouring the mask and downsample_ratio.
Returns:
| Type | Description |
|---|---|
tuple
|
A (keypoints, descriptors, gray_frame) tuple. Keypoints are in full-resolution coordinates and gray_frame is None unless viz is True. All three are None for a None frame or a failed detection, and descriptors is None when no keypoints are found. 'loftr' detects nothing here: it only caches the frame for matching. |
get_matches
¶
Match two descriptor sets with the configured matcher and filter.
Returns:
| Type | Description |
|---|---|
list
|
The surviving cv2.DMatch list, with desc1 as the query; empty if either set is None. |
calculate_transformation_matrix
¶
Estimate the current-to-reference matrix from the point correspondences found by stabilize().
Needs at least four correspondences. On failure the matrix falls back to the last known one (the identity when benchmark is True) and the inlier count becomes None.
create_binary_mask
¶
Build a detection mask of the reference frame's size that excludes the given regions.
Each region is grown by mask_margin_ratio, and regions with non-finite coordinates are skipped with a warning. The box formats, one region per row:
- 'xywh': [x_center, y_center, width, height], axis-aligned
- 'xywha': [x_center, y_center, width, height, angle_degrees], oriented
- 'four': [x1, y1, x2, y2, x3, y3, x4, y4], four corners, oriented or axis-aligned
- 'polygon': [x1, y1, ..., xN, yN] per row, or one (N, 2) array per polygon
- 'circle': [x_center, y_center, radius]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
boxes
|
ndarray
|
Regions to exclude. |
required |
box_format
|
str
|
One of the formats above. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
A uint8 mask, 255 where features may be detected and 0 in the excluded regions. |
warp_cur_frame
¶
Warp the frame last passed to stabilize() onto the reference frame.
Returns:
| Type | Description |
|---|---|
Union[ndarray, None]
|
The warped frame, the input frame unchanged if no matrix is available yet, or None if there is no current frame. |
warp_frame
¶
Warp a frame onto the reference frame with the current transformation matrix.
Returns:
| Type | Description |
|---|---|
Union[ndarray, None]
|
The warped frame, at the reference frame's size; the input frame unchanged if no matrix is available yet; None if frame is None. |
transform_cur_boxes
¶
Map the boxes last passed to stabilize() into the reference frame.
The boxes must be in 'xywh', 'xywha', or 'four' format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
out_box_format
|
str
|
'xywh', 'xywha', or 'four'. |
'xywh'
|
Returns:
| Type | Description |
|---|---|
Union[ndarray, None]
|
The transformed boxes, or None if there are none (including when mask_use is False). |
transform_boxes
¶
Transform boxes with a given matrix, of the configured transformation_type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
boxes
|
ndarray
|
Boxes to transform, one per row. |
required |
trans_matrix
|
ndarray
|
3x3 homography or 2x3 affine matrix. |
required |
in_box_format
|
str
|
'xywh', 'xywha', or 'four'. |
'xywh'
|
out_box_format
|
str
|
'xywh', 'xywha', or 'four'. 'xywha' is approximate under non-uniform scaling, so 'four' is preferred. |
'xywh'
|
Returns:
| Type | Description |
|---|---|
Union[ndarray, None]
|
The transformed boxes in out_box_format; boxes unchanged if trans_matrix is None; None if boxes is None. |
get_cur_boxes
¶
Return the boxes last passed to stabilize(), or None (always None when mask_use is False).
get_cur_trans_matrix
¶
Return the current-to-reference matrix (3x3 projective or 2x3 affine), or None before the first successful estimation.
get_cur_inliers_count
¶
Return the number of inliers behind the current matrix, or None if estimation failed or has not run.
get_cur_num_matches
¶
Return the number of filtered matches (point pairs) passed to the estimator, the denominator of the inlier ratio, or None before the first stabilize().
get_cur_num_keypoints
¶
Return the keypoint counts as (reference, current); either is None if unavailable.
get_ransac_reproj_threshold
¶
Return the reprojection threshold actually handed to the estimator, in full-resolution pixels.
Correspondences are rescaled to full resolution before estimation, so the estimator's threshold is always a full-resolution distance. With ransac_threshold_space='full' that is ransac_epipolar_threshold itself. With 'processed' the configured value is a processed-image distance, and rescaling multiplies it by 1 / downsample_ratio, exactly as it multiplies the residuals the threshold has to separate. The two agree at downsample_ratio 1.0.
get_basic_info
¶
Return the main settings: detector, matcher, filter, transformation type, CLAHE, and mask use.
Configurable keyword arguments¶
Stabilizer(**kwargs) accepts exactly the keys of stabilo/cfg/default.yaml, reproduced below with their defaults and valid ranges. The same keys can be set in a file passed to the CLI's --custom-config. A keyword argument that is not one of these keys is ignored with a warning that names the nearest valid key; Stabilizer.configurable_keys() returns the valid names. The only other keyword argument is logger, an optional logging.Logger that receives all of stabilo's messages.
# =============================================================================
# stabilo - default configuration
# =============================================================================
# These defaults are tuned for the ORB detector, brute-force (BF) matcher,
# Lowe's ratio test, and a projective transformation, balancing accuracy against
# speed for high-altitude bird's-eye-view (BeV) drone imagery.
#
# They were obtained on a set of BeV drone scenes with associated vehicle masks,
# captured from roughly 150 m altitude. With the projective transformation fixed,
# the remaining parameters were optimized hierarchically. See:
# https://github.com/rfonod/stabilo-optimize
#
# Usage:
# stabilo config show # print this file
# stabilo config copy # write an editable copy to ./custom.yaml
# stabilo video <input> --save --custom-config custom.yaml
#
# Only the keys present in this file are configurable. Every key can also be
# passed directly to Stabilizer(...) as a keyword argument. Each parameter is
# documented in more detail in docs/usage.md and in the Stabilizer class
# docstring in stabilo/stabilo.py.
# =============================================================================
# -----------------------------------------------------------------------------
# Feature detection
# -----------------------------------------------------------------------------
# Classical detectors ('orb', 'sift', 'rsift', 'brisk', 'kaze', 'akaze') ship with
# OpenCV. Learning-based detectors ('xfeat', 'disk', 'dedode', 'keynet', 'loftr')
# come from kornia and download pretrained weights on first use (cached under
# torch.hub's directory; set TORCH_HOME to relocate it).
# 'loftr' is detector-free: it matches an image pair directly, so 'matcher_name'
# and 'filter_type' are ignored when it is selected.
#
# Two caveats apply to the learning-based detectors:
# Rotation. 'xfeat', 'disk', 'dedode', and 'loftr' are upright models; matching
# collapses once the current frame is rotated more than roughly 30 degrees from
# the reference frame. Only 'keynet' (which estimates keypoint orientation) and
# the classical detectors are rotation invariant.
# Memory. They are far hungrier than the classical detectors at high resolution.
# Lower 'downsample_ratio' for large inputs; 'loftr' scales quadratically with
# the pixel count. A warning is emitted when the frame size looks risky.
detector_name: 'orb' # ['orb', 'sift', 'rsift', 'brisk', 'kaze', 'akaze', 'xfeat', 'disk', 'dedode', 'keynet', 'loftr']
max_features: 2000 # Max. keypoints kept in the current frame, the strongest by response; (0, inf)
ref_multiplier: 2.0 # Reference frame detects ref_multiplier x max_features keypoints; [1.0, inf)
# -----------------------------------------------------------------------------
# Feature matching and filtering
# -----------------------------------------------------------------------------
# 'bf' and 'flann' are OpenCV descriptor matchers. 'lightglue' is a learned matcher
# (kornia) and is only compatible with the 'disk', 'dedode', and 'keynet' detectors;
# it does its own filtering, so 'filter_type' is ignored when it is selected.
matcher_name: 'bf' # ['bf', 'flann', 'lightglue']
filter_type: 'ratio' # ['none', 'ratio', 'distance']
filter_ratio: 0.9 # Lowe's ratio ('ratio') or distance-threshold ratio ('distance'); (0.0, 1.0]
match_query_frame: 'reference' # Which frame's descriptors are the knnMatch query; ['reference', 'current']
# -----------------------------------------------------------------------------
# Transformation estimation
# -----------------------------------------------------------------------------
# The estimated matrix maps the current frame onto the reference frame, so the
# RANSAC reprojection threshold is evaluated in reference-frame pixels.
#
# Which pixels depends on 'ransac_threshold_space'. Keypoints are rescaled to full
# resolution before the estimator sees them, so under 'full' the threshold is a
# full-resolution distance at any 'downsample_ratio', while the localization error
# it separates arises in the processed image and is magnified by 1/downsample_ratio.
# A fixed threshold is therefore stricter at a low ratio, and a value tuned at one
# ratio does not transfer to another. 'processed' measures it in processed-image
# pixels instead, passing ransac_epipolar_threshold / downsample_ratio to the
# estimator. The two agree at downsample_ratio: 1.0. See docs/usage.md section 11.
#
# Not every ransac_method works with every transformation_type. 'projective'
# (cv2.findHomography) accepts all of them, whereas 'affine'
# (cv2.estimateAffinePartial2D) accepts ONLY 4 (LMEDS) and 8 (RANSAC); any other
# code, including the default 38, is rejected when the Stabilizer is created.
transformation_type: 'projective' # ['projective' (3x3 homography, any ransac_method), 'affine' (2x3, ransac_method 4 or 8 only)]
ransac_method: 38 # [4: LMEDS, 8: RANSAC, 16: RHO, 32: USAC-Default (LO-RANSAC), 33: USAC-Parallel (LO-RANSAC), 35: USAC-Fast (LO-RANSAC), 36: USAC-Accurate (GC-RANSAC), 37: PROSAC, 38: MAGSAC++]
ransac_epipolar_threshold: 2.0 # Reprojection-error threshold, in pixels of ransac_threshold_space; (0.0, inf)
ransac_threshold_space: 'full' # Pixels ransac_epipolar_threshold is measured in; ['full' (full resolution), 'processed' (downsampled image)]
ransac_max_iter: 5000 # Max. RANSAC iterations; (0, inf)
ransac_confidence: 0.999999 # Required RANSAC confidence level; (0.0, 1.0]
# -----------------------------------------------------------------------------
# Exclusion masks
# -----------------------------------------------------------------------------
# Exclusion masks suppress keypoints on dynamic objects (e.g. moving vehicles),
# which would otherwise drag the estimated transformation with them.
mask_use: true # Honour the bounding boxes passed to set_ref_frame()/stabilize(); [true, false]
mask_margin_ratio: 0.15 # Fractional margin grown around each exclusion region; [0.0, 1.0]
# -----------------------------------------------------------------------------
# Image pre-processing
# -----------------------------------------------------------------------------
clahe: false # Apply Contrast Limited Adaptive Histogram Equalization; [true, false]
downsample_ratio: 0.5 # Detect on a resized frame, then rescale keypoints to full resolution; (0.0, 1.0]
# -----------------------------------------------------------------------------
# Hardware acceleration
# -----------------------------------------------------------------------------
# Two independent, mutually exclusive acceleration paths:
#
# gpu -> OpenCV CUDA. Accelerates detection, matching, and frame warping for
# the CLASSICAL detectors. Requires an OpenCV built with CUDA support
# (see docs/cuda.md); currently only detector_name='orb' is supported.
# RANSAC always runs on the CPU (OpenCV has no CUDA equivalent).
#
# device -> PyTorch device. Selects where the LEARNING-BASED detectors/matchers
# run. Ignored by the classical detectors. 'auto' picks cuda > mps > cpu.
#
# Setting gpu: true together with a learning-based detector is rejected; use
# device: 'cuda' instead.
gpu: false # OpenCV CUDA acceleration (classical detectors only); [true, false]
gpu_device_id: 0 # CUDA device index used when gpu is true; [0, inf)
device: 'auto' # Torch device (learning-based detectors/matchers only); ['auto', 'cpu', 'cuda', 'mps']
# -----------------------------------------------------------------------------
# Detector-specific settings
# -----------------------------------------------------------------------------
# Classical. BRISK/KAZE/AKAZE detect every keypoint whose response exceeds the
# threshold below, then keep the max_features strongest (ref_multiplier x
# max_features in the reference frame), as ORB and SIFT do. The defaults are
# OpenCV's. Lower a threshold if a frame yields fewer keypoints than max_features.
sift_enable_precise_upscale: false # SIFT/RSIFT: precise sub-pixel upscaling at octave -1; [true, false]
rsift_eps: 0.00000001 # RSIFT: L1-normalization epsilon (1e-8); (0.0, inf)
brisk_threshold: 30 # BRISK: AGAST detection threshold; (0, 255]
kaze_threshold: 0.001 # KAZE: detector response threshold; (0.0, inf)
akaze_threshold: 0.001 # AKAZE: detector response threshold; (0.0, inf)
# Learning-based (kornia). Weights are downloaded on first use and cached by torch.
# The DeDoDe 'G' descriptor is backed by a DINOv2 ViT-L (a 1.2 GB extra download and
# roughly double the memory of 'B') and is not more rotation robust, so 'B' is the
# default. The 'C4'/'SO2' descriptor variants need a steerer at match time, which
# kornia does not apply, so they are no more rotation robust than the upright ones.
loftr_weights: 'outdoor' # LoFTR: pretrained weights; ['outdoor', 'indoor']
loftr_confidence: 0.0 # LoFTR: min. correspondence confidence to keep; [0.0, 1.0] (0.0 keeps all)
disk_weights: 'depth' # DISK: pretrained weights; ['depth', 'epipolar']
dedode_detector_weights: 'L-C4-v2' # DeDoDe: detector weights; ['L-upright', 'L-C4', 'L-SO2', 'L-C4-v2']
dedode_descriptor_weights: 'B-upright' # DeDoDe: descriptor weights; ['B-upright', 'G-upright', 'B-C4', 'B-SO2', 'G-C4', 'G-SO2']
# -----------------------------------------------------------------------------
# Diagnostics
# -----------------------------------------------------------------------------
viz: false # Retain keypoints, masks, and inliers for visualization; [true, false]
benchmark: false # Benchmark mode: fall back to identity, suppress warnings; [true, false]
min_good_match_count_warning: 20 # Warn when fewer than this many good matches are found; [0, inf)
min_inliers_match_count_warning: 10 # Warn when fewer than this many inliers are found; [0, inf)
The meaning of each key is described in Usage: Configuration parameters.