How to Use PolygonZone for Filtering Detections in Custom Regions
PolygonZone is a lightweight utility in the roboflow/supervision library that defines arbitrary polygonal regions on video frames and filters object detections by testing whether specified anchor points fall inside the defined zone.
Working with computer vision models often requires isolating objects within specific regions of interest, such as counting vehicles in a parking lot or monitoring activity in a retail aisle. The PolygonZone class in the roboflow/supervision repository provides an efficient way to define custom polygonal boundaries and filter Detections objects based on their spatial relationship to these zones. This utility integrates directly with the core detection pipeline defined in supervision/detection/__init__.py, leveraging anchor point geometry to determine inclusion.
Creating a Polygon Zone
Define custom regions by instantiating PolygonZone with a NumPy array of (x, y) vertices. The constructor in src/supervision/detection/tools/polygon_zone.py (lines 61-77) accepts two primary arguments: polygon and triggering_anchors.
The polygon parameter requires a NumPy array of shape (N, 2) containing the vertex coordinates. The triggering_anchors parameter accepts a tuple of sv.Position enum values specifying which points of each bounding box to test against the zone mask. If this iterable is empty, the constructor raises a ValueError as verified in tests/detection/test_polygonzone.py. Internally, the class converts the polygon to a binary mask using polygon_to_mask from src/supervision/detection/utils/converters.py for efficient point-in-polygon testing.
import numpy as np
import supervision as sv
# Define a quadrilateral zone
polygon = np.array([[100, 100], [400, 80], [350, 300], [120, 320]])
# Use default BOTTOM_CENTER anchor (ideal for counting objects on the ground)
zone = sv.PolygonZone(polygon=polygon)
# Or specify multiple anchor points (e.g., top-left and bottom-right corners)
zone_multi = sv.PolygonZone(
polygon=polygon,
triggering_anchors=(sv.Position.TOP_LEFT, sv.Position.BOTTOM_RIGHT)
)
Filtering Detections with the trigger Method
Call zone.trigger(detections) to obtain a boolean mask indicating which detections fall inside the zone. Implemented in polygon_zone.py (lines 78-113), this method extracts anchor coordinates via the Detections.get_anchors_coordinates method, clips them to the mask bounds (x_safe and y_safe), and checks against the pre-computed binary mask (self.mask[y_safe, x_safe]).
The method returns a boolean NumPy array of the same length as the input detections. It also updates the zone.current_count attribute with the total number of detections currently inside the zone.
# detections is an sv.Detections object from a model inference
inside_mask = zone.trigger(detections) # Returns bool[np.ndarray]
filtered_detections = detections[inside_mask]
print(f"Objects in zone: {zone.current_count}")
print(f"Inside mask: {inside_mask}")
Visualizing Zones with PolygonZoneAnnotator
Render polygon boundaries and real-time counts using PolygonZoneAnnotator, defined in the same polygon_zone.py file. This class draws filled or outlined polygons using helper functions draw_filled_polygon and draw_polygon from src/supervision/draw/utils.py.
Configure the visual appearance with parameters like color, opacity, and thickness. The annotator automatically displays the current_count value on the frame.
annotator = sv.PolygonZoneAnnotator(
zone=zone,
color=sv.Color.RED,
opacity=0.2,
thickness=2
)
# Apply visualization to a frame (NumPy array)
annotated_frame = annotator.annotate(frame)
Complete Working Example
The following example integrates PolygonZone with Ultralytics YOLO to filter detections within a defined region of a parking lot or room corner.
import cv2
import numpy as np
import supervision as sv
from ultralytics import YOLO
# Load image and run inference
image = cv2.imread("example.jpg")
model = YOLO("yolo11s")
result = model(image)[0]
# Convert model output to supervision Detections
detections = sv.Detections.from_ultralytics(result)
# Define a custom polygon zone (e.g., a specific parking spot or counter area)
polygon = np.array([[100, 100], [400, 80], [350, 300], [120, 320]])
zone = sv.PolygonZone(
polygon=polygon,
triggering_anchors=(sv.Position.BOTTOM_CENTER, sv.Position.BOTTOM_LEFT)
)
# Filter detections inside the zone
inside_mask = zone.trigger(detections)
filtered = detections[inside_mask]
print(f"Total objects detected: {len(detections)}")
print(f"Objects in zone: {zone.current_count}")
# Visualize results
zone_annotator = sv.PolygonZoneAnnotator(zone, color=sv.Color.GREEN, opacity=0.1)
box_annotator = sv.BoundingBoxAnnotator()
frame = zone_annotator.annotate(image.copy())
frame = box_annotator.annotate(scene=frame, detections=filtered)
cv2.imshow("Zone-filtered view", frame)
cv2.waitKey(0)
Summary
- PolygonZone in
src/supervision/detection/tools/polygon_zone.pydefines custom regions using NumPy vertex arrays and converts them to binary masks viapolygon_to_maskfromsrc/supervision/detection/utils/converters.py. - The
trigger()method returns a boolean mask indicating which detections fall inside the zone based ontriggering_anchors(defaulting tosv.Position.BOTTOM_CENTER). - The
current_countproperty automatically tracks the number of detections inside the zone after eachtriggercall. - PolygonZoneAnnotator renders zones and counts on frames using drawing utilities from
src/supervision/draw/utils.py. - Providing an empty
triggering_anchorsiterable raises aValueErrorduring initialization.
Frequently Asked Questions
What anchor positions does PolygonZone support?
PolygonZone supports any combination of positions from the sv.Position enum, including TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER, and BOTTOM_CENTER (the default). You can pass a tuple of multiple positions to triggering_anchors to require any of the specified points to fall inside the zone for a detection to count.
How does PolygonZone handle coordinates outside the frame boundaries?
The trigger method automatically clips coordinates to the mask bounds before checking values (x_safe and y_safe indexing), preventing index errors when detections extend beyond the defined polygon area or frame edges. This clipping ensures robust operation even with partial detections at image boundaries.
Can I use multiple PolygonZones simultaneously on the same frame?
Yes, you can instantiate multiple PolygonZone objects with different polygons and call trigger on each independently using the same Detections object. Each zone maintains its own current_count and binary mask, allowing you to monitor separate regions (such as counting people in different aisles) within a single inference pipeline.
How do I persist polygon coordinates between sessions?
Store the NumPy array of vertices to a file (JSON, CSV, or NumPy binary format) and reload it before initializing PolygonZone. The class expects a NumPy array of shape (N, 2) where each row contains an (x, y) coordinate pair, making it compatible with coordinates captured from annotation tools or manual UI selection.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →