ultralytics 8.3.87__py3-none-any.whl → 8.3.88__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tests/test_solutions.py +34 -45
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/__init__.py +46 -39
- ultralytics/data/augment.py +2 -2
- ultralytics/solutions/__init__.py +14 -6
- ultralytics/solutions/ai_gym.py +39 -28
- ultralytics/solutions/analytics.py +22 -18
- ultralytics/solutions/distance_calculation.py +25 -25
- ultralytics/solutions/heatmap.py +40 -38
- ultralytics/solutions/instance_segmentation.py +69 -0
- ultralytics/solutions/object_blurrer.py +89 -0
- ultralytics/solutions/object_counter.py +35 -33
- ultralytics/solutions/object_cropper.py +84 -0
- ultralytics/solutions/parking_management.py +21 -9
- ultralytics/solutions/queue_management.py +20 -39
- ultralytics/solutions/region_counter.py +54 -51
- ultralytics/solutions/security_alarm.py +40 -30
- ultralytics/solutions/solutions.py +594 -16
- ultralytics/solutions/speed_estimation.py +34 -31
- ultralytics/solutions/streamlit_inference.py +34 -28
- ultralytics/solutions/trackzone.py +29 -18
- ultralytics/solutions/vision_eye.py +69 -0
- ultralytics/trackers/utils/kalman_filter.py +23 -23
- ultralytics/utils/instance.py +3 -3
- ultralytics/utils/plotting.py +0 -414
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/METADATA +1 -1
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/RECORD +31 -27
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/LICENSE +0 -0
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/WHEEL +0 -0
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
|
+
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from ultralytics.solutions.solutions import BaseSolution, SolutionResults
|
7
|
+
from ultralytics.utils.plotting import save_one_box
|
8
|
+
|
9
|
+
|
10
|
+
class ObjectCropper(BaseSolution):
|
11
|
+
"""
|
12
|
+
A class to manage the cropping of detected objects in a real-time video stream or images.
|
13
|
+
|
14
|
+
This class extends the BaseSolution class and provides functionality for cropping objects based on detected bounding
|
15
|
+
boxes. The cropped images are saved to a specified directory for further analysis or usage.
|
16
|
+
|
17
|
+
Attributes:
|
18
|
+
crop_dir (str): Directory where cropped object images are stored.
|
19
|
+
crop_idx (int): Counter for the total number of cropped objects.
|
20
|
+
iou (float): IoU (Intersection over Union) threshold for non-maximum suppression.
|
21
|
+
conf (float): Confidence threshold for filtering detections.
|
22
|
+
|
23
|
+
Methods:
|
24
|
+
process: Crops detected objects from the input image and saves them to the output directory.
|
25
|
+
|
26
|
+
Examples:
|
27
|
+
>>> cropper = ObjectCropper()
|
28
|
+
>>> frame = cv2.imread("frame.jpg")
|
29
|
+
>>> processed_results = cropper.process(frame)
|
30
|
+
>>> print(f"Total cropped objects: {cropper.crop_idx}")
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(self, **kwargs):
|
34
|
+
"""
|
35
|
+
Initializes the ObjectCropper class for cropping objects from detected bounding boxes.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
**kwargs (Any): Keyword arguments passed to the parent class and used for configuration.
|
39
|
+
crop_dir (str): Path to the directory for saving cropped object images.
|
40
|
+
"""
|
41
|
+
super().__init__(**kwargs)
|
42
|
+
|
43
|
+
self.crop_dir = kwargs.get("crop_dir", "cropped-detections") # Directory for storing cropped detections
|
44
|
+
if not os.path.exists(self.crop_dir):
|
45
|
+
os.mkdir(self.crop_dir) # Create directory if it does not exist
|
46
|
+
if self.CFG["show"]:
|
47
|
+
self.LOGGER.info(
|
48
|
+
f"⚠️ show=True disabled for crop solution, results will be saved in the directory named: {self.crop_dir}"
|
49
|
+
)
|
50
|
+
self.crop_idx = 0 # Initialize counter for total cropped objects
|
51
|
+
self.iou = self.CFG["iou"]
|
52
|
+
self.conf = self.CFG["conf"] if self.CFG["conf"] is not None else 0.25
|
53
|
+
|
54
|
+
def process(self, im0):
|
55
|
+
"""
|
56
|
+
Crops detected objects from the input image and saves them as separate images.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
im0 (numpy.ndarray): The input image containing detected objects.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
(SolutionResults): A SolutionResults object containing the total number of cropped objects and processed image.
|
63
|
+
|
64
|
+
Examples:
|
65
|
+
>>> cropper = ObjectCropper()
|
66
|
+
>>> frame = cv2.imread("image.jpg")
|
67
|
+
>>> results = cropper.process(frame)
|
68
|
+
>>> print(f"Total cropped objects: {results.total_crop_objects}")
|
69
|
+
"""
|
70
|
+
results = self.model.predict(
|
71
|
+
im0, classes=self.classes, conf=self.conf, iou=self.iou, device=self.CFG["device"]
|
72
|
+
)[0]
|
73
|
+
|
74
|
+
for box in results.boxes:
|
75
|
+
self.crop_idx += 1
|
76
|
+
save_one_box(
|
77
|
+
box.xyxy,
|
78
|
+
im0,
|
79
|
+
file=Path(self.crop_dir) / f"crop_{self.crop_idx}.jpg",
|
80
|
+
BGR=True,
|
81
|
+
)
|
82
|
+
|
83
|
+
# Return SolutionResults
|
84
|
+
return SolutionResults(plot_im=im0, total_crop_objects=self.crop_idx)
|
@@ -5,10 +5,9 @@ import json
|
|
5
5
|
import cv2
|
6
6
|
import numpy as np
|
7
7
|
|
8
|
-
from ultralytics.solutions.solutions import BaseSolution
|
8
|
+
from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
|
9
9
|
from ultralytics.utils import LOGGER
|
10
10
|
from ultralytics.utils.checks import check_imshow
|
11
|
-
from ultralytics.utils.plotting import Annotator
|
12
11
|
|
13
12
|
|
14
13
|
class ParkingPtsSelection:
|
@@ -189,7 +188,7 @@ class ParkingManagement(BaseSolution):
|
|
189
188
|
dc (Tuple[int, int, int]): RGB color tuple for centroid visualization of detected objects.
|
190
189
|
|
191
190
|
Methods:
|
192
|
-
|
191
|
+
process: Processes the input image for parking lot management and visualization.
|
193
192
|
|
194
193
|
Examples:
|
195
194
|
>>> from ultralytics.solutions import ParkingManagement
|
@@ -216,9 +215,9 @@ class ParkingManagement(BaseSolution):
|
|
216
215
|
self.occ = (0, 255, 0) # occupied region color
|
217
216
|
self.dc = (255, 0, 189) # centroid color for each box
|
218
217
|
|
219
|
-
def
|
218
|
+
def process(self, im0):
|
220
219
|
"""
|
221
|
-
Processes the
|
220
|
+
Processes the input image for parking lot management and visualization.
|
222
221
|
|
223
222
|
This function analyzes the input image, extracts tracks, and determines the occupancy status of parking
|
224
223
|
regions defined in the JSON file. It annotates the image with occupied and available parking spots,
|
@@ -227,14 +226,18 @@ class ParkingManagement(BaseSolution):
|
|
227
226
|
Args:
|
228
227
|
im0 (np.ndarray): The input inference image.
|
229
228
|
|
229
|
+
Returns:
|
230
|
+
(SolutionResults): Contains processed image `plot_im`, 'filled_slots' (number of occupied parking slots),
|
231
|
+
'available_slots' (number of available parking slots), and 'total_tracks' (total number of tracked objects).
|
232
|
+
|
230
233
|
Examples:
|
231
234
|
>>> parking_manager = ParkingManagement(json_file="parking_regions.json")
|
232
235
|
>>> image = cv2.imread("parking_lot.jpg")
|
233
|
-
>>> parking_manager.
|
236
|
+
>>> results = parking_manager.process(image)
|
234
237
|
"""
|
235
238
|
self.extract_tracks(im0) # extract tracks from im0
|
236
239
|
es, fs = len(self.json), 0 # empty slots, filled slots
|
237
|
-
annotator =
|
240
|
+
annotator = SolutionAnnotator(im0, self.line_width) # init annotator
|
238
241
|
|
239
242
|
for region in self.json:
|
240
243
|
# Convert points to a NumPy array with the correct dtype and reshape properly
|
@@ -257,5 +260,14 @@ class ParkingManagement(BaseSolution):
|
|
257
260
|
self.pr_info["Occupancy"], self.pr_info["Available"] = fs, es
|
258
261
|
|
259
262
|
annotator.display_analytics(im0, self.pr_info, (104, 31, 17), (255, 255, 255), 10)
|
260
|
-
|
261
|
-
|
263
|
+
|
264
|
+
plot_im = annotator.result()
|
265
|
+
self.display_output(plot_im) # display output with base class function
|
266
|
+
|
267
|
+
# Return SolutionResults
|
268
|
+
return SolutionResults(
|
269
|
+
plot_im=plot_im,
|
270
|
+
filled_slots=self.pr_info["Occupancy"],
|
271
|
+
available_slots=self.pr_info["Available"],
|
272
|
+
total_tracks=len(self.track_ids),
|
273
|
+
)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
2
|
|
3
|
-
from ultralytics.solutions.solutions import BaseSolution
|
4
|
-
from ultralytics.utils.plotting import
|
3
|
+
from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
|
4
|
+
from ultralytics.utils.plotting import colors
|
5
5
|
|
6
6
|
|
7
7
|
class QueueManager(BaseSolution):
|
@@ -15,13 +15,12 @@ class QueueManager(BaseSolution):
|
|
15
15
|
counts (int): The current count of objects in the queue.
|
16
16
|
rect_color (Tuple[int, int, int]): RGB color tuple for drawing the queue region rectangle.
|
17
17
|
region_length (int): The number of points defining the queue region.
|
18
|
-
annotator (Annotator): An instance of the Annotator class for drawing on frames.
|
19
18
|
track_line (List[Tuple[int, int]]): List of track line coordinates.
|
20
19
|
track_history (Dict[int, List[Tuple[int, int]]]): Dictionary storing tracking history for each object.
|
21
20
|
|
22
21
|
Methods:
|
23
22
|
initialize_region: Initializes the queue region.
|
24
|
-
|
23
|
+
process: Processes a single frame for queue management.
|
25
24
|
extract_tracks: Extracts object tracks from the current frame.
|
26
25
|
store_tracking_history: Stores the tracking history for an object.
|
27
26
|
display_output: Displays the processed output.
|
@@ -33,18 +32,18 @@ class QueueManager(BaseSolution):
|
|
33
32
|
>>> success, im0 = cap.read()
|
34
33
|
>>> if not success:
|
35
34
|
>>> break
|
36
|
-
>>>
|
35
|
+
>>> results = queue_manager.process(im0)
|
37
36
|
"""
|
38
37
|
|
39
38
|
def __init__(self, **kwargs):
|
40
39
|
"""Initializes the QueueManager with parameters for tracking and counting objects in a video stream."""
|
41
40
|
super().__init__(**kwargs)
|
42
41
|
self.initialize_region()
|
43
|
-
self.counts = 0 # Queue counts
|
44
|
-
self.rect_color = (255, 255, 255) # Rectangle color
|
42
|
+
self.counts = 0 # Queue counts information
|
43
|
+
self.rect_color = (255, 255, 255) # Rectangle color for visualization
|
45
44
|
self.region_length = len(self.region) # Store region length for further usage
|
46
45
|
|
47
|
-
def
|
46
|
+
def process(self, im0):
|
48
47
|
"""
|
49
48
|
Processes the queue management for a single frame of video.
|
50
49
|
|
@@ -52,48 +51,28 @@ class QueueManager(BaseSolution):
|
|
52
51
|
im0 (numpy.ndarray): Input image for processing, typically a frame from a video stream.
|
53
52
|
|
54
53
|
Returns:
|
55
|
-
(
|
56
|
-
|
57
|
-
This method performs the following steps:
|
58
|
-
1. Resets the queue count for the current frame.
|
59
|
-
2. Initializes an Annotator object for drawing on the image.
|
60
|
-
3. Extracts tracks from the image.
|
61
|
-
4. Draws the counting region on the image.
|
62
|
-
5. For each detected object:
|
63
|
-
- Draws bounding boxes and labels.
|
64
|
-
- Stores tracking history.
|
65
|
-
- Draws centroids and tracks.
|
66
|
-
- Checks if the object is inside the counting region and updates the count.
|
67
|
-
6. Displays the queue count on the image.
|
68
|
-
7. Displays the processed output.
|
54
|
+
(SolutionResults): Contains processed image `im0`, 'queue_count' (int, number of objects in the queue) and
|
55
|
+
'total_tracks' (int, total number of tracked objects).
|
69
56
|
|
70
57
|
Examples:
|
71
58
|
>>> queue_manager = QueueManager()
|
72
59
|
>>> frame = cv2.imread("frame.jpg")
|
73
|
-
>>>
|
60
|
+
>>> results = queue_manager.process(frame)
|
74
61
|
"""
|
75
62
|
self.counts = 0 # Reset counts every frame
|
76
|
-
self.
|
77
|
-
self.
|
78
|
-
|
79
|
-
self.annotator.draw_region(
|
80
|
-
reg_pts=self.region, color=self.rect_color, thickness=self.line_width * 2
|
81
|
-
) # Draw region
|
63
|
+
self.extract_tracks(im0) # Extract tracks from the current frame
|
64
|
+
annotator = SolutionAnnotator(im0, line_width=self.line_width) # Initialize annotator
|
65
|
+
annotator.draw_region(reg_pts=self.region, color=self.rect_color, thickness=self.line_width * 2) # Draw region
|
82
66
|
|
83
67
|
for box, track_id, cls in zip(self.boxes, self.track_ids, self.clss):
|
84
68
|
# Draw bounding box and counting region
|
85
|
-
|
69
|
+
annotator.box_label(box, label=self.names[cls], color=colors(track_id, True))
|
86
70
|
self.store_tracking_history(track_id, box) # Store track history
|
87
71
|
|
88
|
-
# Draw tracks of objects
|
89
|
-
self.annotator.draw_centroid_and_tracks(
|
90
|
-
self.track_line, color=colors(int(track_id), True), track_thickness=self.line_width
|
91
|
-
)
|
92
|
-
|
93
72
|
# Cache frequently accessed attributes
|
94
73
|
track_history = self.track_history.get(track_id, [])
|
95
74
|
|
96
|
-
#
|
75
|
+
# Store previous position of track and check if the object is inside the counting region
|
97
76
|
prev_position = None
|
98
77
|
if len(track_history) > 1:
|
99
78
|
prev_position = track_history[-2]
|
@@ -101,12 +80,14 @@ class QueueManager(BaseSolution):
|
|
101
80
|
self.counts += 1
|
102
81
|
|
103
82
|
# Display queue counts
|
104
|
-
|
83
|
+
annotator.queue_counts_display(
|
105
84
|
f"Queue Counts : {str(self.counts)}",
|
106
85
|
points=self.region,
|
107
86
|
region_color=self.rect_color,
|
108
87
|
txt_color=(104, 31, 17),
|
109
88
|
)
|
110
|
-
|
89
|
+
plot_im = annotator.result()
|
90
|
+
self.display_output(plot_im) # Display output with base class function
|
111
91
|
|
112
|
-
|
92
|
+
# Return a SolutionResults object with processed data
|
93
|
+
return SolutionResults(plot_im=plot_im, queue_count=self.counts, total_tracks=len(self.track_ids))
|
@@ -1,33 +1,33 @@
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
from ultralytics.
|
3
|
+
import numpy as np
|
4
|
+
|
5
|
+
from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
|
6
|
+
from ultralytics.utils.plotting import colors
|
6
7
|
|
7
8
|
|
8
9
|
class RegionCounter(BaseSolution):
|
9
10
|
"""
|
10
|
-
A class
|
11
|
+
A class for real-time counting of objects within user-defined regions in a video stream.
|
11
12
|
|
12
|
-
This class inherits from `BaseSolution` and
|
13
|
-
|
14
|
-
|
13
|
+
This class inherits from `BaseSolution` and provides functionality to define polygonal regions in a video frame,
|
14
|
+
track objects, and count those objects that pass through each defined region. Useful for applications requiring
|
15
|
+
counting in specified areas, such as monitoring zones or segmented sections.
|
15
16
|
|
16
17
|
Attributes:
|
17
|
-
region_template (
|
18
|
-
|
19
|
-
counting_regions (
|
20
|
-
|
18
|
+
region_template (Dict): Template for creating new counting regions with default attributes including name,
|
19
|
+
polygon coordinates, and display colors.
|
20
|
+
counting_regions (List): List storing all defined regions, where each entry is based on `region_template`
|
21
|
+
and includes specific region settings like name, coordinates, and color.
|
22
|
+
region_counts (Dict): Dictionary storing the count of objects for each named region.
|
21
23
|
|
22
24
|
Methods:
|
23
|
-
add_region: Adds a new counting region with specified attributes
|
24
|
-
|
25
|
-
count: Processes video frames to count objects in each region, drawing regions and displaying counts
|
26
|
-
on the frame. Handles object detection, region definition, and containment checks.
|
25
|
+
add_region: Adds a new counting region with specified attributes.
|
26
|
+
process: Processes video frames to count objects in each region.
|
27
27
|
"""
|
28
28
|
|
29
29
|
def __init__(self, **kwargs):
|
30
|
-
"""Initializes the RegionCounter class for real-time counting in different regions of
|
30
|
+
"""Initializes the RegionCounter class for real-time counting in different regions of video streams."""
|
31
31
|
super().__init__(**kwargs)
|
32
32
|
self.region_template = {
|
33
33
|
"name": "Default Region",
|
@@ -37,6 +37,7 @@ class RegionCounter(BaseSolution):
|
|
37
37
|
"region_color": (255, 255, 255),
|
38
38
|
"text_color": (0, 0, 0),
|
39
39
|
}
|
40
|
+
self.region_counts = {}
|
40
41
|
self.counting_regions = []
|
41
42
|
|
42
43
|
def add_region(self, name, polygon_points, region_color, text_color):
|
@@ -45,9 +46,9 @@ class RegionCounter(BaseSolution):
|
|
45
46
|
|
46
47
|
Args:
|
47
48
|
name (str): Name assigned to the new region.
|
48
|
-
polygon_points (
|
49
|
-
region_color (
|
50
|
-
text_color (
|
49
|
+
polygon_points (List[Tuple]): List of (x, y) coordinates defining the region's polygon.
|
50
|
+
region_color (Tuple): BGR color for region visualization.
|
51
|
+
text_color (Tuple): BGR color for the text within the region.
|
51
52
|
"""
|
52
53
|
region = self.region_template.copy()
|
53
54
|
region.update(
|
@@ -60,57 +61,59 @@ class RegionCounter(BaseSolution):
|
|
60
61
|
)
|
61
62
|
self.counting_regions.append(region)
|
62
63
|
|
63
|
-
def
|
64
|
+
def process(self, im0):
|
64
65
|
"""
|
65
66
|
Processes the input frame to detect and count objects within each defined region.
|
66
67
|
|
67
68
|
Args:
|
68
|
-
im0 (
|
69
|
+
im0 (np.ndarray): Input image frame where objects and regions are annotated.
|
69
70
|
|
70
71
|
Returns:
|
71
|
-
|
72
|
+
(SolutionResults): Contains processed image `plot_im`, 'total_tracks' (int, total number of tracked objects),
|
73
|
+
and 'region_counts' (Dict, counts of objects per region).
|
72
74
|
"""
|
73
|
-
self.annotator = Annotator(im0, line_width=self.line_width)
|
74
75
|
self.extract_tracks(im0)
|
76
|
+
annotator = SolutionAnnotator(im0, line_width=self.line_width)
|
77
|
+
|
78
|
+
# Ensure self.region is initialized and structured as a dictionary
|
79
|
+
if not isinstance(self.region, dict):
|
80
|
+
self.region = {"Region#01": self.region or self.initialize_region()}
|
75
81
|
|
76
|
-
#
|
77
|
-
|
78
|
-
self.initialize_region()
|
79
|
-
regions = {"Region#01": self.region}
|
80
|
-
else:
|
81
|
-
regions = self.region if isinstance(self.region, dict) else {"Region#01": self.region}
|
82
|
-
|
83
|
-
# Draw regions and process counts for each defined area
|
84
|
-
for idx, (region_name, reg_pts) in enumerate(regions.items(), start=1):
|
85
|
-
if not isinstance(reg_pts, list) or not all(isinstance(pt, tuple) for pt in reg_pts):
|
86
|
-
LOGGER.warning(f"Invalid region points for {region_name}: {reg_pts}")
|
87
|
-
continue # Skip invalid entries
|
82
|
+
# Draw only valid regions
|
83
|
+
for idx, (region_name, reg_pts) in enumerate(self.region.items(), start=1):
|
88
84
|
color = colors(idx, True)
|
89
|
-
|
90
|
-
self.add_region(region_name, reg_pts, color,
|
85
|
+
annotator.draw_region(reg_pts, color, self.line_width * 2)
|
86
|
+
self.add_region(region_name, reg_pts, color, annotator.get_txt_color())
|
91
87
|
|
92
|
-
# Prepare regions for containment check
|
88
|
+
# Prepare regions for containment check (only process valid ones)
|
93
89
|
for region in self.counting_regions:
|
94
|
-
|
90
|
+
if "prepared_polygon" not in region:
|
91
|
+
region["prepared_polygon"] = self.prep(region["polygon"])
|
92
|
+
|
93
|
+
# Convert bounding boxes to NumPy array for center points
|
94
|
+
boxes_np = np.array([((box[0] + box[2]) / 2, (box[1] + box[3]) / 2) for box in self.boxes], dtype=np.float32)
|
95
|
+
points = [self.Point(pt) for pt in boxes_np] # Convert centers to Point objects
|
95
96
|
|
96
|
-
# Process bounding boxes
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
# Process bounding boxes & check containment
|
98
|
+
if points:
|
99
|
+
for (point, cls), box in zip(zip(points, self.clss), self.boxes):
|
100
|
+
annotator.box_label(box, label=self.names[cls], color=colors(cls))
|
100
101
|
|
101
|
-
|
102
|
-
|
103
|
-
|
102
|
+
for region in self.counting_regions:
|
103
|
+
if region["prepared_polygon"].contains(point):
|
104
|
+
region["counts"] += 1
|
105
|
+
self.region_counts[region["name"]] = region["counts"]
|
104
106
|
|
105
|
-
# Display counts
|
107
|
+
# Display region counts
|
106
108
|
for region in self.counting_regions:
|
107
|
-
|
109
|
+
annotator.text_label(
|
108
110
|
region["polygon"].bounds,
|
109
111
|
label=str(region["counts"]),
|
110
112
|
color=region["region_color"],
|
111
113
|
txt_color=region["text_color"],
|
112
114
|
)
|
113
|
-
region["counts"] = 0 # Reset
|
115
|
+
region["counts"] = 0 # Reset for next frame
|
116
|
+
plot_im = annotator.result()
|
117
|
+
self.display_output(plot_im)
|
114
118
|
|
115
|
-
self.
|
116
|
-
return im0
|
119
|
+
return SolutionResults(plot_im=plot_im, total_tracks=len(self.track_ids), region_counts=self.region_counts)
|
@@ -1,36 +1,44 @@
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
2
|
|
3
|
-
from ultralytics.solutions.solutions import BaseSolution
|
3
|
+
from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
|
4
4
|
from ultralytics.utils import LOGGER
|
5
|
-
from ultralytics.utils.plotting import
|
5
|
+
from ultralytics.utils.plotting import colors
|
6
6
|
|
7
7
|
|
8
8
|
class SecurityAlarm(BaseSolution):
|
9
9
|
"""
|
10
10
|
A class to manage security alarm functionalities for real-time monitoring.
|
11
11
|
|
12
|
-
This class extends the BaseSolution class and provides features to monitor
|
13
|
-
|
14
|
-
|
12
|
+
This class extends the BaseSolution class and provides features to monitor objects in a frame, send email
|
13
|
+
notifications when specific thresholds are exceeded for total detections, and annotate the output frame for
|
14
|
+
visualization.
|
15
15
|
|
16
16
|
Attributes:
|
17
|
-
|
18
|
-
|
17
|
+
email_sent (bool): Flag to track if an email has already been sent for the current event.
|
18
|
+
records (int): Threshold for the number of detected objects to trigger an alert.
|
19
|
+
server (smtplib.SMTP): SMTP server connection for sending email alerts.
|
20
|
+
to_email (str): Recipient's email address for alerts.
|
21
|
+
from_email (str): Sender's email address for alerts.
|
19
22
|
|
20
23
|
Methods:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
authenticate: Sets up email server authentication for sending alerts.
|
25
|
+
send_email: Sends an email notification with details and an image attachment.
|
26
|
+
process: Monitors the frame, processes detections, and triggers alerts if thresholds are crossed.
|
24
27
|
|
25
28
|
Examples:
|
26
29
|
>>> security = SecurityAlarm()
|
27
30
|
>>> security.authenticate("abc@gmail.com", "1111222233334444", "xyz@gmail.com")
|
28
31
|
>>> frame = cv2.imread("frame.jpg")
|
29
|
-
>>>
|
32
|
+
>>> results = security.process(frame)
|
30
33
|
"""
|
31
34
|
|
32
35
|
def __init__(self, **kwargs):
|
33
|
-
"""
|
36
|
+
"""
|
37
|
+
Initializes the SecurityAlarm class with parameters for real-time object monitoring.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
**kwargs (Any): Additional keyword arguments passed to the parent class.
|
41
|
+
"""
|
34
42
|
super().__init__(**kwargs)
|
35
43
|
self.email_sent = False
|
36
44
|
self.records = self.CFG["records"]
|
@@ -47,8 +55,7 @@ class SecurityAlarm(BaseSolution):
|
|
47
55
|
password (str): Password for the sender's email account.
|
48
56
|
to_email (str): Recipient's email address.
|
49
57
|
|
50
|
-
This method initializes a secure connection with the SMTP server
|
51
|
-
and logs in using the provided credentials.
|
58
|
+
This method initializes a secure connection with the SMTP server and logs in using the provided credentials.
|
52
59
|
|
53
60
|
Examples:
|
54
61
|
>>> alarm = SecurityAlarm()
|
@@ -70,8 +77,8 @@ class SecurityAlarm(BaseSolution):
|
|
70
77
|
im0 (numpy.ndarray): The input image or frame to be attached to the email.
|
71
78
|
records (int): The number of detected objects to be included in the email message.
|
72
79
|
|
73
|
-
This method encodes the input image, composes the email message with
|
74
|
-
|
80
|
+
This method encodes the input image, composes the email message with details about the detection, and sends it
|
81
|
+
to the specified recipient.
|
75
82
|
|
76
83
|
Examples:
|
77
84
|
>>> alarm = SecurityAlarm()
|
@@ -105,40 +112,43 @@ class SecurityAlarm(BaseSolution):
|
|
105
112
|
self.server.send_message(message)
|
106
113
|
LOGGER.info("✅ Email sent successfully!")
|
107
114
|
except Exception as e:
|
108
|
-
|
115
|
+
LOGGER.error(f"❌ Failed to send email: {e}")
|
109
116
|
|
110
|
-
def
|
117
|
+
def process(self, im0):
|
111
118
|
"""
|
112
119
|
Monitors the frame, processes object detections, and triggers alerts if thresholds are exceeded.
|
113
120
|
|
114
121
|
Args:
|
115
122
|
im0 (numpy.ndarray): The input image or frame to be processed and annotated.
|
116
123
|
|
117
|
-
This method processes the input frame, extracts detections, annotates the frame
|
118
|
-
with bounding boxes, and sends an email notification if the number of detected objects
|
119
|
-
surpasses the specified threshold and an alert has not already been sent.
|
120
|
-
|
121
124
|
Returns:
|
122
|
-
(
|
125
|
+
(SolutionResults): Contains processed image `plot_im`, 'total_tracks' (total number of tracked objects) and
|
126
|
+
'email_sent' (whether an email alert was triggered).
|
127
|
+
|
128
|
+
This method processes the input frame, extracts detections, annotates the frame with bounding boxes, and sends
|
129
|
+
an email notification if the number of detected objects surpasses the specified threshold and an alert has not
|
130
|
+
already been sent.
|
123
131
|
|
124
132
|
Examples:
|
125
133
|
>>> alarm = SecurityAlarm()
|
126
134
|
>>> frame = cv2.imread("path/to/image.jpg")
|
127
|
-
>>>
|
135
|
+
>>> results = alarm.process(frame)
|
128
136
|
"""
|
129
|
-
self.annotator = Annotator(im0, line_width=self.line_width) # Initialize annotator
|
130
137
|
self.extract_tracks(im0) # Extract tracks
|
138
|
+
annotator = SolutionAnnotator(im0, line_width=self.line_width) # Initialize annotator
|
131
139
|
|
132
|
-
# Iterate over bounding boxes
|
140
|
+
# Iterate over bounding boxes and classes index
|
133
141
|
for box, cls in zip(self.boxes, self.clss):
|
134
142
|
# Draw bounding box
|
135
|
-
|
143
|
+
annotator.box_label(box, label=self.names[cls], color=colors(cls, True))
|
136
144
|
|
137
145
|
total_det = len(self.clss)
|
138
|
-
if total_det > self.records and not self.email_sent: # Only send email
|
146
|
+
if total_det > self.records and not self.email_sent: # Only send email if not sent before
|
139
147
|
self.send_email(im0, total_det)
|
140
148
|
self.email_sent = True
|
141
149
|
|
142
|
-
|
150
|
+
plot_im = annotator.result()
|
151
|
+
self.display_output(plot_im) # Display output with base class function
|
143
152
|
|
144
|
-
|
153
|
+
# Return a SolutionResults
|
154
|
+
return SolutionResults(plot_im=plot_im, total_tracks=len(self.track_ids), email_sent=self.email_sent)
|