diff --git a/docs/detection/tools/line_zone.md b/docs/detection/tools/line_zone.md new file mode 100644 index 00000000..cbecdaa9 --- /dev/null +++ b/docs/detection/tools/line_zone.md @@ -0,0 +1,3 @@ +## LineZone + +:::supervision.detection.line_counter.LineZone diff --git a/mkdocs.yml b/mkdocs.yml index 276c85dc..f511cb2f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,6 +39,7 @@ nav: - Core: detection/core.md - Utils: detection/utils.md - Tools: + - Line Zone: detection/tools/line_zone.md - Polygon Zone: detection/tools/polygon_zone.md - Inference Slicer: detection/tools/inference_slicer.md - Annotators: annotators.md diff --git a/supervision/detection/line_counter.py b/supervision/detection/line_counter.py index c668354e..851d1d4a 100644 --- a/supervision/detection/line_counter.py +++ b/supervision/detection/line_counter.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional +from typing import Dict, Optional, Tuple import cv2 import numpy as np @@ -10,42 +10,54 @@ from supervision.geometry.core import Point, Rect, Vector class LineZone: """ - Count the number of objects that cross a line. + This class is responsible for counting the number of objects that cross a + predefined line. + + !!! warning + + LineZone utilizes the `tracker_id`. Read + [here](https://supervision.roboflow.com/trackers/) to learn how to plug + tracking into your inference pipeline. + + Attributes: + in_count (int): The number of objects that have crossed the line from outside + to inside. + out_count (int): The number of objects that have crossed the line from inside + to outside. """ def __init__(self, start: Point, end: Point): """ - Initialize a LineCounter object. - - Attributes: + Args: start (Point): The starting point of the line. end (Point): The ending point of the line. - """ self.vector = Vector(start=start, end=end) self.tracker_state: Dict[str, bool] = {} self.in_count: int = 0 self.out_count: int = 0 - def trigger(self, detections: Detections) -> np.ndarray: + def trigger(self, detections: Detections) -> Tuple[np.ndarray, np.ndarray]: """ - Update the in_count and out_count for the detections that cross the line. + Update the `in_count` and `out_count` based on the objects that cross the line. - Attributes: - detections (Detections): The detections for which to update the counts. + Args: + detections (Detections): A list of detections for which to update the + counts. Returns: - np.ndarray: A boolean array indicating - which detection has crossed the line on the either sides + A tuple of two boolean NumPy arrays. The first array indicates which + detections have crossed the line from outside to inside. The second + array indicates which detections have crossed the line from inside to + outside. """ - crossed = np.full(len(detections), False) + crossed_in = np.full(len(detections), False) + crossed_out = np.full(len(detections), False) for i, (xyxy, _, confidence, class_id, tracker_id) in enumerate(detections): - # handle detections with no tracker_id if tracker_id is None: continue - # we check if all four anchors of bbox are on the same side of vector x1, y1, x2, y2 = xyxy anchors = [ Point(x=x1, y=y1), @@ -55,30 +67,27 @@ class LineZone: ] triggers = [self.vector.is_in(point=anchor) for anchor in anchors] - # detection is partially in and partially out if len(set(triggers)) == 2: continue tracker_state = triggers[0] - # handle new detection + if tracker_id not in self.tracker_state: self.tracker_state[tracker_id] = tracker_state continue - # handle detection on the same side of the line if self.tracker_state.get(tracker_id) == tracker_state: continue self.tracker_state[tracker_id] = tracker_state if tracker_state: self.in_count += 1 - crossed[i] = True - + crossed_in[i] = True else: self.out_count += 1 - crossed[i] = True + crossed_out[i] = True - return crossed + return crossed_in, crossed_out class LineZoneAnnotator: