v0.30.0: Run supervision without OpenCV
supervision 0.30.0 makes OpenCV optional. A new private _cv2/ backend (NumPy and Pillow, with PyAV for the video path) reimplements every OpenCV call the library needs, so supervision now runs on opencv-python-headless — or no OpenCV wheel at all — instead of crashing on import. This release also adds Soft-NMS, LabelMe and CreateML dataset formats, GeoTIFF-aware windowed reads for InferenceSlicer, and ships five breaking changes, most notably OpenCV no longer being installed by default, JSONSink switching to native JSON types, and mask_non_max_merge computing exact mask overlap instead of a downscaled approximation. Python 3.9 support is dropped — 3.10 is now the minimum.
✨ Spotlights / highlights
Run supervision without OpenCV
import supervision as sv
window = sv.ImageWindow("frame")
for frame in sv.get_video_frames_generator("input.mp4"):
window.show(frame)
if window.wait_key(1) == "q":
breakThe largest change in this release: OpenCV stays the default backend when installed, but supervision no longer requires it — there's no opencv-python extra anymore either. sv.ImageWindow replaces cv2.imshow/cv2.waitKey for display. av>=14.2 is now a required dependency for the PyAV video path during this transition. See the OpenCV migration guide.
Soft-NMS
detections = sv.Detections.from_ultralytics(result)
softened = detections.with_soft_nms(sigma=0.5)
filtered = detections.with_soft_nms(sigma=0.5, score_threshold=0.3)sv.Detections.with_soft_nms (plus sv.box_soft_non_max_suppression / sv.mask_soft_non_max_suppression) rescales overlapping detections' confidence instead of discarding them outright — useful in crowded scenes where hard NMS drops valid overlapping objects.
New dataset formats + GeoTIFF-aware, batched slicing
dataset = sv.DetectionDataset.from_labelme(
images_directory_path="images/",
annotations_directory_path="annotations/",
)
import rasterio
with rasterio.open("RGB.byte.tif") as raster:
slicer = sv.InferenceSlicer(callback=my_model_callback, batch_size=4)
detections = slicer(raster)DetectionDataset.from_labelme/as_labelme and from_createml/as_createml join the existing COCO/YOLO/Pascal-VOC converters. sv.InferenceSlicer can now read an open rasterio dataset window-by-window for multi-GB aerial/drone GeoTIFFs without loading the whole image (pip install "supervision[geotiff]"), and accepts batch_size for batched-callback inference.
sv.load_image_from_url
image = sv.load_image_from_url("https://media.roboflow.com/notebooks/examples/dog.jpeg")Load an image straight from an HTTP(S) URL as an OpenCV array, with optional on-disk caching.
🔄 Migration guide
Five breaking changes. Most require no code changes beyond a type check or threshold recalibration. The two that need action from most users: the OpenCV install change below, and the Python 3.10 floor.
OpenCV is no longer installed by default. If a compatible cv2 is already importable in your environment, nothing changes for you — it's still preferred automatically. Otherwise install one wheel family yourself (pip install opencv-python or opencv-python-headless) if you need OpenCV-specific behavior, then restart the process — cv2 is detected once at import time. sv.ImageWindow replaces cv2.imshow/cv2.waitKey. Full guide: docs/how_to/opencv_migration.md.
Python 3.10+ is now required — 3.9 reached end-of-life in October 2025.
sv.JSONSink now emits native JSON types, not strings:
# before 0.30.0
row["score"] == "0.85" # str
row["is_valid"] == "True" # str
# after 0.30.0
row["score"] == 0.85 # float
row["is_valid"] is True # boolsv.CSVSink stays textual, but its per-row custom-data slicing now matches JSONSink.
sv.mask_non_max_merge computes exact mask overlap, not a downscaled approximation, and ignores the now-deprecated mask_dimension parameter (kept for signature compatibility, removal in 0.33.0). Re-tune your overlap threshold after upgrading. Passing overlap_metric/mask_dimension positionally still works — the values are still honored — but now emits a DeprecationWarning; pass them by keyword to silence it. More than five positional arguments raises TypeError.
Detections.merge() on mixed dense + CompactMask inputs now returns a CompactMask, not a plain ndarray:
merged = sv.Detections.merge([dense_detections, compact_mask_detections])
isinstance(merged.mask, np.ndarray) # was True, now False — it's a CompactMaskOnly affects code that explicitly merges a CompactMask-carrying Detections object with a dense-mask one yourself — InferenceSlicer, DetectionsSmoother, and with_nms/with_nmm always merge type-homogeneous lists internally, so they're unaffected. The all-dense merge path is also unchanged. This is a substantial performance win: ~2500× less peak memory, ~13× faster on a 1080p frame with 40 detections. If you need the old return type without touching every call site: call merged.mask = merged.mask.to_dense() right after merge(), or avoid producing CompactMask in the first place (Detections.from_inference(compact_masks=False), the default).
supervision also now requires av>=14.2 as an install-time dependency for the PyAV cv2-free video path — this doesn't change any API, so it isn't counted as breaking, but pinned/vendored environments should account for it.
Deprecation removals pushed back one release: ByteTrack, supervision.keypoint, normalized_xyxy, and supervision.dataset.utils RLE compatibility shims — originally scheduled for removal in 0.30.0 — are now scheduled for 0.31.0 instead, giving a full transition window.
📝 Notable changes
🚀 Added
sv.load_image_from_url— load an HTTP(S) image as an OpenCV array, with optional on-disk caching (#2372)- cv2-free PyAV video fallback + private
_cv2backend facade — image/geometry/drawing/text/video without OpenCV (#2430, #2431, #2432, #2433, #2435, #2438, #2439, #2440, #2441, #2443) sv.ImageWindow— tkinter+Pillow desktop window replacingcv2.imshow/cv2.waitKey(#2320)- Soft-NMS —
sv.box_soft_non_max_suppression,sv.mask_soft_non_max_suppression,sv.Detections.with_soft_nms(#1624) sv.VLM.GOOGLE_GEMINI_3_5—Detections.from_vlmparses Gemini 3.5 output (#2449)get_video_frames_generator(prefetch=...)— background-thread decode into a bounded queue (#2273)PolygonZone(require_all_anchors=...)— toggle all-anchors vs. any-anchor containment (#2272)KeyPoints.merge()— combine a list ofKeyPoints, mirroringDetections.merge(#2412)BaseAnnotator.requires_mask— class-level flag on all annotators (#2370)CompactMask.from_coco_rle+Detections.from_inference(compact_masks=True)(#2367)CompactMask.image_shapeproperty (#2383)sv.mask_to_roi— exclusive mask-bound helper for slicing/crops (#2416)DetectionDataset.from_labelme/as_labelme(#2299)DetectionDataset.from_createml/as_createml(#2284)InferenceSlicerGeoTIFF support —sv.WindowedRasterDataset,pip install "supervision[geotiff]"(#2281)InferenceSlicer(batch_size=...)— batched callback contract (#1239)ConfusionMatrix.benchmark(save_directory_path=...)— adaptive TP/FP/FN validation-mosaic export (#2271)HeatMapAnnotator.reset(),TraceAnnotator.reset(),DetectionsSmoother.reset()— clear accumulated per-stream state, so a single instance can be reused across independent streams (#2418)AREA_DATA_FIELDconfig constant (#2428)sv.denormalize_boxesandsv.xyxyxyxy_to_xyxynow exported at the top level
⚠️ Breaking Changes
- OpenCV no longer installed by default; no OpenCV extra (#2443)
- Python 3.10+ required — 3.9 dropped (#2260, #2381)
sv.JSONSinkemits native JSON types instead of strings;sv.CSVSinkcustom-data slicing now matchesJSONSink(#2400)sv.mask_non_max_mergecomputes exact overlap, ignoresmask_dimension, positionaloverlap_metric/mask_dimensiondeprecated (#2400)Detections.merge()on mixed dense +CompactMaskinputs returnsCompactMask(#2383)
🌱 Changed
DetectionDataset/ClassificationDatasetequality now compares orderedclasseslists, not an unordered setsupervisionnow requiresav>=14.2as an install-time dependency for the cv2-free video fallback — no API change (#2438)- Deprecation-window delays:
ByteTrack,supervision.keypoint,normalized_xyxy, dataset-utils RLE compat removals moved0.30.0→0.31.0 - Perf:
count_nonzeromask pixel counts (#2361), vectorizedbox_iou_batch_with_jaccard(#2359), faster mask-annotation ROI blending (#2368), fewer corner circles on square label backgrounds (#2346), less compact-mask materialization in the polygon annotator (#2369) - Geometry-aware IoU/area dispatch centralized (#2374)
🔧 Fixed
sv.Recalltracks prediction-only classes, matchingPrecision/F1Score(#2467, #2468)DetectionDataset.from_pascal_vocno longer raises on background images, with or withoutforce_masks=True(#2463, #2469)import supervisionno longer surfaces the deprecatedByteTrackwarning- Reopening
sv.CSVSink/sv.JSONSinkstarts a fresh session — no stale rows or header (#2459) from_vlmGemini 2.0/2.5/3.5 salvages valid entries from partially malformed JSON arrays (#2449)save_coco_annotations/as_cocoread image sizes from headers, no pixel decode for labels-only export (#2442)sv.F1Scoreno longer emits a spurious div-by-zeroRuntimeWarning(#2437)- Size-bucketed
Precision/Recall/F1Scoreno longer miscount out-of-bucket detections (#2427, #2428, #2408) sv.box_iou_batchupcasts corners tofloat64, fixing int32-coordinate overflow into a wrong0.0IoU (#2418)from_tensorflowscales boxes by correct axes (#2360);from_inferencestays aligned on partial masks (#2362) and partialtracker_id(#2353)get_anchors_coordinatesis OBB-aware (#2382)- Annotator clipping:
CropAnnotator(#2391),HeatMapAnnotatoruint8 wrap (#2393),BackgroundOverlayAnnotatornegative coords (#2396);get_video_frames_generatorreleases capture via try/finally (#2393) ByteTrackno longer mutates inputDetections; hardened edge cases (#2413)KeyPoints.as_detectionsaccepts numpy/tuple/generator indices (#2402)hex_to_rgbarejects multiple leading#(#2421);Color(...)validates RGBA range (#2407)ColorPalette.by_idx()on empty palette raisesValueError, notZeroDivisionError(#2407)- Metrics scoring hardening: greedy matching (#2380), COCO 101-point AP,
ConfusionMatrixrejects invalid class ids, per-class recall per max-det cutoff, userignoreflags preserved; FP counted on empty-GT images (#2397) - Dataset IO hardening — no caller mutation, class-id validation, optional COCO fields, VOC determinized, basename-collision preflight, RGBA/palette PNG support (#2394, #2410, #2416)
Classifications.from_timmsoftmaxes logits;download_assetsverifies MD5 + retries once (#2414)ImageSink.save_image()raisesOSErroron write failure (#2416)- Replaced deprecated 2-D
np.crosswith explicit determinant (#2386); removed defensive asserts in image annotators (#2354) - cv2-free fallback correctness fixes across border/blend/polygon/text/color operations (#2431, #2433, #2439, #2440, #2441)
🏆 Contributors
- Abhijith Neil Abraham (@abhijithneilabraham, LinkedIn) — added
KeyPoints.merge(); fixed out-of-bucket metric scoring andkey_pointsedge cases - Agis Kounelis (@kounelisagis, LinkedIn) — made
get_anchors_coordinatesOBB-aware; keptfrom_inferencealigned on partial data - Andrew Barnes (@Bortlesboat, LinkedIn) — fixed sink state on reopen
- Arthi Arumugam (@arthi-arumugam-git, LinkedIn) — fixed the Recall metric to track prediction-only classes
- Dylan Parsons (@dylanparsons, LinkedIn) — converted
Detectionsdoctests to runnable examples - Erik (@Erol444) — added
sv.load_image_from_url - Yann Hallouard (@YHallouard, LinkedIn) — added Soft-NMS
- Lee Clement (@leeclemnet) — fixed COCO export to read image sizes from headers
- Linas Kondrackis (@LinasKo, LinkedIn) — added batching to
InferenceSlicer - Madhav-C (@madhavcodez, LinkedIn) — added LabelMe and CreateML dataset formats, GeoTIFF
InferenceSlicersupport - Mahbod (@Ace3Z) — added
prefetchtoget_video_frames_generator,require_all_anchorstoPolygonZone - Matt Van Horn (@mvanhorn, LinkedIn) — centralized geometry-aware IoU/area dispatch
- Murillo Rodrigues (@murillo-ro-silva, LinkedIn) — added
show_progressto dataset load/save (0.29.1) - Nick Herrig (@NickHerrig, LinkedIn) — added the face-blurring cookbook
- Piotr Skalski (@SkalskiP, LinkedIn) — added Gemini 3.5 Flash VLM support
- Ruben (@RubenHaisma) — perf fixes across mask counting, box IoU,
from_tensorflow - Saif Khan (@K-saif, LinkedIn) — added the adaptive TP/FP/FN validation mosaic export
- Shadow_Lu (@LuShadowX) — fixed
class_idto stay integral for VOC background images - shao (@shaoming11, LinkedIn) — improved
draw/utils.pydoctests - Shehzad Waseem (@Shehzad3684) — fixed a division-by-zero warning in
F1Score - Teïlo M (@teilomillet) — fixed the hex parser accepting multiple leading prefixes
- Vikas Saini (@vikassaini77, LinkedIn) — converted fenced examples to doctests; removed defensive asserts in annotators
- Jirka Borovec (@Borda, LinkedIn) — built the cv2-free OpenCV-optional backend (image, geometry, drawing, text, and PyAV video fallback) end to end, plus various hardening fixes across detection, dataset, and metrics modules; release maintainer
Full changelog: 0.29.1...0.30.0