Repository: roboflow/supervision

Issue:
BlurAnnotator raise `(-215:Assertion failed) !_src.empty() in function 'boxFilter'` error 
### Search before asking

- [X] I have searched the Supervision [issues](https://github.com/roboflow/supervision/issues) and found no similar bug report.


### Bug

```
 11%|█         | 37/341 [00:30<04:06,  1.23it/s]
---------------------------------------------------------------------------
error                                     Traceback (most recent call last)
<ipython-input-8-5f523fb09b4c> in <cell line: 15>()
     24             detections=detections
     25         )
---> 26         annotated_frame = blur_annotator.annotate(
     27             scene=annotated_frame,
     28             detections=detections

/usr/local/lib/python3.10/dist-packages/supervision/annotators/core.py in annotate(self, scene, detections)
    896             roi = scene[y1:y2, x1:x2]
    897 
--> 898             roi = cv2.blur(roi, (self.kernel_size, self.kernel_size))
    899             scene[y1:y2, x1:x2] = roi
    900 

error: OpenCV(4.8.0) /io/opencv/modules/imgproc/src/box_filter.dispatch.cpp:446: error: (-215:Assertion failed) !_src.empty() in function 'boxFilter'
```

### Environment

Colab. But rrror is probably unrelated to the environment.

### Minimal Reproducible Example

```python
import supervision as sv
from tqdm import tqdm
from ultralytics import YOLO

model = YOLO('yolov8x-seg.pt')
video_info = sv.VideoInfo.from_video_path(video_path='walking.mp4')
frame_generator = sv.get_video_frames_generator(source_path='walking.mp4')
byte_tracker = sv.ByteTrack()
trace_annotator = sv.TraceAnnotator(color_lookup=sv.ColorLookup.CLASS, trace_length=50, position=sv.Position.BOTTOM_CENTER)
blur_annotator = sv.BlurAnnotator()
box_corner_annotator = sv.BoxCornerAnnotator(color_lookup=sv.ColorLookup.CLASS, thickness=2, corner_length=10)
rect = sv.Rect(x=1180, y=620, width=80, height=80)


with sv.VideoSink(target_path='walking-trace-blur-corner.mp4', video_info=video_info) as sink:
    for frame in tqdm(frame_generator, total=video_info.total_frames):
        result = model.track(frame, imgsz=1920, verbose=False)[0]
        detections = sv.Detections.from_ultralytics(result)
        detections = detections[detections.class_id == 0]
        detections = byte_tracker.update_with_detections(detections)

        annotated_frame = trace_annotator.annotate(
            scene=frame.copy(),
            detections=detections
        )
        annotated_frame = blur_annotator.annotate(
            scene=annotated_frame,
            detections=detections
        )
        annotated_frame = box_corner_annotator.annotate(
            scene=annotated_frame,
            detections=detections
        )
        annotated_frame = sv.draw_image(
            scene=annotated_frame,
            image='supervision-lenny.png',
            opacity=0.8,
            rect=rect)
        sink.write_frame(frame=annotated_frame)
```

### Additional

_No response_

### Are you willing to submit a PR?

- [ ] Yes I'd like to help by submitting a PR!


Discussion:
Hi @SkalskiP want to work on this.
Can you assign it to me?
Hi @fdloopes 👋🏻! Done. You are assigned! 

Resolving patch:
diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py
--- a/supervision/annotators/core.py
+++ b/supervision/annotators/core.py
@@ -7,6 +7,7 @@
 from supervision.annotators.base import BaseAnnotator
 from supervision.annotators.utils import ColorLookup, Trace, resolve_color
 from supervision.detection.core import Detections
+from supervision.detection.utils import clip_boxes
 from supervision.draw.color import Color, ColorPalette
 from supervision.geometry.core import Position
 
@@ -891,10 +892,13 @@ def annotate(
         ![blur-annotator-example](https://media.roboflow.com/
         supervision-annotator-examples/blur-annotator-example-purple.png)
         """
-        for detection_idx in range(len(detections)):
-            x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
-            roi = scene[y1:y2, x1:x2]
+        image_height, image_width = scene.shape[:2]
+        clipped_xyxy = clip_boxes(
+            xyxy=detections.xyxy, resolution_wh=(image_width, image_height)
+        ).astype(int)
 
+        for x1, y1, x2, y2 in clipped_xyxy:
+            roi = scene[y1:y2, x1:x2]
             roi = cv2.blur(roi, (self.kernel_size, self.kernel_size))
             scene[y1:y2, x1:x2] = roi
 
diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py
--- a/supervision/detection/tools/polygon_zone.py
+++ b/supervision/detection/tools/polygon_zone.py
@@ -56,7 +56,7 @@ def trigger(self, detections: Detections) -> np.ndarray:
         """
 
         clipped_xyxy = clip_boxes(
-            boxes_xyxy=detections.xyxy, frame_resolution_wh=self.frame_resolution_wh
+            xyxy=detections.xyxy, resolution_wh=self.frame_resolution_wh
         )
         clipped_detections = replace(detections, xyxy=clipped_xyxy)
         clipped_anchors = np.ceil(
diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py
--- a/supervision/detection/utils.py
+++ b/supervision/detection/utils.py
@@ -110,17 +110,15 @@ def non_max_suppression(
     return keep[sort_index.argsort()]
 
 
-def clip_boxes(
-    boxes_xyxy: np.ndarray, frame_resolution_wh: Tuple[int, int]
-) -> np.ndarray:
+def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
     """
     Clips bounding boxes coordinates to fit within the frame resolution.
 
     Args:
-        boxes_xyxy (np.ndarray): A numpy array of shape `(N, 4)` where each
+        xyxy (np.ndarray): A numpy array of shape `(N, 4)` where each
             row corresponds to a bounding box in
         the format `(x_min, y_min, x_max, y_max)`.
-        frame_resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)`
+        resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)`
             representing the resolution of the frame.
 
     Returns:
@@ -128,8 +126,8 @@ def clip_boxes(
             corresponds to a bounding box with coordinates clipped to fit
             within the frame resolution.
     """
-    result = np.copy(boxes_xyxy)
-    width, height = frame_resolution_wh
+    result = np.copy(xyxy)
+    width, height = resolution_wh
     result[:, [0, 2]] = result[:, [0, 2]].clip(0, width)
     result[:, [1, 3]] = result[:, [1, 3]].clip(0, height)
     return result


Test patch:
diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py
--- a/test/detection/test_utils.py
+++ b/test/detection/test_utils.py
@@ -122,7 +122,7 @@ def test_non_max_suppression(
 
 
 @pytest.mark.parametrize(
-    "boxes_xyxy, frame_resolution_wh, expected_result",
+    "xyxy, resolution_wh, expected_result",
     [
         (
             np.empty(shape=(0, 4)),
@@ -157,11 +157,11 @@ def test_non_max_suppression(
     ],
 )
 def test_clip_boxes(
-    boxes_xyxy: np.ndarray,
-    frame_resolution_wh: Tuple[int, int],
+    xyxy: np.ndarray,
+    resolution_wh: Tuple[int, int],
     expected_result: np.ndarray,
 ) -> None:
-    result = clip_boxes(boxes_xyxy=boxes_xyxy, frame_resolution_wh=frame_resolution_wh)
+    result = clip_boxes(xyxy=xyxy, resolution_wh=resolution_wh)
     assert np.array_equal(result, expected_result)