Skip to content

Line Zone

LineZone

This class is responsible for counting the number of objects that cross a predefined line.

Warning

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

Attributes:

Name Type Description
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.

Source code in supervision/detection/line_counter.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
class LineZone:
    """
    This class is responsible for counting the number of objects that cross a
    predefined line.

    !!! warning

        LineZone uses 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,
        triggering_anchors: Iterable[Position] = (
            Position.TOP_LEFT,
            Position.TOP_RIGHT,
            Position.BOTTOM_LEFT,
            Position.BOTTOM_RIGHT,
        ),
    ):
        """
        Args:
            start (Point): The starting point of the line.
            end (Point): The ending point of the line.
            triggering_anchors (List[sv.Position]): A list of positions
                specifying which anchors of the detections bounding box
                to consider when deciding on whether the detection
                has passed the line counter or not. By default, this
                contains the four corners of the detection's bounding box
        """
        self.vector = Vector(start=start, end=end)
        self.limits = self.calculate_region_of_interest_limits(vector=self.vector)
        self.tracker_state: Dict[str, bool] = {}
        self.in_count: int = 0
        self.out_count: int = 0
        self.triggering_anchors = triggering_anchors

    @staticmethod
    def calculate_region_of_interest_limits(vector: Vector) -> Tuple[Vector, Vector]:
        magnitude = vector.magnitude

        if magnitude == 0:
            raise ValueError("The magnitude of the vector cannot be zero.")

        delta_x = vector.end.x - vector.start.x
        delta_y = vector.end.y - vector.start.y

        unit_vector_x = delta_x / magnitude
        unit_vector_y = delta_y / magnitude

        perpendicular_vector_x = -unit_vector_y
        perpendicular_vector_y = unit_vector_x

        start_region_limit = Vector(
            start=vector.start,
            end=Point(
                x=vector.start.x + perpendicular_vector_x,
                y=vector.start.y + perpendicular_vector_y,
            ),
        )
        end_region_limit = Vector(
            start=vector.end,
            end=Point(
                x=vector.end.x - perpendicular_vector_x,
                y=vector.end.y - perpendicular_vector_y,
            ),
        )
        return start_region_limit, end_region_limit

    @staticmethod
    def is_point_in_limits(point: Point, limits: Tuple[Vector, Vector]) -> bool:
        cross_product_1 = limits[0].cross_product(point)
        cross_product_2 = limits[1].cross_product(point)
        return (cross_product_1 > 0) == (cross_product_2 > 0)

    def trigger(self, detections: Detections) -> Tuple[np.ndarray, np.ndarray]:
        """
        Update the `in_count` and `out_count` based on the objects that cross the line.

        Args:
            detections (Detections): A list of detections for which to update the
                counts.

        Returns:
            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_in = np.full(len(detections), False)
        crossed_out = np.full(len(detections), False)

        if len(detections) == 0:
            return crossed_in, crossed_out

        all_anchors = np.array(
            [
                detections.get_anchors_coordinates(anchor)
                for anchor in self.triggering_anchors
            ]
        )

        for i, tracker_id in enumerate(detections.tracker_id):
            if tracker_id is None:
                continue

            box_anchors = [Point(x=x, y=y) for x, y in all_anchors[:, i, :]]

            in_limits = all(
                [
                    self.is_point_in_limits(point=anchor, limits=self.limits)
                    for anchor in box_anchors
                ]
            )

            if not in_limits:
                continue

            triggers = [
                self.vector.cross_product(point=anchor) > 0 for anchor in box_anchors
            ]

            if len(set(triggers)) == 2:
                continue

            tracker_state = triggers[0]

            if tracker_id not in self.tracker_state:
                self.tracker_state[tracker_id] = tracker_state
                continue

            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_in[i] = True
            else:
                self.out_count += 1
                crossed_out[i] = True

        return crossed_in, crossed_out

__init__(start, end, triggering_anchors=(Position.TOP_LEFT, Position.TOP_RIGHT, Position.BOTTOM_LEFT, Position.BOTTOM_RIGHT))

Parameters:

Name Type Description Default
start Point

The starting point of the line.

required
end Point

The ending point of the line.

required
triggering_anchors List[Position]

A list of positions specifying which anchors of the detections bounding box to consider when deciding on whether the detection has passed the line counter or not. By default, this contains the four corners of the detection's bounding box

(TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT)
Source code in supervision/detection/line_counter.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def __init__(
    self,
    start: Point,
    end: Point,
    triggering_anchors: Iterable[Position] = (
        Position.TOP_LEFT,
        Position.TOP_RIGHT,
        Position.BOTTOM_LEFT,
        Position.BOTTOM_RIGHT,
    ),
):
    """
    Args:
        start (Point): The starting point of the line.
        end (Point): The ending point of the line.
        triggering_anchors (List[sv.Position]): A list of positions
            specifying which anchors of the detections bounding box
            to consider when deciding on whether the detection
            has passed the line counter or not. By default, this
            contains the four corners of the detection's bounding box
    """
    self.vector = Vector(start=start, end=end)
    self.limits = self.calculate_region_of_interest_limits(vector=self.vector)
    self.tracker_state: Dict[str, bool] = {}
    self.in_count: int = 0
    self.out_count: int = 0
    self.triggering_anchors = triggering_anchors

trigger(detections)

Update the in_count and out_count based on the objects that cross the line.

Parameters:

Name Type Description Default
detections Detections

A list of detections for which to update the counts.

required

Returns:

Type Description
Tuple[ndarray, ndarray]

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.

Source code in supervision/detection/line_counter.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def trigger(self, detections: Detections) -> Tuple[np.ndarray, np.ndarray]:
    """
    Update the `in_count` and `out_count` based on the objects that cross the line.

    Args:
        detections (Detections): A list of detections for which to update the
            counts.

    Returns:
        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_in = np.full(len(detections), False)
    crossed_out = np.full(len(detections), False)

    if len(detections) == 0:
        return crossed_in, crossed_out

    all_anchors = np.array(
        [
            detections.get_anchors_coordinates(anchor)
            for anchor in self.triggering_anchors
        ]
    )

    for i, tracker_id in enumerate(detections.tracker_id):
        if tracker_id is None:
            continue

        box_anchors = [Point(x=x, y=y) for x, y in all_anchors[:, i, :]]

        in_limits = all(
            [
                self.is_point_in_limits(point=anchor, limits=self.limits)
                for anchor in box_anchors
            ]
        )

        if not in_limits:
            continue

        triggers = [
            self.vector.cross_product(point=anchor) > 0 for anchor in box_anchors
        ]

        if len(set(triggers)) == 2:
            continue

        tracker_state = triggers[0]

        if tracker_id not in self.tracker_state:
            self.tracker_state[tracker_id] = tracker_state
            continue

        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_in[i] = True
        else:
            self.out_count += 1
            crossed_out[i] = True

    return crossed_in, crossed_out

Comments