From b912db7a0632ab383e9f0290142cfdeb72586186 Mon Sep 17 00:00:00 2001 From: Brad Dwyer Date: Wed, 27 Dec 2023 14:13:40 -0600 Subject: [PATCH 01/23] Add Detections Smoother --- docs/detection/smoother.md | 7 ++ supervision/__init__.py | 1 + supervision/detection/smoother.py | 118 ++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 docs/detection/smoother.md create mode 100644 supervision/detection/smoother.py diff --git a/docs/detection/smoother.md b/docs/detection/smoother.md new file mode 100644 index 00000000..ed5523b5 --- /dev/null +++ b/docs/detection/smoother.md @@ -0,0 +1,7 @@ +## Smoother + + + +:::supervision.detection.smoother.Smoother diff --git a/supervision/__init__.py b/supervision/__init__.py index 85b07850..7e34c0f4 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -32,6 +32,7 @@ from supervision.dataset.core import ( ) from supervision.detection.annotate import BoxAnnotator from supervision.detection.core import Detections +from supervision.detection.smoother import Smoother from supervision.detection.line_counter import LineZone, LineZoneAnnotator from supervision.detection.tools.inference_slicer import InferenceSlicer from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator diff --git a/supervision/detection/smoother.py b/supervision/detection/smoother.py new file mode 100644 index 00000000..fb810121 --- /dev/null +++ b/supervision/detection/smoother.py @@ -0,0 +1,118 @@ +from collections import defaultdict +import numpy as np +from supervision.detection.core import Detections + +class Smoother: + """ + A class for smoothing out noise in predictions over time by using a Tracker + to track objects over time and averaging out the predictions over the + `length` most recent frames. + + !!! warning + + Smoother utilizes the `tracker_id`. Read + [here](https://supervision.roboflow.com/trackers/) to learn how to plug + tracking into your inference pipeline. + """ + + def __init__( + self, + length: int = 5 + ) -> None: + """ + Args: + length (int): The current count of detected objects within the zone + """ + + self.length = length + + self.current_frame = 0 + self.tracks = NoneDict() + self.track_starts = NoneDict() + self.track_ends = NoneDict() + + def tracker_length(self, tracker_id): + return self.current_frame - self.track_starts[tracker_id] + + def add_frame(self, detections: Detections) -> None: + """ + Adds a new set of predictions to the smoother. Run this with every new + prediction received from the model. + + Args: + detections (Detections): The detections to add to the smoother. + """ + + self.current_frame += 1 + + for detection_idx in range(len(detections)): + tracker_id = detections.tracker_id[detection_idx] + if tracker_id is None: + # skip detections without a tracker id + continue + + if self.tracks[tracker_id] is None: + # initialize a new tracker_id + self.tracks[tracker_id] = [] + self.track_starts[tracker_id] = self.current_frame + + self.tracks[tracker_id].append(detections[detection_idx]) + self.track_ends[tracker_id] = self.current_frame + + for track_id in self.tracks: + track = self.tracks[track_id] + if self.track_ends[track_id] < self.current_frame: + # continue tracking for a few frames after the object has left + # (to prevent flickering in case it comes back) + track.append(None) + + if len(track) > self.length: + # remove the oldest detection from the track it's too long + track.pop(0) + + def get_track(self, track_id): + track = self.tracks[track_id] + if track is None: + return None + + track = [d for d in track if d is not None] + if len(track) == 0: + return None + + ret = track[0] + # set to an average of all the detection boxes + 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): + """ + Returns a smoothed set of predictions based on the `length` most recent frames. + + Returns: + detections (Detections): The smoothed 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) + +class NoneDict(defaultdict): + """ + Helper class that returns None instead of raising a KeyError + when a key is not found. + """ + + def __init__(self, *args, **kwargs): + super(NoneDict, self).__init__(None, *args, **kwargs) + + def __getitem__(self, key): + try: + return super(NoneDict, self).__getitem__(key) + except KeyError: + return None \ No newline at end of file From ffdab06376893c42340b8ed5cf85d7c3b883a343 Mon Sep 17 00:00:00 2001 From: Brad Dwyer Date: Wed, 27 Dec 2023 15:02:02 -0600 Subject: [PATCH 02/23] Update Docs --- mkdocs.yml | 1 + supervision/detection/smoother.py | 48 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index c7c58f9e..4cb23161 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,6 +37,7 @@ nav: - Core: classification/core.md - Detections: - Core: detection/core.md + - Smoother: detection/smoother.md - Utils: detection/utils.md - Tools: - Line Zone: detection/tools/line_zone.md diff --git a/supervision/detection/smoother.py b/supervision/detection/smoother.py index fb810121..5559903d 100644 --- a/supervision/detection/smoother.py +++ b/supervision/detection/smoother.py @@ -8,6 +8,54 @@ class Smoother: to track objects over time and averaging out the predictions over the `length` most recent frames. + + > _On the left are the model's raw predictions, on the right is the output of Smoother._ + + ## Example Usage: + + ```python + import cv2 + # remember to `pip install inference` + from inference import InferencePipeline + import supervision as sv + + box_annotator = sv.BoxAnnotator(color=sv.Color(52, 236, 217)) + byte_tracker = sv.ByteTrack() + + # Initialize the Smoother + smoother = sv.Smoother() + + def render(detections, video_frame): + # Parse the detections + detections = sv.Detections.from_roboflow(detections) + + # Run a tracker to link predictions across frames + detections = byte_tracker.update_with_detections(detections) + + # Record the new frame and get the smoothed predictions + smoother.add_frame(detections) + smoothed_detections = smoother.get_smoothed_detections() + + # Render + image_smoothed = box_annotator.annotate(scene=image.copy(), detections=smoothed_detections) + + # Visualize + cv2.imshow("Prediction", image) + cv2.waitKey(1) + + + pipeline = InferencePipeline.init( + model_id="microsoft-coco/9", # Or put your custom trained model here + # api_key="YOUR_ROBOFLOW_KEY", # Uncomment and fill if you want to access a model that requires auth (or setup a .env file) + video_reference=0, # Webcam; can also be video path or RTSP stream + on_prediction=render + ) + pipeline.start() + pipeline.join() + ``` + !!! warning Smoother utilizes the `tracker_id`. Read From 7f4b27a556cbd604baf2c42a27acf588d39f866e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 27 Dec 2023 21:09:25 +0000 Subject: [PATCH 03/23] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/__init__.py | 2 +- supervision/detection/smoother.py | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/supervision/__init__.py b/supervision/__init__.py index 7e34c0f4..ef9327df 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -32,8 +32,8 @@ from supervision.dataset.core import ( ) from supervision.detection.annotate import BoxAnnotator from supervision.detection.core import Detections -from supervision.detection.smoother import Smoother from supervision.detection.line_counter import LineZone, LineZoneAnnotator +from supervision.detection.smoother import Smoother from supervision.detection.tools.inference_slicer import InferenceSlicer from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator from supervision.detection.utils import ( diff --git a/supervision/detection/smoother.py b/supervision/detection/smoother.py index 5559903d..21c9101b 100644 --- a/supervision/detection/smoother.py +++ b/supervision/detection/smoother.py @@ -1,7 +1,10 @@ from collections import defaultdict + import numpy as np + from supervision.detection.core import Detections + class Smoother: """ A class for smoothing out noise in predictions over time by using a Tracker @@ -33,11 +36,11 @@ class Smoother: # Run a tracker to link predictions across frames detections = byte_tracker.update_with_detections(detections) - + # Record the new frame and get the smoothed predictions smoother.add_frame(detections) smoothed_detections = smoother.get_smoothed_detections() - + # Render image_smoothed = box_annotator.annotate(scene=image.copy(), detections=smoothed_detections) @@ -63,10 +66,7 @@ class Smoother: tracking into your inference pipeline. """ - def __init__( - self, - length: int = 5 - ) -> None: + def __init__(self, length: int = 5) -> None: """ Args: length (int): The current count of detected objects within the zone @@ -103,21 +103,21 @@ class Smoother: # initialize a new tracker_id self.tracks[tracker_id] = [] self.track_starts[tracker_id] = self.current_frame - + self.tracks[tracker_id].append(detections[detection_idx]) self.track_ends[tracker_id] = self.current_frame - + for track_id in self.tracks: track = self.tracks[track_id] if self.track_ends[track_id] < self.current_frame: # continue tracking for a few frames after the object has left # (to prevent flickering in case it comes back) track.append(None) - + if len(track) > self.length: # remove the oldest detection from the track it's too long track.pop(0) - + def get_track(self, track_id): track = self.tracks[track_id] if track is None: @@ -126,12 +126,12 @@ class Smoother: track = [d for d in track if d is not None] if len(track) == 0: return None - + ret = track[0] # set to an average of all the detection boxes 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): @@ -147,9 +147,10 @@ class Smoother: track = self.get_track(track_id) if track is not None: tracked_detections.append(track) - + return Detections.merge(tracked_detections) + class NoneDict(defaultdict): """ Helper class that returns None instead of raising a KeyError @@ -163,4 +164,4 @@ class NoneDict(defaultdict): try: return super(NoneDict, self).__getitem__(key) except KeyError: - return None \ No newline at end of file + return None From ca6ba303759dc4b8c50b49fcd110555fcb4cb9e3 Mon Sep 17 00:00:00 2001 From: Brad Dwyer Date: Wed, 27 Dec 2023 15:15:18 -0600 Subject: [PATCH 04/23] Fix linter errors --- supervision/detection/smoother.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/supervision/detection/smoother.py b/supervision/detection/smoother.py index 5559903d..0d58f6dd 100644 --- a/supervision/detection/smoother.py +++ b/supervision/detection/smoother.py @@ -11,7 +11,8 @@ class Smoother: - > _On the left are the model's raw predictions, on the right is the output of Smoother._ + > _On the left are the model's raw predictions, + > on the right is the output of Smoother._ ## Example Usage: @@ -39,7 +40,10 @@ class Smoother: smoothed_detections = smoother.get_smoothed_detections() # Render - image_smoothed = box_annotator.annotate(scene=image.copy(), detections=smoothed_detections) + image_smoothed = box_annotator.annotate( + scene=image.copy(), + detections=smoothed_detections + ) # Visualize cv2.imshow("Prediction", image) @@ -48,7 +52,8 @@ class Smoother: pipeline = InferencePipeline.init( model_id="microsoft-coco/9", # Or put your custom trained model here - # api_key="YOUR_ROBOFLOW_KEY", # Uncomment and fill if you want to access a model that requires auth (or setup a .env file) + # api_key="YOUR_ROBOFLOW_KEY", # Uncomment and fill if you want to access a + # model that requires auth (or setup a .env file) video_reference=0, # Webcam; can also be video path or RTSP stream on_prediction=render ) From d7420c545ea87938e2fcb68a9b37375186fe02ab Mon Sep 17 00:00:00 2001 From: Brad Dwyer Date: Wed, 27 Dec 2023 15:17:28 -0600 Subject: [PATCH 05/23] Move video to correct spot in the docs --- docs/detection/smoother.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/detection/smoother.md b/docs/detection/smoother.md index ed5523b5..b55f9ade 100644 --- a/docs/detection/smoother.md +++ b/docs/detection/smoother.md @@ -1,7 +1,3 @@ ## Smoother - - :::supervision.detection.smoother.Smoother From 1d4d7dd4ea1290332dcf3134ce38b998481b0737 Mon Sep 17 00:00:00 2001 From: Brad Dwyer Date: Thu, 28 Dec 2023 11:42:55 -0600 Subject: [PATCH 06/23] Move into tools directory --- docs/detection/smoother.md | 3 --- docs/detection/tools/smoother.md | 3 +++ mkdocs.yml | 2 +- supervision/__init__.py | 2 +- supervision/detection/{ => tools}/smoother.py | 0 5 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 docs/detection/smoother.md create mode 100644 docs/detection/tools/smoother.md rename supervision/detection/{ => tools}/smoother.py (100%) diff --git a/docs/detection/smoother.md b/docs/detection/smoother.md deleted file mode 100644 index b55f9ade..00000000 --- a/docs/detection/smoother.md +++ /dev/null @@ -1,3 +0,0 @@ -## Smoother - -:::supervision.detection.smoother.Smoother diff --git a/docs/detection/tools/smoother.md b/docs/detection/tools/smoother.md new file mode 100644 index 00000000..f27fe529 --- /dev/null +++ b/docs/detection/tools/smoother.md @@ -0,0 +1,3 @@ +## Detection Smoother + +:::supervision.detection.tools.smoother.Smoother diff --git a/mkdocs.yml b/mkdocs.yml index 4cb23161..f25bc382 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,12 +37,12 @@ nav: - Core: classification/core.md - Detections: - Core: detection/core.md - - Smoother: detection/smoother.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 + - Detection Smoother: detection/tools/smoother.md - Annotators: annotators.md - Trackers: trackers.md - Datasets: datasets.md diff --git a/supervision/__init__.py b/supervision/__init__.py index ef9327df..1922553a 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -33,7 +33,7 @@ from supervision.dataset.core import ( from supervision.detection.annotate import BoxAnnotator from supervision.detection.core import Detections from supervision.detection.line_counter import LineZone, LineZoneAnnotator -from supervision.detection.smoother import Smoother +from supervision.detection.tools.smoother import Smoother from supervision.detection.tools.inference_slicer import InferenceSlicer from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator from supervision.detection.utils import ( diff --git a/supervision/detection/smoother.py b/supervision/detection/tools/smoother.py similarity index 100% rename from supervision/detection/smoother.py rename to supervision/detection/tools/smoother.py From 6600d7533ae38b0eaf0e91c87e01a6bcbf712400 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:43:54 +0000 Subject: [PATCH 07/23] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/__init__.py b/supervision/__init__.py index 1922553a..1f5b9fcb 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -33,9 +33,9 @@ from supervision.dataset.core import ( from supervision.detection.annotate import BoxAnnotator from supervision.detection.core import Detections from supervision.detection.line_counter import LineZone, LineZoneAnnotator -from supervision.detection.tools.smoother import Smoother from supervision.detection.tools.inference_slicer import InferenceSlicer from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator +from supervision.detection.tools.smoother import Smoother from supervision.detection.utils import ( box_iou_batch, calculate_masks_centroids, From 5d1e76fbd01aeea9470f4f002f515ecf0ad209af Mon Sep 17 00:00:00 2001 From: Brad Dwyer Date: Thu, 28 Dec 2023 11:50:06 -0600 Subject: [PATCH 08/23] Update copy based on feedback from James & Piotr --- CONTRIBUTING.md | 2 +- docs/how_to/track_objects.md | 2 +- supervision/annotators/core.py | 8 ++--- supervision/detection/line_counter.py | 2 +- supervision/detection/tools/smoother.py | 42 +++++++++---------------- 5 files changed, 21 insertions(+), 35 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 84291639..3b1976e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ git push -u origin main ### Pre-commit tool -This project utilizes the [pre-commit](https://pre-commit.com/) tool to maintain code quality and consistency. Before submitting a pull request or making any commits, it is important to run the pre-commit tool to ensure that your changes meet the project's guidelines. +This project uses the [pre-commit](https://pre-commit.com/) tool to maintain code quality and consistency. Before submitting a pull request or making any commits, it is important to run the pre-commit tool to ensure that your changes meet the project's guidelines. Furthermore, we have integrated a pre-commit GitHub Action into our workflow. This means that with every pull request opened, the pre-commit checks will be automatically enforced, streamlining the code review process and ensuring that all contributions adhere to our quality standards. diff --git a/docs/how_to/track_objects.md b/docs/how_to/track_objects.md index ea48b070..282470c0 100644 --- a/docs/how_to/track_objects.md +++ b/docs/how_to/track_objects.md @@ -1,4 +1,4 @@ -Utilize Supervision to elevate your video analysis capabilities by effortlessly +Use Supervision to elevate your video analysis capabilities by effortlessly [tracking](https://supervision.roboflow.com/trackers/) objects identified by various object detection and segmentation models. This guide will walk you through the process of running inference using the [Ultralytics](https://github.com/ultralytics/ultralytics) diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index fc3cf7f2..1503d80f 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -97,7 +97,7 @@ class MaskAnnotator(BaseAnnotator): !!! warning - This annotator utilizes the `sv.Detections.mask`. + This annotator uses `sv.Detections.mask`. """ def __init__( @@ -180,7 +180,7 @@ class PolygonAnnotator(BaseAnnotator): !!! warning - This annotator utilizes the `sv.Detections.mask`. + This annotator uses `sv.Detections.mask`. """ def __init__( @@ -348,7 +348,7 @@ class HaloAnnotator(BaseAnnotator): !!! warning - This annotator utilizes the `sv.Detections.mask`. + This annotator uses `sv.Detections.mask`. """ def __init__( @@ -1004,7 +1004,7 @@ class TraceAnnotator: !!! warning - This annotator utilizes the `sv.Detections.tracker_id`. Read + This annotator uses the `sv.Detections.tracker_id`. Read [here](https://supervision.roboflow.com/trackers/) to learn how to plug tracking into your inference pipeline. """ diff --git a/supervision/detection/line_counter.py b/supervision/detection/line_counter.py index 851d1d4a..d4bef678 100644 --- a/supervision/detection/line_counter.py +++ b/supervision/detection/line_counter.py @@ -15,7 +15,7 @@ class LineZone: !!! warning - LineZone utilizes the `tracker_id`. Read + LineZone uses the `tracker_id`. Read [here](https://supervision.roboflow.com/trackers/) to learn how to plug tracking into your inference pipeline. diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index 7fd8166a..65d653ff 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -7,9 +7,9 @@ from supervision.detection.core import Detections class Smoother: """ - A class for smoothing out noise in predictions over time by using a Tracker - to track objects over time and averaging out the predictions over the - `length` most recent frames. + Smooth out noise in predictions over time with the `Smoother` class. + This classes uses an existing `Tracker` to track objects over time. + Detections are averaged out over the `length` most recent frames. > _On the left are the model's raw predictions, - > on the right is the output of Smoother._ + > on the right is the output of DetectionsSmoother._ !!! warning @@ -38,7 +38,7 @@ class Smoother: byte_tracker = sv.ByteTrack() # Initialize the Smoother - smoother = sv.Smoother() + smoother = sv.DetectionsSmoother() def render(detections, video_frame): # Parse the detections @@ -129,7 +129,7 @@ class Smoother: return self.get_smoothed_detections() - def get_track(self, track_id: int) -> Optional[dict]: + def get_track(self, track_id: int) -> Optional[Detections]: track = self.tracks.get(track_id, None) if track is None: return None @@ -138,7 +138,7 @@ class Smoother: if len(track) == 0: return None - ret = track[0] + ret = track.copy()[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) From 6efd51dfb14c66a9628e81a7b188ef4d1890c297 Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Tue, 23 Jan 2024 12:57:36 +0000 Subject: [PATCH 15/23] respond to feedback --- supervision/detection/tools/smoother.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index 5168693c..d43427bd 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -20,11 +20,11 @@ class DetectionsSmoother: !!! warning - Smoother uses the `tracker_id`. Read + DetectionsSmoother uses the `tracker_id`. Read [here](https://supervision.roboflow.com/trackers/) to learn how to plug tracking into your inference pipeline. - Note: Smoother is intended for use on Detections without a `mask` field. + Note: DetectionsSmoother is intended for use on Detections without a `mask` field. ## Example Usage: From 24ca25ef9439ada02d91e55c642f4b4bf48f1fd0 Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Tue, 23 Jan 2024 13:27:48 +0000 Subject: [PATCH 16/23] fix formatting --- supervision/detection/tools/smoother.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index d43427bd..ad064b18 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -13,7 +13,9 @@ class DetectionsSmoother: Detections are averaged out over the `length` most recent frames. > _On the left are the model's raw predictions, > on the right is the output of DetectionsSmoother._ @@ -21,10 +23,11 @@ class DetectionsSmoother: !!! warning DetectionsSmoother uses the `tracker_id`. Read - [here](https://supervision.roboflow.com/trackers/) to learn how to plug - tracking into your inference pipeline. + [here](https://supervision.roboflow.com/trackers/) to learn + how to plug tracking into your inference pipeline. - Note: DetectionsSmoother is intended for use on Detections without a `mask` field. + Note: DetectionsSmoother is intended for use on Detections + without a `mask` field. ## Example Usage: From 21791c85225b86786fb09699e11767e899c25828 Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Tue, 23 Jan 2024 13:38:15 +0000 Subject: [PATCH 17/23] remove idle trackers --- supervision/detection/tools/smoother.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index ad064b18..ebabb349 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -111,6 +111,8 @@ class DetectionsSmoother: self.current_frame += 1 + used_tracker_ids = {} + for detection_idx in range(len(detections)): tracker_id = detections.tracker_id[detection_idx] if tracker_id is None: @@ -123,6 +125,13 @@ class DetectionsSmoother: self.tracks[tracker_id].append(detections[detection_idx]) self.track_ends[tracker_id] = self.current_frame + used_tracker_ids[tracker_id] = True + + for track_id in list(self.tracks.keys()): + if track_id not in used_tracker_ids: + del self.tracks[track_id] + del self.track_ends[track_id] + for track_id in self.tracks: track = self.tracks[track_id] if self.track_ends[track_id] < self.current_frame: From 626636cfa75605b92f3ee7265ba9992670640b00 Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Tue, 23 Jan 2024 17:10:02 +0000 Subject: [PATCH 18/23] update tracking logic --- supervision/detection/tools/smoother.py | 32 +++++++++++-------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index ebabb349..be44b22d 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -86,7 +86,6 @@ class DetectionsSmoother: self.current_frame = 0 self.tracks = {} - self.track_ends = {} def set_length(self, length: int) -> None: """ @@ -109,35 +108,32 @@ class DetectionsSmoother: detections (Detections): The detections to add to the smoother. """ - self.current_frame += 1 - - used_tracker_ids = {} + already_tracked_ids = set(self.tracks.keys()) for detection_idx in range(len(detections)): tracker_id = detections.tracker_id[detection_idx] if tracker_id is None: - # skip detections without a tracker id continue if self.tracks.get(tracker_id, None) is None: self.tracks[tracker_id] = deque(maxlen=self.length) - self.tracks[tracker_id].append(detections[detection_idx]) - self.track_ends[tracker_id] = self.current_frame - - used_tracker_ids[tracker_id] = True + if tracker_id in already_tracked_ids: + self.tracks[tracker_id].append(detections[detection_idx]) for track_id in list(self.tracks.keys()): - if track_id not in used_tracker_ids: - del self.tracks[track_id] - del self.track_ends[track_id] + if track_id not in detections.tracker_id: + self.tracks[track_id].append(None) - for track_id in self.tracks: - track = self.tracks[track_id] - if self.track_ends[track_id] < self.current_frame: - # continue tracking for a few frames after the object has left - # (to prevent flickering in case it comes back) - track.append(None) + for track_id in list(self.tracks.keys()): + if ( + all([d is None for d in self.tracks[track_id]]) + and len(self.tracks[track_id]) == self.length + ): + del self.tracks[track_id] + print("Removed track", track_id) + + print("Tracks:", self.tracks.keys()) return self.get_smoothed_detections() From e2751368d573290f30308cf94af4c012580d016b Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Jan 2024 13:06:07 +0100 Subject: [PATCH 19/23] improvements --- supervision/detection/tools/smoother.py | 134 +++++++----------------- 1 file changed, 40 insertions(+), 94 deletions(-) diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index be44b22d..f1478c8c 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -1,4 +1,5 @@ -from collections import deque +from collections import deque, defaultdict +from copy import deepcopy from typing import Optional import numpy as np @@ -8,132 +9,77 @@ from supervision.detection.core import Detections class DetectionsSmoother: """ - Smooth out noise in predictions over time with the `DetectionsSmoother` class. - This classes uses an existing `Tracker` to track objects over time. - Detections are averaged out over the `length` most recent frames. - - - > _On the left are the model's raw predictions, - > on the right is the output of 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. !!! warning - DetectionsSmoother uses the `tracker_id`. Read - [here](https://supervision.roboflow.com/trackers/) to learn - how to plug tracking into your inference pipeline. + - `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. - Note: DetectionsSmoother is intended for use on Detections - without a `mask` field. + Example: + ```python + import supervision as sv + from ultralytics import YOLO - ## Example Usage: + video_info = sv.VideoInfo.from_video_path(video_path=) + frame_generator = sv.get_video_frames_generator(source_path=) - ```python - import cv2 - # remember to `pip install inference` - from inference import InferencePipeline - import supervision as sv + model = YOLO() + tracker = sv.ByteTrack(frame_rate=video_info.fps) + smoother = sv.DetectionsSmoother() - box_annotator = sv.BoxAnnotator(color=sv.Color(52, 236, 217)) - byte_tracker = sv.ByteTrack() + annotator = sv.BoundingBoxAnnotator() - # Initialize the Smoother - smoother = sv.DetectionsSmoother() + with sv.VideoSink(, 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 = tracker.update_with_detections(detections) - def render(detections, video_frame): - # Parse the detections - detections = sv.Detections.from_roboflow(detections) - - # Run a tracker to link predictions across frames - detections = byte_tracker.update_with_detections(detections) - - # Record the new frame and get the smoothed predictions - smoothed_detections = smoother.update_with_detections(detections) - - # Render - image_smoothed = box_annotator.annotate( - scene=image.copy(), - detections=smoothed_detections - ) - - # Visualize - cv2.imshow("Prediction", image) - cv2.waitKey(1) - - - pipeline = InferencePipeline.init( - model_id="microsoft-coco/9", # Or put your custom trained model here - # api_key="YOUR_ROBOFLOW_KEY", # Uncomment and fill if you want to access a - # model that requires auth (or setup a .env file) - video_reference=0, # Webcam; can also be video path or RTSP stream - on_prediction=render - ) - pipeline.start() - pipeline.join() - ``` - """ + 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 current count of detected objects within the zone + length (int): The maximum number of frames to consider for smoothing + detections. Defaults to 5. """ - - self.length = length - - self.current_frame = 0 - self.tracks = {} - - def set_length(self, length: int) -> None: - """ - Sets the number of frames to average out detections over. - - Args: - length (int): The number of frames to average out detections over. - """ - - self.length = length - for track_id in self.tracks: - self.tracks[track_id] = deque(self.tracks[track_id], maxlen=length) + self.tracks = defaultdict(lambda: deque(maxlen=length)) def update_with_detections(self, detections: Detections) -> Detections: """ - Adds a new set of predictions to the smoother. Run this with every new - prediction received from the model. + Updates the smoother with a new set of detections from a frame. Args: detections (Detections): The detections to add to the smoother. """ - already_tracked_ids = set(self.tracks.keys()) + if detections.tracker_id is None: + print("DetectionsSmoother requires tracker_id to be set on Detections") + return detections for detection_idx in range(len(detections)): tracker_id = detections.tracker_id[detection_idx] if tracker_id is None: continue - if self.tracks.get(tracker_id, None) is None: - self.tracks[tracker_id] = deque(maxlen=self.length) + self.tracks[tracker_id].append(detections[detection_idx]) - if tracker_id in already_tracked_ids: - self.tracks[tracker_id].append(detections[detection_idx]) - - for track_id in list(self.tracks.keys()): + 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]]) - and len(self.tracks[track_id]) == self.length - ): + if all([d is None for d in self.tracks[track_id]]): del self.tracks[track_id] - print("Removed track", track_id) - - print("Tracks:", self.tracks.keys()) return self.get_smoothed_detections() @@ -146,7 +92,7 @@ class DetectionsSmoother: if len(track) == 0: return None - ret = track.copy()[0] + 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) From f6e5b6dc5476fcba5bda1fc712d2dd671388d1a6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:06:20 +0000 Subject: [PATCH 20/23] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/tools/smoother.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index f1478c8c..9fbd89a6 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -1,4 +1,4 @@ -from collections import deque, defaultdict +from collections import defaultdict, deque from copy import deepcopy from typing import Optional @@ -44,7 +44,7 @@ class DetectionsSmoother: annotated_frame = bounding_box_annotator.annotate(frame.copy(), detections) sink.write_frame(annotated_frame) ``` - """ # noqa: E501 // docs + """ # noqa: E501 // docs def __init__(self, length: int = 5) -> None: """ From 79560945f52308cab9ac47c390e14ee9fd1dd242 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Jan 2024 15:07:32 +0100 Subject: [PATCH 21/23] ready for merge --- docs/annotators.md | 1 + docs/detection/core.md | 1 + docs/detection/tools/smoother.md | 5 ++++ docs/how_to/evaluate_model.md | 5 ---- docs/how_to/process_video.md | 5 ---- docs/index.md | 32 +++++++++++++++++++++++++ mkdocs.yml | 6 +++-- supervision/detection/tools/smoother.py | 21 +++++++++------- 8 files changed, 55 insertions(+), 21 deletions(-) delete mode 100644 docs/how_to/evaluate_model.md delete mode 100644 docs/how_to/process_video.md diff --git a/docs/annotators.md b/docs/annotators.md index f7dc8ece..84041330 100644 --- a/docs/annotators.md +++ b/docs/annotators.md @@ -1,5 +1,6 @@ --- comments: true +status: new --- === "BoundingBox" diff --git a/docs/detection/core.md b/docs/detection/core.md index e966d60b..94d641e8 100644 --- a/docs/detection/core.md +++ b/docs/detection/core.md @@ -1,5 +1,6 @@ --- comments: true +status: new --- ## Detections diff --git a/docs/detection/tools/smoother.md b/docs/detection/tools/smoother.md index 9f90636e..e19778d3 100644 --- a/docs/detection/tools/smoother.md +++ b/docs/detection/tools/smoother.md @@ -1,3 +1,8 @@ +--- +comments: true +status: new +--- + ## Detection Smoother :::supervision.detection.tools.smoother.DetectionsSmoother diff --git a/docs/how_to/evaluate_model.md b/docs/how_to/evaluate_model.md deleted file mode 100644 index 7adab821..00000000 --- a/docs/how_to/evaluate_model.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -comments: true ---- - -🚧 Page under construction. diff --git a/docs/how_to/process_video.md b/docs/how_to/process_video.md deleted file mode 100644 index 7adab821..00000000 --- a/docs/how_to/process_video.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -comments: true ---- - -🚧 Page under construction. diff --git a/docs/index.md b/docs/index.md index d3c48e20..2e04331d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,6 +17,38 @@ comments: true We write your reusable computer vision tools. Whether you need to load your dataset from your hard drive, draw detections on an image or video, or count how many detections are in a zone. You can count on us! +
+ +- __Detect and Annotate__ + + --- + + Annotate predictions from a range of object detection and segmentation models + + [:octicons-arrow-right-24: Tutorial](how_to/detect_and_annotate) + +- __Track Objects__ + + --- + + Discover how to enhance video analysis by implementing seamless object tracking + + [:octicons-arrow-right-24: Tutorial](how_to/track_objects) + +- > __Count Objects Crossing Line__ + + --- + + Explore methods to accurately count and analyze objects crossing a predefined line + +- > __Filter Objects in Zone__ + + --- + + Master the techniques to selectively filter and focus on objects within a specific zone + +
+ ## 💻 Install You can install `supervision` with pip in a diff --git a/mkdocs.yml b/mkdocs.yml index 5b6fa226..c9d4eae4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,10 +28,8 @@ nav: - Home: index.md - How to: - Detect and Annotate: how_to/detect_and_annotate.md - - Process Video: how_to/process_video.md - Track Objects: how_to/track_objects.md - Filter Detections: how_to/filter_detections.md - - Evaluate Model: how_to/evaluate_model.md - API: - Classifications: - Core: classification/core.md @@ -83,6 +81,7 @@ theme: code: Roboto Mono features: - content.code.copy + - content.code.annotate plugins: - mkdocstrings @@ -94,6 +93,9 @@ markdown_extensions: - pymdownx.superfences - attr_list - md_in_html + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg - pymdownx.tabbed: alternate_style: true - toc: diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index 9fbd89a6..905d4f49 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -12,6 +12,12 @@ 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. + + !!! warning @@ -23,6 +29,7 @@ class DetectionsSmoother: Example: ```python import supervision as sv + from ultralytics import YOLO video_info = sv.VideoInfo.from_video_path(video_path=) @@ -39,7 +46,7 @@ class DetectionsSmoother: result = model(frame)[0] detections = sv.Detections.from_ultralytics(result) detections = tracker.update_with_detections(detections) - 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) @@ -63,7 +70,10 @@ class DetectionsSmoother: """ if detections.tracker_id is None: - print("DetectionsSmoother requires tracker_id to be set on Detections") + 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)): @@ -99,13 +109,6 @@ class DetectionsSmoother: return ret def get_smoothed_detections(self) -> Detections: - """ - Returns a smoothed set of predictions based on the `length` most recent frames. - - Returns: - detections (Detections): The smoothed detections. - """ - tracked_detections = [] for track_id in self.tracks: track = self.get_track(track_id) From 7916d6a6a461a32eb3404201a7772b382733530c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:07:45 +0000 Subject: [PATCH 22/23] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/tools/smoother.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index 905d4f49..3913a9d3 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -12,7 +12,7 @@ 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. - +