dgenerate-ultralytics-headless 8.3.137__py3-none-any.whl → 8.3.224__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.
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/METADATA +41 -34
- dgenerate_ultralytics_headless-8.3.224.dist-info/RECORD +285 -0
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/WHEEL +1 -1
- tests/__init__.py +7 -6
- tests/conftest.py +15 -39
- tests/test_cli.py +17 -17
- tests/test_cuda.py +17 -8
- tests/test_engine.py +36 -10
- tests/test_exports.py +98 -37
- tests/test_integrations.py +12 -15
- tests/test_python.py +126 -82
- tests/test_solutions.py +319 -135
- ultralytics/__init__.py +27 -9
- ultralytics/cfg/__init__.py +83 -87
- ultralytics/cfg/datasets/Argoverse.yaml +4 -4
- ultralytics/cfg/datasets/DOTAv1.5.yaml +2 -2
- ultralytics/cfg/datasets/DOTAv1.yaml +2 -2
- ultralytics/cfg/datasets/GlobalWheat2020.yaml +2 -2
- ultralytics/cfg/datasets/HomeObjects-3K.yaml +4 -5
- ultralytics/cfg/datasets/ImageNet.yaml +3 -3
- ultralytics/cfg/datasets/Objects365.yaml +24 -20
- ultralytics/cfg/datasets/SKU-110K.yaml +9 -9
- ultralytics/cfg/datasets/VOC.yaml +10 -13
- ultralytics/cfg/datasets/VisDrone.yaml +43 -33
- ultralytics/cfg/datasets/african-wildlife.yaml +5 -5
- ultralytics/cfg/datasets/brain-tumor.yaml +4 -5
- ultralytics/cfg/datasets/carparts-seg.yaml +5 -5
- ultralytics/cfg/datasets/coco-pose.yaml +26 -4
- ultralytics/cfg/datasets/coco.yaml +4 -4
- ultralytics/cfg/datasets/coco128-seg.yaml +2 -2
- ultralytics/cfg/datasets/coco128.yaml +2 -2
- ultralytics/cfg/datasets/coco8-grayscale.yaml +103 -0
- ultralytics/cfg/datasets/coco8-multispectral.yaml +2 -2
- ultralytics/cfg/datasets/coco8-pose.yaml +23 -2
- ultralytics/cfg/datasets/coco8-seg.yaml +2 -2
- ultralytics/cfg/datasets/coco8.yaml +2 -2
- ultralytics/cfg/datasets/construction-ppe.yaml +32 -0
- ultralytics/cfg/datasets/crack-seg.yaml +5 -5
- ultralytics/cfg/datasets/dog-pose.yaml +32 -4
- ultralytics/cfg/datasets/dota8-multispectral.yaml +2 -2
- ultralytics/cfg/datasets/dota8.yaml +2 -2
- ultralytics/cfg/datasets/hand-keypoints.yaml +29 -4
- ultralytics/cfg/datasets/lvis.yaml +9 -9
- ultralytics/cfg/datasets/medical-pills.yaml +4 -5
- ultralytics/cfg/datasets/open-images-v7.yaml +7 -10
- ultralytics/cfg/datasets/package-seg.yaml +5 -5
- ultralytics/cfg/datasets/signature.yaml +4 -4
- ultralytics/cfg/datasets/tiger-pose.yaml +20 -4
- ultralytics/cfg/datasets/xView.yaml +5 -5
- ultralytics/cfg/default.yaml +96 -93
- ultralytics/cfg/trackers/botsort.yaml +16 -17
- ultralytics/cfg/trackers/bytetrack.yaml +9 -11
- ultralytics/data/__init__.py +4 -4
- ultralytics/data/annotator.py +12 -12
- ultralytics/data/augment.py +531 -564
- ultralytics/data/base.py +76 -81
- ultralytics/data/build.py +206 -42
- ultralytics/data/converter.py +179 -78
- ultralytics/data/dataset.py +121 -121
- ultralytics/data/loaders.py +114 -91
- ultralytics/data/split.py +28 -15
- ultralytics/data/split_dota.py +67 -48
- ultralytics/data/utils.py +110 -89
- ultralytics/engine/exporter.py +422 -460
- ultralytics/engine/model.py +224 -252
- ultralytics/engine/predictor.py +94 -89
- ultralytics/engine/results.py +345 -595
- ultralytics/engine/trainer.py +231 -134
- ultralytics/engine/tuner.py +279 -73
- ultralytics/engine/validator.py +53 -46
- ultralytics/hub/__init__.py +26 -28
- ultralytics/hub/auth.py +30 -16
- ultralytics/hub/google/__init__.py +34 -36
- ultralytics/hub/session.py +53 -77
- ultralytics/hub/utils.py +23 -109
- ultralytics/models/__init__.py +1 -1
- ultralytics/models/fastsam/__init__.py +1 -1
- ultralytics/models/fastsam/model.py +36 -18
- ultralytics/models/fastsam/predict.py +33 -44
- ultralytics/models/fastsam/utils.py +4 -5
- ultralytics/models/fastsam/val.py +12 -14
- ultralytics/models/nas/__init__.py +1 -1
- ultralytics/models/nas/model.py +16 -20
- ultralytics/models/nas/predict.py +12 -14
- ultralytics/models/nas/val.py +4 -5
- ultralytics/models/rtdetr/__init__.py +1 -1
- ultralytics/models/rtdetr/model.py +9 -9
- ultralytics/models/rtdetr/predict.py +22 -17
- ultralytics/models/rtdetr/train.py +20 -16
- ultralytics/models/rtdetr/val.py +79 -59
- ultralytics/models/sam/__init__.py +8 -2
- ultralytics/models/sam/amg.py +53 -38
- ultralytics/models/sam/build.py +29 -31
- ultralytics/models/sam/model.py +33 -38
- ultralytics/models/sam/modules/blocks.py +159 -182
- ultralytics/models/sam/modules/decoders.py +38 -47
- ultralytics/models/sam/modules/encoders.py +114 -133
- ultralytics/models/sam/modules/memory_attention.py +38 -31
- ultralytics/models/sam/modules/sam.py +114 -93
- ultralytics/models/sam/modules/tiny_encoder.py +268 -291
- ultralytics/models/sam/modules/transformer.py +59 -66
- ultralytics/models/sam/modules/utils.py +55 -72
- ultralytics/models/sam/predict.py +745 -341
- ultralytics/models/utils/loss.py +118 -107
- ultralytics/models/utils/ops.py +118 -71
- ultralytics/models/yolo/__init__.py +1 -1
- ultralytics/models/yolo/classify/predict.py +28 -26
- ultralytics/models/yolo/classify/train.py +50 -81
- ultralytics/models/yolo/classify/val.py +68 -61
- ultralytics/models/yolo/detect/predict.py +12 -15
- ultralytics/models/yolo/detect/train.py +56 -46
- ultralytics/models/yolo/detect/val.py +279 -223
- ultralytics/models/yolo/model.py +167 -86
- ultralytics/models/yolo/obb/predict.py +7 -11
- ultralytics/models/yolo/obb/train.py +23 -25
- ultralytics/models/yolo/obb/val.py +107 -99
- ultralytics/models/yolo/pose/__init__.py +1 -1
- ultralytics/models/yolo/pose/predict.py +12 -14
- ultralytics/models/yolo/pose/train.py +31 -69
- ultralytics/models/yolo/pose/val.py +119 -254
- ultralytics/models/yolo/segment/predict.py +21 -25
- ultralytics/models/yolo/segment/train.py +12 -66
- ultralytics/models/yolo/segment/val.py +126 -305
- ultralytics/models/yolo/world/train.py +53 -45
- ultralytics/models/yolo/world/train_world.py +51 -32
- ultralytics/models/yolo/yoloe/__init__.py +7 -7
- ultralytics/models/yolo/yoloe/predict.py +30 -37
- ultralytics/models/yolo/yoloe/train.py +89 -71
- ultralytics/models/yolo/yoloe/train_seg.py +15 -17
- ultralytics/models/yolo/yoloe/val.py +56 -41
- ultralytics/nn/__init__.py +9 -11
- ultralytics/nn/autobackend.py +179 -107
- ultralytics/nn/modules/__init__.py +67 -67
- ultralytics/nn/modules/activation.py +8 -7
- ultralytics/nn/modules/block.py +302 -323
- ultralytics/nn/modules/conv.py +61 -104
- ultralytics/nn/modules/head.py +488 -186
- ultralytics/nn/modules/transformer.py +183 -123
- ultralytics/nn/modules/utils.py +15 -20
- ultralytics/nn/tasks.py +327 -203
- ultralytics/nn/text_model.py +81 -65
- ultralytics/py.typed +1 -0
- ultralytics/solutions/__init__.py +12 -12
- ultralytics/solutions/ai_gym.py +19 -27
- ultralytics/solutions/analytics.py +36 -26
- ultralytics/solutions/config.py +29 -28
- ultralytics/solutions/distance_calculation.py +23 -24
- ultralytics/solutions/heatmap.py +17 -19
- ultralytics/solutions/instance_segmentation.py +21 -19
- ultralytics/solutions/object_blurrer.py +16 -17
- ultralytics/solutions/object_counter.py +48 -53
- ultralytics/solutions/object_cropper.py +22 -16
- ultralytics/solutions/parking_management.py +61 -58
- ultralytics/solutions/queue_management.py +19 -19
- ultralytics/solutions/region_counter.py +63 -50
- ultralytics/solutions/security_alarm.py +22 -25
- ultralytics/solutions/similarity_search.py +107 -60
- ultralytics/solutions/solutions.py +343 -262
- ultralytics/solutions/speed_estimation.py +35 -31
- ultralytics/solutions/streamlit_inference.py +104 -40
- ultralytics/solutions/templates/similarity-search.html +31 -24
- ultralytics/solutions/trackzone.py +24 -24
- ultralytics/solutions/vision_eye.py +11 -12
- ultralytics/trackers/__init__.py +1 -1
- ultralytics/trackers/basetrack.py +18 -27
- ultralytics/trackers/bot_sort.py +48 -39
- ultralytics/trackers/byte_tracker.py +94 -94
- ultralytics/trackers/track.py +7 -16
- ultralytics/trackers/utils/gmc.py +37 -69
- ultralytics/trackers/utils/kalman_filter.py +68 -76
- ultralytics/trackers/utils/matching.py +13 -17
- ultralytics/utils/__init__.py +251 -275
- ultralytics/utils/autobatch.py +19 -7
- ultralytics/utils/autodevice.py +68 -38
- ultralytics/utils/benchmarks.py +169 -130
- ultralytics/utils/callbacks/base.py +12 -13
- ultralytics/utils/callbacks/clearml.py +14 -15
- ultralytics/utils/callbacks/comet.py +139 -66
- ultralytics/utils/callbacks/dvc.py +19 -27
- ultralytics/utils/callbacks/hub.py +8 -6
- ultralytics/utils/callbacks/mlflow.py +6 -10
- ultralytics/utils/callbacks/neptune.py +11 -19
- ultralytics/utils/callbacks/platform.py +73 -0
- ultralytics/utils/callbacks/raytune.py +3 -4
- ultralytics/utils/callbacks/tensorboard.py +9 -12
- ultralytics/utils/callbacks/wb.py +33 -30
- ultralytics/utils/checks.py +163 -114
- ultralytics/utils/cpu.py +89 -0
- ultralytics/utils/dist.py +24 -20
- ultralytics/utils/downloads.py +176 -146
- ultralytics/utils/errors.py +11 -13
- ultralytics/utils/events.py +113 -0
- ultralytics/utils/export/__init__.py +7 -0
- ultralytics/utils/{export.py → export/engine.py} +81 -63
- ultralytics/utils/export/imx.py +294 -0
- ultralytics/utils/export/tensorflow.py +217 -0
- ultralytics/utils/files.py +33 -36
- ultralytics/utils/git.py +137 -0
- ultralytics/utils/instance.py +105 -120
- ultralytics/utils/logger.py +404 -0
- ultralytics/utils/loss.py +99 -61
- ultralytics/utils/metrics.py +649 -478
- ultralytics/utils/nms.py +337 -0
- ultralytics/utils/ops.py +263 -451
- ultralytics/utils/patches.py +70 -31
- ultralytics/utils/plotting.py +253 -223
- ultralytics/utils/tal.py +48 -61
- ultralytics/utils/torch_utils.py +244 -251
- ultralytics/utils/tqdm.py +438 -0
- ultralytics/utils/triton.py +22 -23
- ultralytics/utils/tuner.py +11 -10
- dgenerate_ultralytics_headless-8.3.137.dist-info/RECORD +0 -272
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/entry_points.txt +0 -0
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/licenses/LICENSE +0 -0
- {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/top_level.txt +0 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from collections import defaultdict
|
|
6
|
+
from typing import Any
|
|
4
7
|
|
|
5
8
|
from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
|
|
6
9
|
from ultralytics.utils.plotting import colors
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class ObjectCounter(BaseSolution):
|
|
10
|
-
"""
|
|
11
|
-
A class to manage the counting of objects in a real-time video stream based on their tracks.
|
|
13
|
+
"""A class to manage the counting of objects in a real-time video stream based on their tracks.
|
|
12
14
|
|
|
13
15
|
This class extends the BaseSolution class and provides functionality for counting objects moving in and out of a
|
|
14
16
|
specified region in a video stream. It supports both polygonal and linear regions for counting.
|
|
@@ -16,16 +18,17 @@ class ObjectCounter(BaseSolution):
|
|
|
16
18
|
Attributes:
|
|
17
19
|
in_count (int): Counter for objects moving inward.
|
|
18
20
|
out_count (int): Counter for objects moving outward.
|
|
19
|
-
counted_ids (
|
|
20
|
-
classwise_counts (
|
|
21
|
+
counted_ids (list[int]): List of IDs of objects that have been counted.
|
|
22
|
+
classwise_counts (dict[str, dict[str, int]]): Dictionary for counts, categorized by object class.
|
|
21
23
|
region_initialized (bool): Flag indicating whether the counting region has been initialized.
|
|
22
24
|
show_in (bool): Flag to control display of inward count.
|
|
23
25
|
show_out (bool): Flag to control display of outward count.
|
|
26
|
+
margin (int): Margin for background rectangle size to display counts properly.
|
|
24
27
|
|
|
25
28
|
Methods:
|
|
26
|
-
count_objects:
|
|
27
|
-
display_counts:
|
|
28
|
-
process:
|
|
29
|
+
count_objects: Count objects within a polygonal or linear region based on their tracks.
|
|
30
|
+
display_counts: Display object counts on the frame.
|
|
31
|
+
process: Process input data and update counts.
|
|
29
32
|
|
|
30
33
|
Examples:
|
|
31
34
|
>>> counter = ObjectCounter()
|
|
@@ -34,28 +37,33 @@ class ObjectCounter(BaseSolution):
|
|
|
34
37
|
>>> print(f"Inward count: {counter.in_count}, Outward count: {counter.out_count}")
|
|
35
38
|
"""
|
|
36
39
|
|
|
37
|
-
def __init__(self, **kwargs):
|
|
38
|
-
"""
|
|
40
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
41
|
+
"""Initialize the ObjectCounter class for real-time object counting in video streams."""
|
|
39
42
|
super().__init__(**kwargs)
|
|
40
43
|
|
|
41
44
|
self.in_count = 0 # Counter for objects moving inward
|
|
42
45
|
self.out_count = 0 # Counter for objects moving outward
|
|
43
46
|
self.counted_ids = [] # List of IDs of objects that have been counted
|
|
44
|
-
self.
|
|
47
|
+
self.classwise_count = defaultdict(lambda: {"IN": 0, "OUT": 0}) # Dictionary for counts, categorized by class
|
|
45
48
|
self.region_initialized = False # Flag indicating whether the region has been initialized
|
|
46
49
|
|
|
47
50
|
self.show_in = self.CFG["show_in"]
|
|
48
51
|
self.show_out = self.CFG["show_out"]
|
|
49
52
|
self.margin = self.line_width * 2 # Scales the background rectangle size to display counts properly
|
|
50
53
|
|
|
51
|
-
def count_objects(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
def count_objects(
|
|
55
|
+
self,
|
|
56
|
+
current_centroid: tuple[float, float],
|
|
57
|
+
track_id: int,
|
|
58
|
+
prev_position: tuple[float, float] | None,
|
|
59
|
+
cls: int,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Count objects within a polygonal or linear region based on their tracks.
|
|
54
62
|
|
|
55
63
|
Args:
|
|
56
|
-
current_centroid (
|
|
64
|
+
current_centroid (tuple[float, float]): Current centroid coordinates (x, y) in the current frame.
|
|
57
65
|
track_id (int): Unique identifier for the tracked object.
|
|
58
|
-
prev_position (
|
|
66
|
+
prev_position (tuple[float, float], optional): Last frame position coordinates (x, y) of the track.
|
|
59
67
|
cls (int): Class index for classwise count updates.
|
|
60
68
|
|
|
61
69
|
Examples:
|
|
@@ -71,52 +79,46 @@ class ObjectCounter(BaseSolution):
|
|
|
71
79
|
return
|
|
72
80
|
|
|
73
81
|
if len(self.region) == 2: # Linear region (defined as a line segment)
|
|
74
|
-
|
|
75
|
-
if line.intersects(self.LineString([prev_position, current_centroid])):
|
|
82
|
+
if self.r_s.intersects(self.LineString([prev_position, current_centroid])):
|
|
76
83
|
# Determine orientation of the region (vertical or horizontal)
|
|
77
84
|
if abs(self.region[0][0] - self.region[1][0]) < abs(self.region[0][1] - self.region[1][1]):
|
|
78
85
|
# Vertical region: Compare x-coordinates to determine direction
|
|
79
86
|
if current_centroid[0] > prev_position[0]: # Moving right
|
|
80
87
|
self.in_count += 1
|
|
81
|
-
self.
|
|
88
|
+
self.classwise_count[self.names[cls]]["IN"] += 1
|
|
82
89
|
else: # Moving left
|
|
83
90
|
self.out_count += 1
|
|
84
|
-
self.
|
|
91
|
+
self.classwise_count[self.names[cls]]["OUT"] += 1
|
|
85
92
|
# Horizontal region: Compare y-coordinates to determine direction
|
|
86
93
|
elif current_centroid[1] > prev_position[1]: # Moving downward
|
|
87
94
|
self.in_count += 1
|
|
88
|
-
self.
|
|
95
|
+
self.classwise_count[self.names[cls]]["IN"] += 1
|
|
89
96
|
else: # Moving upward
|
|
90
97
|
self.out_count += 1
|
|
91
|
-
self.
|
|
98
|
+
self.classwise_count[self.names[cls]]["OUT"] += 1
|
|
92
99
|
self.counted_ids.append(track_id)
|
|
93
100
|
|
|
94
101
|
elif len(self.region) > 2: # Polygonal region
|
|
95
|
-
|
|
96
|
-
if polygon.contains(self.Point(current_centroid)):
|
|
102
|
+
if self.r_s.contains(self.Point(current_centroid)):
|
|
97
103
|
# Determine motion direction for vertical or horizontal polygons
|
|
98
104
|
region_width = max(p[0] for p in self.region) - min(p[0] for p in self.region)
|
|
99
105
|
region_height = max(p[1] for p in self.region) - min(p[1] for p in self.region)
|
|
100
106
|
|
|
101
|
-
if (
|
|
102
|
-
region_width
|
|
103
|
-
and current_centroid[0] > prev_position[0]
|
|
104
|
-
or region_width >= region_height
|
|
105
|
-
and current_centroid[1] > prev_position[1]
|
|
107
|
+
if (region_width < region_height and current_centroid[0] > prev_position[0]) or (
|
|
108
|
+
region_width >= region_height and current_centroid[1] > prev_position[1]
|
|
106
109
|
): # Moving right or downward
|
|
107
110
|
self.in_count += 1
|
|
108
|
-
self.
|
|
111
|
+
self.classwise_count[self.names[cls]]["IN"] += 1
|
|
109
112
|
else: # Moving left or upward
|
|
110
113
|
self.out_count += 1
|
|
111
|
-
self.
|
|
114
|
+
self.classwise_count[self.names[cls]]["OUT"] += 1
|
|
112
115
|
self.counted_ids.append(track_id)
|
|
113
116
|
|
|
114
|
-
def display_counts(self, plot_im):
|
|
115
|
-
"""
|
|
116
|
-
Display object counts on the input image or frame.
|
|
117
|
+
def display_counts(self, plot_im) -> None:
|
|
118
|
+
"""Display object counts on the input image or frame.
|
|
117
119
|
|
|
118
120
|
Args:
|
|
119
|
-
plot_im (
|
|
121
|
+
plot_im (np.ndarray): The image or frame to display counts on.
|
|
120
122
|
|
|
121
123
|
Examples:
|
|
122
124
|
>>> counter = ObjectCounter()
|
|
@@ -126,26 +128,25 @@ class ObjectCounter(BaseSolution):
|
|
|
126
128
|
labels_dict = {
|
|
127
129
|
str.capitalize(key): f"{'IN ' + str(value['IN']) if self.show_in else ''} "
|
|
128
130
|
f"{'OUT ' + str(value['OUT']) if self.show_out else ''}".strip()
|
|
129
|
-
for key, value in self.
|
|
130
|
-
if value["IN"] != 0 or value["OUT"] != 0
|
|
131
|
+
for key, value in self.classwise_count.items()
|
|
132
|
+
if value["IN"] != 0 or (value["OUT"] != 0 and (self.show_in or self.show_out))
|
|
131
133
|
}
|
|
132
134
|
if labels_dict:
|
|
133
135
|
self.annotator.display_analytics(plot_im, labels_dict, (104, 31, 17), (255, 255, 255), self.margin)
|
|
134
136
|
|
|
135
|
-
def process(self, im0):
|
|
136
|
-
"""
|
|
137
|
-
Process input data (frames or object tracks) and update object counts.
|
|
137
|
+
def process(self, im0) -> SolutionResults:
|
|
138
|
+
"""Process input data (frames or object tracks) and update object counts.
|
|
138
139
|
|
|
139
|
-
This method initializes the counting region, extracts tracks, draws bounding boxes and regions, updates
|
|
140
|
-
|
|
140
|
+
This method initializes the counting region, extracts tracks, draws bounding boxes and regions, updates object
|
|
141
|
+
counts, and displays the results on the input image.
|
|
141
142
|
|
|
142
143
|
Args:
|
|
143
|
-
im0 (
|
|
144
|
+
im0 (np.ndarray): The input image or frame to be processed.
|
|
144
145
|
|
|
145
146
|
Returns:
|
|
146
147
|
(SolutionResults): Contains processed image `im0`, 'in_count' (int, count of objects entering the region),
|
|
147
|
-
'out_count' (int, count of objects exiting the region), 'classwise_count' (dict, per-class object
|
|
148
|
-
and 'total_tracks' (int, total number of tracked objects).
|
|
148
|
+
'out_count' (int, count of objects exiting the region), 'classwise_count' (dict, per-class object
|
|
149
|
+
count), and 'total_tracks' (int, total number of tracked objects).
|
|
149
150
|
|
|
150
151
|
Examples:
|
|
151
152
|
>>> counter = ObjectCounter()
|
|
@@ -159,10 +160,6 @@ class ObjectCounter(BaseSolution):
|
|
|
159
160
|
self.extract_tracks(im0) # Extract tracks
|
|
160
161
|
self.annotator = SolutionAnnotator(im0, line_width=self.line_width) # Initialize annotator
|
|
161
162
|
|
|
162
|
-
is_obb = getattr(self.tracks[0], "obb", None) is not None # True if OBB results exist
|
|
163
|
-
if is_obb:
|
|
164
|
-
self.boxes = self.track_data.xyxyxyxy.reshape(-1, 4, 2).cpu()
|
|
165
|
-
|
|
166
163
|
self.annotator.draw_region(
|
|
167
164
|
reg_pts=self.region, color=(104, 0, 123), thickness=self.line_width * 2
|
|
168
165
|
) # Draw region
|
|
@@ -170,10 +167,8 @@ class ObjectCounter(BaseSolution):
|
|
|
170
167
|
# Iterate over bounding boxes, track ids and classes index
|
|
171
168
|
for box, track_id, cls, conf in zip(self.boxes, self.track_ids, self.clss, self.confs):
|
|
172
169
|
# Draw bounding box and counting region
|
|
173
|
-
self.annotator.box_label(
|
|
174
|
-
|
|
175
|
-
)
|
|
176
|
-
self.store_tracking_history(track_id, box, is_obb=is_obb) # Store track history
|
|
170
|
+
self.annotator.box_label(box, label=self.adjust_box_label(cls, conf, track_id), color=colors(cls, True))
|
|
171
|
+
self.store_tracking_history(track_id, box) # Store track history
|
|
177
172
|
|
|
178
173
|
# Store previous position of track for object counting
|
|
179
174
|
prev_position = None
|
|
@@ -190,6 +185,6 @@ class ObjectCounter(BaseSolution):
|
|
|
190
185
|
plot_im=plot_im,
|
|
191
186
|
in_count=self.in_count,
|
|
192
187
|
out_count=self.out_count,
|
|
193
|
-
classwise_count=dict(self.
|
|
188
|
+
classwise_count=dict(self.classwise_count),
|
|
194
189
|
total_tracks=len(self.track_ids),
|
|
195
190
|
)
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
from ultralytics.solutions.solutions import BaseSolution, SolutionResults
|
|
7
8
|
from ultralytics.utils.plotting import save_one_box
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ObjectCropper(BaseSolution):
|
|
11
|
-
"""
|
|
12
|
-
A class to manage the cropping of detected objects in a real-time video stream or images.
|
|
12
|
+
"""A class to manage the cropping of detected objects in a real-time video stream or images.
|
|
13
13
|
|
|
14
14
|
This class extends the BaseSolution class and provides functionality for cropping objects based on detected bounding
|
|
15
15
|
boxes. The cropped images are saved to a specified directory for further analysis or usage.
|
|
@@ -21,7 +21,7 @@ class ObjectCropper(BaseSolution):
|
|
|
21
21
|
conf (float): Confidence threshold for filtering detections.
|
|
22
22
|
|
|
23
23
|
Methods:
|
|
24
|
-
process:
|
|
24
|
+
process: Crop detected objects from the input image and save them to the output directory.
|
|
25
25
|
|
|
26
26
|
Examples:
|
|
27
27
|
>>> cropper = ObjectCropper()
|
|
@@ -30,13 +30,12 @@ class ObjectCropper(BaseSolution):
|
|
|
30
30
|
>>> print(f"Total cropped objects: {cropper.crop_idx}")
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
def __init__(self, **kwargs):
|
|
34
|
-
"""
|
|
35
|
-
Initialize the ObjectCropper class for cropping objects from detected bounding boxes.
|
|
33
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
34
|
+
"""Initialize the ObjectCropper class for cropping objects from detected bounding boxes.
|
|
36
35
|
|
|
37
36
|
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.
|
|
37
|
+
**kwargs (Any): Keyword arguments passed to the parent class and used for configuration including:
|
|
38
|
+
- crop_dir (str): Path to the directory for saving cropped object images.
|
|
40
39
|
"""
|
|
41
40
|
super().__init__(**kwargs)
|
|
42
41
|
|
|
@@ -51,15 +50,15 @@ class ObjectCropper(BaseSolution):
|
|
|
51
50
|
self.iou = self.CFG["iou"]
|
|
52
51
|
self.conf = self.CFG["conf"]
|
|
53
52
|
|
|
54
|
-
def process(self, im0):
|
|
55
|
-
"""
|
|
56
|
-
Crop detected objects from the input image and save them as separate images.
|
|
53
|
+
def process(self, im0) -> SolutionResults:
|
|
54
|
+
"""Crop detected objects from the input image and save them as separate images.
|
|
57
55
|
|
|
58
56
|
Args:
|
|
59
|
-
im0 (
|
|
57
|
+
im0 (np.ndarray): The input image containing detected objects.
|
|
60
58
|
|
|
61
59
|
Returns:
|
|
62
|
-
(SolutionResults): A SolutionResults object containing the total number of cropped objects and processed
|
|
60
|
+
(SolutionResults): A SolutionResults object containing the total number of cropped objects and processed
|
|
61
|
+
image.
|
|
63
62
|
|
|
64
63
|
Examples:
|
|
65
64
|
>>> cropper = ObjectCropper()
|
|
@@ -67,9 +66,16 @@ class ObjectCropper(BaseSolution):
|
|
|
67
66
|
>>> results = cropper.process(frame)
|
|
68
67
|
>>> print(f"Total cropped objects: {results.total_crop_objects}")
|
|
69
68
|
"""
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
with self.profilers[0]:
|
|
70
|
+
results = self.model.predict(
|
|
71
|
+
im0,
|
|
72
|
+
classes=self.classes,
|
|
73
|
+
conf=self.conf,
|
|
74
|
+
iou=self.iou,
|
|
75
|
+
device=self.CFG["device"],
|
|
76
|
+
verbose=False,
|
|
77
|
+
)[0]
|
|
78
|
+
self.clss = results.boxes.cls.tolist() # required for logging only.
|
|
73
79
|
|
|
74
80
|
for box in results.boxes:
|
|
75
81
|
self.crop_idx += 1
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import json
|
|
6
|
+
from typing import Any
|
|
4
7
|
|
|
5
8
|
import cv2
|
|
6
9
|
import numpy as np
|
|
@@ -11,11 +14,10 @@ from ultralytics.utils.checks import check_imshow
|
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
class ParkingPtsSelection:
|
|
14
|
-
"""
|
|
15
|
-
A class for selecting and managing parking zone points on images using a Tkinter-based UI.
|
|
17
|
+
"""A class for selecting and managing parking zone points on images using a Tkinter-based UI.
|
|
16
18
|
|
|
17
|
-
This class provides functionality to upload an image, select points to define parking zones, and save the
|
|
18
|
-
|
|
19
|
+
This class provides functionality to upload an image, select points to define parking zones, and save the selected
|
|
20
|
+
points to a JSON file. It uses Tkinter for the graphical user interface.
|
|
19
21
|
|
|
20
22
|
Attributes:
|
|
21
23
|
tk (module): The Tkinter module for GUI operations.
|
|
@@ -25,30 +27,30 @@ class ParkingPtsSelection:
|
|
|
25
27
|
canvas (tk.Canvas): The canvas widget for displaying the image and drawing bounding boxes.
|
|
26
28
|
image (PIL.Image.Image): The uploaded image.
|
|
27
29
|
canvas_image (ImageTk.PhotoImage): The image displayed on the canvas.
|
|
28
|
-
rg_data (
|
|
29
|
-
current_box (
|
|
30
|
+
rg_data (list[list[tuple[int, int]]]): List of bounding boxes, each defined by 4 points.
|
|
31
|
+
current_box (list[tuple[int, int]]): Temporary storage for the points of the current bounding box.
|
|
30
32
|
imgw (int): Original width of the uploaded image.
|
|
31
33
|
imgh (int): Original height of the uploaded image.
|
|
32
34
|
canvas_max_width (int): Maximum width of the canvas.
|
|
33
35
|
canvas_max_height (int): Maximum height of the canvas.
|
|
34
36
|
|
|
35
37
|
Methods:
|
|
36
|
-
initialize_properties:
|
|
37
|
-
upload_image:
|
|
38
|
-
on_canvas_click:
|
|
39
|
-
draw_box:
|
|
40
|
-
remove_last_bounding_box:
|
|
41
|
-
redraw_canvas:
|
|
42
|
-
save_to_json:
|
|
38
|
+
initialize_properties: Initialize properties for image, canvas, bounding boxes, and dimensions.
|
|
39
|
+
upload_image: Upload and display an image on the canvas, resizing it to fit within specified dimensions.
|
|
40
|
+
on_canvas_click: Handle mouse clicks to add points for bounding boxes on the canvas.
|
|
41
|
+
draw_box: Draw a bounding box on the canvas using the provided coordinates.
|
|
42
|
+
remove_last_bounding_box: Remove the last bounding box from the list and redraw the canvas.
|
|
43
|
+
redraw_canvas: Redraw the canvas with the image and all bounding boxes.
|
|
44
|
+
save_to_json: Save the selected parking zone points to a JSON file with scaled coordinates.
|
|
43
45
|
|
|
44
46
|
Examples:
|
|
45
47
|
>>> parking_selector = ParkingPtsSelection()
|
|
46
48
|
>>> # Use the GUI to upload an image, select parking zones, and save the data
|
|
47
49
|
"""
|
|
48
50
|
|
|
49
|
-
def __init__(self):
|
|
51
|
+
def __init__(self) -> None:
|
|
50
52
|
"""Initialize the ParkingPtsSelection class, setting up UI and properties for parking zone point selection."""
|
|
51
|
-
try: #
|
|
53
|
+
try: # Check if tkinter is installed
|
|
52
54
|
import tkinter as tk
|
|
53
55
|
from tkinter import filedialog, messagebox
|
|
54
56
|
except ImportError: # Display error with recommendations
|
|
@@ -68,19 +70,19 @@ class ParkingPtsSelection:
|
|
|
68
70
|
return
|
|
69
71
|
|
|
70
72
|
self.tk, self.filedialog, self.messagebox = tk, filedialog, messagebox
|
|
71
|
-
self.master = self.tk.Tk() # Reference to the main application window
|
|
73
|
+
self.master = self.tk.Tk() # Reference to the main application window
|
|
72
74
|
self.master.title("Ultralytics Parking Zones Points Selector")
|
|
73
75
|
self.master.resizable(False, False)
|
|
74
76
|
|
|
75
|
-
self.canvas = self.tk.Canvas(self.master, bg="white") # Canvas widget for displaying images
|
|
77
|
+
self.canvas = self.tk.Canvas(self.master, bg="white") # Canvas widget for displaying images
|
|
76
78
|
self.canvas.pack(side=self.tk.BOTTOM)
|
|
77
79
|
|
|
78
80
|
self.image = None # Variable to store the loaded image
|
|
79
81
|
self.canvas_image = None # Reference to the image displayed on the canvas
|
|
80
82
|
self.canvas_max_width = None # Maximum allowed width for the canvas
|
|
81
83
|
self.canvas_max_height = None # Maximum allowed height for the canvas
|
|
82
|
-
self.rg_data = None # Data
|
|
83
|
-
self.current_box = None # Stores the currently selected
|
|
84
|
+
self.rg_data = None # Data for region annotation management
|
|
85
|
+
self.current_box = None # Stores the currently selected bounding box
|
|
84
86
|
self.imgh = None # Height of the current image
|
|
85
87
|
self.imgw = None # Width of the current image
|
|
86
88
|
|
|
@@ -98,21 +100,23 @@ class ParkingPtsSelection:
|
|
|
98
100
|
self.initialize_properties()
|
|
99
101
|
self.master.mainloop()
|
|
100
102
|
|
|
101
|
-
def initialize_properties(self):
|
|
103
|
+
def initialize_properties(self) -> None:
|
|
102
104
|
"""Initialize properties for image, canvas, bounding boxes, and dimensions."""
|
|
103
105
|
self.image = self.canvas_image = None
|
|
104
106
|
self.rg_data, self.current_box = [], []
|
|
105
107
|
self.imgw = self.imgh = 0
|
|
106
108
|
self.canvas_max_width, self.canvas_max_height = 1280, 720
|
|
107
109
|
|
|
108
|
-
def upload_image(self):
|
|
110
|
+
def upload_image(self) -> None:
|
|
109
111
|
"""Upload and display an image on the canvas, resizing it to fit within specified dimensions."""
|
|
110
|
-
from PIL import Image, ImageTk #
|
|
112
|
+
from PIL import Image, ImageTk # Scoped import because ImageTk requires tkinter package
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
if not
|
|
114
|
+
file = self.filedialog.askopenfilename(filetypes=[("Image Files", "*.png *.jpg *.jpeg")])
|
|
115
|
+
if not file:
|
|
116
|
+
LOGGER.info("No image selected.")
|
|
114
117
|
return
|
|
115
118
|
|
|
119
|
+
self.image = Image.open(file)
|
|
116
120
|
self.imgw, self.imgh = self.image.size
|
|
117
121
|
aspect_ratio = self.imgw / self.imgh
|
|
118
122
|
canvas_width = (
|
|
@@ -129,7 +133,7 @@ class ParkingPtsSelection:
|
|
|
129
133
|
|
|
130
134
|
self.rg_data.clear(), self.current_box.clear()
|
|
131
135
|
|
|
132
|
-
def on_canvas_click(self, event):
|
|
136
|
+
def on_canvas_click(self, event) -> None:
|
|
133
137
|
"""Handle mouse clicks to add points for bounding boxes on the canvas."""
|
|
134
138
|
self.current_box.append((event.x, event.y))
|
|
135
139
|
self.canvas.create_oval(event.x - 3, event.y - 3, event.x + 3, event.y + 3, fill="red")
|
|
@@ -138,12 +142,12 @@ class ParkingPtsSelection:
|
|
|
138
142
|
self.draw_box(self.current_box)
|
|
139
143
|
self.current_box.clear()
|
|
140
144
|
|
|
141
|
-
def draw_box(self, box):
|
|
145
|
+
def draw_box(self, box: list[tuple[int, int]]) -> None:
|
|
142
146
|
"""Draw a bounding box on the canvas using the provided coordinates."""
|
|
143
147
|
for i in range(4):
|
|
144
148
|
self.canvas.create_line(box[i], box[(i + 1) % 4], fill="blue", width=2)
|
|
145
149
|
|
|
146
|
-
def remove_last_bounding_box(self):
|
|
150
|
+
def remove_last_bounding_box(self) -> None:
|
|
147
151
|
"""Remove the last bounding box from the list and redraw the canvas."""
|
|
148
152
|
if not self.rg_data:
|
|
149
153
|
self.messagebox.showwarning("Warning", "No bounding boxes to remove.")
|
|
@@ -151,19 +155,19 @@ class ParkingPtsSelection:
|
|
|
151
155
|
self.rg_data.pop()
|
|
152
156
|
self.redraw_canvas()
|
|
153
157
|
|
|
154
|
-
def redraw_canvas(self):
|
|
158
|
+
def redraw_canvas(self) -> None:
|
|
155
159
|
"""Redraw the canvas with the image and all bounding boxes."""
|
|
156
160
|
self.canvas.delete("all")
|
|
157
161
|
self.canvas.create_image(0, 0, anchor=self.tk.NW, image=self.canvas_image)
|
|
158
162
|
for box in self.rg_data:
|
|
159
163
|
self.draw_box(box)
|
|
160
164
|
|
|
161
|
-
def save_to_json(self):
|
|
165
|
+
def save_to_json(self) -> None:
|
|
162
166
|
"""Save the selected parking zone points to a JSON file with scaled coordinates."""
|
|
163
167
|
scale_w, scale_h = self.imgw / self.canvas.winfo_width(), self.imgh / self.canvas.winfo_height()
|
|
164
168
|
data = [{"points": [(int(x * scale_w), int(y * scale_h)) for x, y in box]} for box in self.rg_data]
|
|
165
169
|
|
|
166
|
-
from io import StringIO # Function level import, as it's only required to store coordinates
|
|
170
|
+
from io import StringIO # Function level import, as it's only required to store coordinates
|
|
167
171
|
|
|
168
172
|
write_buffer = StringIO()
|
|
169
173
|
json.dump(data, write_buffer, indent=4)
|
|
@@ -173,22 +177,21 @@ class ParkingPtsSelection:
|
|
|
173
177
|
|
|
174
178
|
|
|
175
179
|
class ParkingManagement(BaseSolution):
|
|
176
|
-
"""
|
|
177
|
-
Manages parking occupancy and availability using YOLO model for real-time monitoring and visualization.
|
|
180
|
+
"""Manages parking occupancy and availability using YOLO model for real-time monitoring and visualization.
|
|
178
181
|
|
|
179
|
-
This class extends BaseSolution to provide functionality for parking lot management, including detection of
|
|
180
|
-
|
|
182
|
+
This class extends BaseSolution to provide functionality for parking lot management, including detection of occupied
|
|
183
|
+
spaces, visualization of parking regions, and display of occupancy statistics.
|
|
181
184
|
|
|
182
185
|
Attributes:
|
|
183
186
|
json_file (str): Path to the JSON file containing parking region details.
|
|
184
|
-
json (
|
|
185
|
-
pr_info (
|
|
186
|
-
arc (
|
|
187
|
-
occ (
|
|
188
|
-
dc (
|
|
187
|
+
json (list[dict]): Loaded JSON data containing parking region information.
|
|
188
|
+
pr_info (dict[str, int]): Dictionary storing parking information (Occupancy and Available spaces).
|
|
189
|
+
arc (tuple[int, int, int]): RGB color tuple for available region visualization.
|
|
190
|
+
occ (tuple[int, int, int]): RGB color tuple for occupied region visualization.
|
|
191
|
+
dc (tuple[int, int, int]): RGB color tuple for centroid visualization of detected objects.
|
|
189
192
|
|
|
190
193
|
Methods:
|
|
191
|
-
process:
|
|
194
|
+
process: Process the input image for parking lot management and visualization.
|
|
192
195
|
|
|
193
196
|
Examples:
|
|
194
197
|
>>> from ultralytics.solutions import ParkingManagement
|
|
@@ -197,7 +200,7 @@ class ParkingManagement(BaseSolution):
|
|
|
197
200
|
>>> print(f"Available spaces: {parking_manager.pr_info['Available']}")
|
|
198
201
|
"""
|
|
199
202
|
|
|
200
|
-
def __init__(self, **kwargs):
|
|
203
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
201
204
|
"""Initialize the parking management system with a YOLO model and visualization settings."""
|
|
202
205
|
super().__init__(**kwargs)
|
|
203
206
|
|
|
@@ -209,40 +212,40 @@ class ParkingManagement(BaseSolution):
|
|
|
209
212
|
with open(self.json_file) as f:
|
|
210
213
|
self.json = json.load(f)
|
|
211
214
|
|
|
212
|
-
self.pr_info = {"Occupancy": 0, "Available": 0} #
|
|
215
|
+
self.pr_info = {"Occupancy": 0, "Available": 0} # Dictionary for parking information
|
|
213
216
|
|
|
214
|
-
self.arc = (0, 0, 255) #
|
|
215
|
-
self.occ = (0, 255, 0) #
|
|
216
|
-
self.dc = (255, 0, 189) #
|
|
217
|
+
self.arc = (0, 0, 255) # Available region color
|
|
218
|
+
self.occ = (0, 255, 0) # Occupied region color
|
|
219
|
+
self.dc = (255, 0, 189) # Centroid color for each box
|
|
217
220
|
|
|
218
|
-
def process(self, im0):
|
|
219
|
-
"""
|
|
220
|
-
Process the input image for parking lot management and visualization.
|
|
221
|
+
def process(self, im0: np.ndarray) -> SolutionResults:
|
|
222
|
+
"""Process the input image for parking lot management and visualization.
|
|
221
223
|
|
|
222
|
-
This function analyzes the input image, extracts tracks, and determines the occupancy status of parking
|
|
223
|
-
|
|
224
|
-
|
|
224
|
+
This function analyzes the input image, extracts tracks, and determines the occupancy status of parking regions
|
|
225
|
+
defined in the JSON file. It annotates the image with occupied and available parking spots, and updates the
|
|
226
|
+
parking information.
|
|
225
227
|
|
|
226
228
|
Args:
|
|
227
229
|
im0 (np.ndarray): The input inference image.
|
|
228
230
|
|
|
229
231
|
Returns:
|
|
230
232
|
(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
|
|
233
|
+
'available_slots' (number of available parking slots), and 'total_tracks' (total number of
|
|
234
|
+
tracked objects).
|
|
232
235
|
|
|
233
236
|
Examples:
|
|
234
237
|
>>> parking_manager = ParkingManagement(json_file="parking_regions.json")
|
|
235
238
|
>>> image = cv2.imread("parking_lot.jpg")
|
|
236
239
|
>>> results = parking_manager.process(image)
|
|
237
240
|
"""
|
|
238
|
-
self.extract_tracks(im0) #
|
|
239
|
-
es, fs = len(self.json), 0 #
|
|
240
|
-
annotator = SolutionAnnotator(im0, self.line_width) #
|
|
241
|
+
self.extract_tracks(im0) # Extract tracks from im0
|
|
242
|
+
es, fs = len(self.json), 0 # Empty slots, filled slots
|
|
243
|
+
annotator = SolutionAnnotator(im0, self.line_width) # Initialize annotator
|
|
241
244
|
|
|
242
245
|
for region in self.json:
|
|
243
246
|
# Convert points to a NumPy array with the correct dtype and reshape properly
|
|
244
247
|
pts_array = np.array(region["points"], dtype=np.int32).reshape((-1, 1, 2))
|
|
245
|
-
rg_occupied = False #
|
|
248
|
+
rg_occupied = False # Occupied region initialization
|
|
246
249
|
for box, cls in zip(self.boxes, self.clss):
|
|
247
250
|
xc, yc = int((box[0] + box[2]) / 2), int((box[1] + box[3]) / 2)
|
|
248
251
|
dist = cv2.pointPolygonTest(pts_array, (xc, yc), False)
|
|
@@ -254,7 +257,7 @@ class ParkingManagement(BaseSolution):
|
|
|
254
257
|
rg_occupied = True
|
|
255
258
|
break
|
|
256
259
|
fs, es = (fs + 1, es - 1) if rg_occupied else (fs, es)
|
|
257
|
-
#
|
|
260
|
+
# Plot regions
|
|
258
261
|
cv2.polylines(im0, [pts_array], isClosed=True, color=self.occ if rg_occupied else self.arc, thickness=2)
|
|
259
262
|
|
|
260
263
|
self.pr_info["Occupancy"], self.pr_info["Available"] = fs, es
|
|
@@ -262,7 +265,7 @@ class ParkingManagement(BaseSolution):
|
|
|
262
265
|
annotator.display_analytics(im0, self.pr_info, (104, 31, 17), (255, 255, 255), 10)
|
|
263
266
|
|
|
264
267
|
plot_im = annotator.result()
|
|
265
|
-
self.display_output(plot_im) #
|
|
268
|
+
self.display_output(plot_im) # Display output with base class function
|
|
266
269
|
|
|
267
270
|
# Return SolutionResults
|
|
268
271
|
return SolutionResults(
|