Skip to content

Polygon Zone

PolygonZone

supervision.detection.tools.polygon_zone.PolygonZone

A class for defining a polygon-shaped zone within a frame for detecting objects.

Warning

PolygonZone uses the tracker_id. Read here to learn how to plug tracking into your inference pipeline.

Attributes:

Name Type Description
polygon

A polygon represented by a numpy array of shape (N, 2), containing the x, y coordinates of the points.

triggering_anchors

A list of positions specifying which anchors of the detections bounding box to consider when deciding on whether the detection fits within the PolygonZone (default: (sv.Position.BOTTOM_CENTER,)).

current_count

The current count of detected objects within the zone

mask

The 2D bool mask for the polygon zone

Example
import supervision as sv
from ultralytics import YOLO
import numpy as np
import cv2

image = cv2.imread("<SOURCE_IMAGE_PATH>")
model = YOLO("yolo11s")
tracker = sv.ByteTrack()

polygon = np.array([[100, 200], [200, 100], [300, 200], [200, 300]])
polygon_zone = sv.PolygonZone(polygon=polygon)

result = model.infer(image)[0]
detections = sv.Detections.from_ultralytics(result)
detections = tracker.update_with_detections(detections)

is_detections_in_zone = polygon_zone.trigger(detections)
print(polygon_zone.current_count)
Source code in src/supervision/detection/tools/polygon_zone.py
class PolygonZone:
    """
    A class for defining a polygon-shaped zone within a frame for detecting objects.

    !!! warning

        PolygonZone uses the `tracker_id`. Read
        [here](/latest/trackers/) to learn how to plug
        tracking into your inference pipeline.

    Attributes:
        polygon: A polygon represented by a numpy array of shape
            `(N, 2)`, containing the `x`, `y` coordinates of the points.
        triggering_anchors: A list of positions specifying
            which anchors of the detections bounding box to consider when deciding on
            whether the detection fits within the PolygonZone
            (default: (sv.Position.BOTTOM_CENTER,)).
        current_count: The current count of detected objects within the zone
        mask: The 2D bool mask for the polygon zone

    Example:
        ```python
        import supervision as sv
        from ultralytics import YOLO
        import numpy as np
        import cv2

        image = cv2.imread("<SOURCE_IMAGE_PATH>")
        model = YOLO("yolo11s")
        tracker = sv.ByteTrack()

        polygon = np.array([[100, 200], [200, 100], [300, 200], [200, 300]])
        polygon_zone = sv.PolygonZone(polygon=polygon)

        result = model.infer(image)[0]
        detections = sv.Detections.from_ultralytics(result)
        detections = tracker.update_with_detections(detections)

        is_detections_in_zone = polygon_zone.trigger(detections)
        print(polygon_zone.current_count)
        ```
    """

    def __init__(
        self,
        polygon: npt.NDArray[np.int64],
        triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,),
    ):
        self.polygon = polygon.astype(int)
        self.triggering_anchors = triggering_anchors
        if not list(self.triggering_anchors):
            raise ValueError("Triggering anchors cannot be empty.")

        self.current_count = 0

        x_max, y_max = np.max(polygon, axis=0)
        self.mask = polygon_to_mask(
            polygon=polygon, resolution_wh=(x_max + 2, y_max + 2)
        )

    def trigger(self, detections: Detections) -> npt.NDArray[np.bool_]:
        """
        Determines if the detections are within the polygon zone.

        Anchor points are calculated from original (unclipped) detection boxes to
        avoid per-zone clipping shifting anchor positions. This prevents a single
        detection from being counted in multiple non-overlapping zones due to
        clipping artifacts, although overlapping zones may still legitimately
        contain the same detection.

        Args:
            detections: The detections to be checked against the polygon zone

        Returns:
            A boolean numpy array indicating
                if each detection is within the polygon zone
        """
        if len(detections) == 0:
            self.current_count = 0
            return np.array([], dtype=bool)

        all_anchors = np.array(
            [
                np.ceil(detections.get_anchors_coordinates(anchors)).astype(int)
                for anchors in self.triggering_anchors
            ]
        )

        mask_h, mask_w = self.mask.shape
        x, y = all_anchors[:, :, 0], all_anchors[:, :, 1]
        in_bounds = (x >= 0) & (y >= 0) & (x < mask_w) & (y < mask_h)
        x_safe = np.clip(x, 0, mask_w - 1)
        y_safe = np.clip(y, 0, mask_h - 1)
        is_in_zone = np.all(in_bounds & self.mask[y_safe, x_safe], axis=0)
        self.current_count = int(np.sum(is_in_zone))
        return is_in_zone.astype(bool)

Functions

trigger(detections: Detections) -> npt.NDArray[np.bool_]

Determines if the detections are within the polygon zone.

Anchor points are calculated from original (unclipped) detection boxes to avoid per-zone clipping shifting anchor positions. This prevents a single detection from being counted in multiple non-overlapping zones due to clipping artifacts, although overlapping zones may still legitimately contain the same detection.

Parameters:

Name Type Description Default
detections
Detections

The detections to be checked against the polygon zone

required

Returns:

Type Description
NDArray[bool_]

A boolean numpy array indicating if each detection is within the polygon zone

Source code in src/supervision/detection/tools/polygon_zone.py
def trigger(self, detections: Detections) -> npt.NDArray[np.bool_]:
    """
    Determines if the detections are within the polygon zone.

    Anchor points are calculated from original (unclipped) detection boxes to
    avoid per-zone clipping shifting anchor positions. This prevents a single
    detection from being counted in multiple non-overlapping zones due to
    clipping artifacts, although overlapping zones may still legitimately
    contain the same detection.

    Args:
        detections: The detections to be checked against the polygon zone

    Returns:
        A boolean numpy array indicating
            if each detection is within the polygon zone
    """
    if len(detections) == 0:
        self.current_count = 0
        return np.array([], dtype=bool)

    all_anchors = np.array(
        [
            np.ceil(detections.get_anchors_coordinates(anchors)).astype(int)
            for anchors in self.triggering_anchors
        ]
    )

    mask_h, mask_w = self.mask.shape
    x, y = all_anchors[:, :, 0], all_anchors[:, :, 1]
    in_bounds = (x >= 0) & (y >= 0) & (x < mask_w) & (y < mask_h)
    x_safe = np.clip(x, 0, mask_w - 1)
    y_safe = np.clip(y, 0, mask_h - 1)
    is_in_zone = np.all(in_bounds & self.mask[y_safe, x_safe], axis=0)
    self.current_count = int(np.sum(is_in_zone))
    return is_in_zone.astype(bool)

PolygonZoneAnnotator

supervision.detection.tools.polygon_zone.PolygonZoneAnnotator

A class for annotating a polygon-shaped zone within a frame with a count of detected objects.

Attributes:

Name Type Description
zone

The polygon zone to be annotated

color

The color to draw the polygon lines, default is white

thickness

The thickness of the polygon lines, default is 2

text_color

The color of the text on the polygon, default is black

text_scale

The scale of the text on the polygon, default is 0.5

text_thickness

The thickness of the text on the polygon, default is 1

text_padding

The padding around the text on the polygon, default is 10

font

The font type for the text on the polygon, default is cv2.FONT_HERSHEY_SIMPLEX

center

The center of the polygon for text placement

display_in_zone_count

Show the label of the zone or not. Default is True

opacity

The opacity of zone filling when drawn on the scene. Default is 0

Source code in src/supervision/detection/tools/polygon_zone.py
class PolygonZoneAnnotator:
    """
    A class for annotating a polygon-shaped zone within a frame with a count of
    detected objects.

    Attributes:
        zone: The polygon zone to be annotated
        color: The color to draw the polygon lines, default is white
        thickness: The thickness of the polygon lines, default is 2
        text_color: The color of the text on the polygon, default is black
        text_scale: The scale of the text on the polygon, default is 0.5
        text_thickness: The thickness of the text on the polygon, default is 1
        text_padding: The padding around the text on the polygon, default is 10
        font: The font type for the text on the polygon,
            default is cv2.FONT_HERSHEY_SIMPLEX
        center: The center of the polygon for text placement
        display_in_zone_count: Show the label of the zone or not. Default is True
        opacity: The opacity of zone filling when drawn on the scene. Default is 0
    """

    def __init__(
        self,
        zone: PolygonZone,
        color: Color = Color.WHITE,
        thickness: int = 2,
        text_color: Color = Color.BLACK,
        text_scale: float = 0.5,
        text_thickness: int = 1,
        text_padding: int = 10,
        display_in_zone_count: bool = True,
        opacity: float = 0,
    ):
        self.zone = zone
        self.color = color
        self.thickness = thickness
        self.text_color = text_color
        self.text_scale = text_scale
        self.text_thickness = text_thickness
        self.text_padding = text_padding
        self.font = cv2.FONT_HERSHEY_SIMPLEX
        self.center = get_polygon_center(polygon=zone.polygon)
        self.display_in_zone_count = display_in_zone_count
        self.opacity = opacity

    def annotate(
        self, scene: npt.NDArray[Any], label: str | None = None
    ) -> npt.NDArray[Any]:
        """
        Annotates the polygon zone within a frame with a count of detected objects.

        Args:
            scene: The image on which the polygon zone will be annotated
            label: A label for the count of detected objects
                within the polygon zone (default: None)

        Returns:
            The image with the polygon zone and count of detected objects
        """
        if self.opacity == 0:
            annotated_frame = draw_polygon(
                scene=scene,
                polygon=self.zone.polygon,
                color=self.color,
                thickness=self.thickness,
            )
        else:
            annotated_frame = draw_filled_polygon(
                scene=scene.copy(),
                polygon=self.zone.polygon,
                color=self.color,
                opacity=self.opacity,
            )
            annotated_frame = draw_polygon(
                scene=annotated_frame,
                polygon=self.zone.polygon,
                color=self.color,
                thickness=self.thickness,
            )

        if self.display_in_zone_count:
            annotated_frame = draw_text(
                scene=annotated_frame,
                text=str(self.zone.current_count) if label is None else label,
                text_anchor=self.center,
                background_color=self.color,
                text_color=self.text_color,
                text_scale=self.text_scale,
                text_thickness=self.text_thickness,
                text_padding=self.text_padding,
                text_font=self.font,
            )

        return cast(npt.NDArray[Any], annotated_frame)

Functions

annotate(scene: npt.NDArray[Any], label: str | None = None) -> npt.NDArray[Any]

Annotates the polygon zone within a frame with a count of detected objects.

Parameters:

Name Type Description Default
scene
NDArray[Any]

The image on which the polygon zone will be annotated

required
label
str | None

A label for the count of detected objects within the polygon zone (default: None)

None

Returns:

Type Description
NDArray[Any]

The image with the polygon zone and count of detected objects

Source code in src/supervision/detection/tools/polygon_zone.py
def annotate(
    self, scene: npt.NDArray[Any], label: str | None = None
) -> npt.NDArray[Any]:
    """
    Annotates the polygon zone within a frame with a count of detected objects.

    Args:
        scene: The image on which the polygon zone will be annotated
        label: A label for the count of detected objects
            within the polygon zone (default: None)

    Returns:
        The image with the polygon zone and count of detected objects
    """
    if self.opacity == 0:
        annotated_frame = draw_polygon(
            scene=scene,
            polygon=self.zone.polygon,
            color=self.color,
            thickness=self.thickness,
        )
    else:
        annotated_frame = draw_filled_polygon(
            scene=scene.copy(),
            polygon=self.zone.polygon,
            color=self.color,
            opacity=self.opacity,
        )
        annotated_frame = draw_polygon(
            scene=annotated_frame,
            polygon=self.zone.polygon,
            color=self.color,
            thickness=self.thickness,
        )

    if self.display_in_zone_count:
        annotated_frame = draw_text(
            scene=annotated_frame,
            text=str(self.zone.current_count) if label is None else label,
            text_anchor=self.center,
            background_color=self.color,
            text_color=self.text_color,
            text_scale=self.text_scale,
            text_thickness=self.text_thickness,
            text_padding=self.text_padding,
            text_font=self.font,
        )

    return cast(npt.NDArray[Any], annotated_frame)

Comments