Skip to content

Detection Smoother

Detection Smoother

A utility class for smoothing detections over multiple frames in video tracking. It maintains a history of detections for each track and provides smoothed predictions based on these histories.

Warning

  • DetectionsSmoother requires the tracker_id for each detection. Refer to Roboflow Trackers for information on integrating tracking into your inference pipeline.
  • This class is not compatible with segmentation models.
Example
import supervision as sv

from ultralytics import YOLO

video_info = sv.VideoInfo.from_video_path(video_path=<SOURCE_FILE_PATH>)
frame_generator = sv.get_video_frames_generator(source_path=<SOURCE_FILE_PATH>)

model = YOLO(<MODEL_PATH>)
tracker = sv.ByteTrack(frame_rate=video_info.fps)
smoother = sv.DetectionsSmoother()

annotator = sv.BoundingBoxAnnotator()

with sv.VideoSink(<TARGET_FILE_PATH>, video_info=video_info) as sink:
    for frame in frame_generator:
        result = model(frame)[0]
        detections = sv.Detections.from_ultralytics(result)
        detections = tracker.update_with_detections(detections)
        detections = smoother.update_with_detections(detections)

        annotated_frame = bounding_box_annotator.annotate(frame.copy(), detections)
        sink.write_frame(annotated_frame)
Source code in supervision/detection/tools/smoother.py
 10
 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
class DetectionsSmoother:
    """
    A utility class for smoothing detections over multiple frames in video tracking.
    It maintains a history of detections for each track and provides smoothed
    predictions based on these histories.

    <video controls>
        <source
            src="https://media.roboflow.com/supervision-detection-smoothing.mp4"
            type="video/mp4">
    </video>

    !!! warning

        - `DetectionsSmoother` requires the `tracker_id` for each detection. Refer to
          [Roboflow Trackers](https://supervision.roboflow.com/trackers/) for
          information on integrating tracking into your inference pipeline.
        - This class is not compatible with segmentation models.

    Example:
        ```python
        import supervision as sv

        from ultralytics import YOLO

        video_info = sv.VideoInfo.from_video_path(video_path=<SOURCE_FILE_PATH>)
        frame_generator = sv.get_video_frames_generator(source_path=<SOURCE_FILE_PATH>)

        model = YOLO(<MODEL_PATH>)
        tracker = sv.ByteTrack(frame_rate=video_info.fps)
        smoother = sv.DetectionsSmoother()

        annotator = sv.BoundingBoxAnnotator()

        with sv.VideoSink(<TARGET_FILE_PATH>, video_info=video_info) as sink:
            for frame in frame_generator:
                result = model(frame)[0]
                detections = sv.Detections.from_ultralytics(result)
                detections = tracker.update_with_detections(detections)
                detections = smoother.update_with_detections(detections)

                annotated_frame = bounding_box_annotator.annotate(frame.copy(), detections)
                sink.write_frame(annotated_frame)
        ```
    """  # noqa: E501 // docs

    def __init__(self, length: int = 5) -> None:
        """
        Args:
            length (int): The maximum number of frames to consider for smoothing
                detections. Defaults to 5.
        """
        self.tracks = defaultdict(lambda: deque(maxlen=length))

    def update_with_detections(self, detections: Detections) -> Detections:
        """
        Updates the smoother with a new set of detections from a frame.

        Args:
            detections (Detections): The detections to add to the smoother.
        """

        if detections.tracker_id is None:
            print(
                "Smoothing skipped. DetectionsSmoother requires tracker_id. Refer to "
                "https://supervision.roboflow.com/trackers for more information."
            )
            return detections

        for detection_idx in range(len(detections)):
            tracker_id = detections.tracker_id[detection_idx]
            if tracker_id is None:
                continue

            self.tracks[tracker_id].append(detections[detection_idx])

        for track_id in self.tracks.keys():
            if track_id not in detections.tracker_id:
                self.tracks[track_id].append(None)

        for track_id in list(self.tracks.keys()):
            if all([d is None for d in self.tracks[track_id]]):
                del self.tracks[track_id]

        return self.get_smoothed_detections()

    def get_track(self, track_id: int) -> Optional[Detections]:
        track = self.tracks.get(track_id, None)
        if track is None:
            return None

        track = [d for d in track if d is not None]
        if len(track) == 0:
            return None

        ret = deepcopy(track[0])
        ret.xyxy = np.mean([d.xyxy for d in track], axis=0)
        ret.confidence = np.mean([d.confidence for d in track], axis=0)

        return ret

    def get_smoothed_detections(self) -> Detections:
        tracked_detections = []
        for track_id in self.tracks:
            track = self.get_track(track_id)
            if track is not None:
                tracked_detections.append(track)

        return Detections.merge(tracked_detections)

__init__(length=5)

Parameters:

Name Type Description Default
length int

The maximum number of frames to consider for smoothing detections. Defaults to 5.

5
Source code in supervision/detection/tools/smoother.py
56
57
58
59
60
61
62
def __init__(self, length: int = 5) -> None:
    """
    Args:
        length (int): The maximum number of frames to consider for smoothing
            detections. Defaults to 5.
    """
    self.tracks = defaultdict(lambda: deque(maxlen=length))

update_with_detections(detections)

Updates the smoother with a new set of detections from a frame.

Parameters:

Name Type Description Default
detections Detections

The detections to add to the smoother.

required
Source code in supervision/detection/tools/smoother.py
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
def update_with_detections(self, detections: Detections) -> Detections:
    """
    Updates the smoother with a new set of detections from a frame.

    Args:
        detections (Detections): The detections to add to the smoother.
    """

    if detections.tracker_id is None:
        print(
            "Smoothing skipped. DetectionsSmoother requires tracker_id. Refer to "
            "https://supervision.roboflow.com/trackers for more information."
        )
        return detections

    for detection_idx in range(len(detections)):
        tracker_id = detections.tracker_id[detection_idx]
        if tracker_id is None:
            continue

        self.tracks[tracker_id].append(detections[detection_idx])

    for track_id in self.tracks.keys():
        if track_id not in detections.tracker_id:
            self.tracks[track_id].append(None)

    for track_id in list(self.tracks.keys()):
        if all([d is None for d in self.tracks[track_id]]):
            del self.tracks[track_id]

    return self.get_smoothed_detections()

Comments