ultralytics 8.3.4__py3-none-any.whl → 8.3.6__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.
Potentially problematic release.
This version of ultralytics might be problematic. Click here for more details.
- tests/test_solutions.py +6 -8
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/default.yaml +1 -1
- ultralytics/cfg/solutions/default.yaml +16 -0
- ultralytics/data/base.py +37 -5
- ultralytics/data/utils.py +3 -3
- ultralytics/engine/exporter.py +3 -4
- ultralytics/engine/trainer.py +4 -4
- ultralytics/engine/validator.py +2 -0
- ultralytics/solutions/ai_gym.py +62 -110
- ultralytics/solutions/heatmap.py +62 -228
- ultralytics/solutions/object_counter.py +105 -217
- ultralytics/solutions/solutions.py +93 -0
- ultralytics/utils/__init__.py +55 -54
- ultralytics/utils/checks.py +36 -20
- ultralytics/utils/plotting.py +50 -70
- ultralytics/utils/torch_utils.py +7 -2
- {ultralytics-8.3.4.dist-info → ultralytics-8.3.6.dist-info}/METADATA +8 -9
- {ultralytics-8.3.4.dist-info → ultralytics-8.3.6.dist-info}/RECORD +23 -21
- {ultralytics-8.3.4.dist-info → ultralytics-8.3.6.dist-info}/LICENSE +0 -0
- {ultralytics-8.3.4.dist-info → ultralytics-8.3.6.dist-info}/WHEEL +0 -0
- {ultralytics-8.3.4.dist-info → ultralytics-8.3.6.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.3.4.dist-info → ultralytics-8.3.6.dist-info}/top_level.txt +0 -0
tests/test_solutions.py
CHANGED
|
@@ -19,8 +19,8 @@ def test_major_solutions():
|
|
|
19
19
|
cap = cv2.VideoCapture("solutions_ci_demo.mp4")
|
|
20
20
|
assert cap.isOpened(), "Error reading video file"
|
|
21
21
|
region_points = [(20, 400), (1080, 404), (1080, 360), (20, 360)]
|
|
22
|
-
counter = solutions.ObjectCounter(
|
|
23
|
-
heatmap = solutions.Heatmap(colormap=cv2.COLORMAP_PARULA,
|
|
22
|
+
counter = solutions.ObjectCounter(region=region_points, model="yolo11n.pt", show=False)
|
|
23
|
+
heatmap = solutions.Heatmap(colormap=cv2.COLORMAP_PARULA, model="yolo11n.pt", show=False)
|
|
24
24
|
speed = solutions.SpeedEstimator(reg_pts=region_points, names=names, view_img=False)
|
|
25
25
|
queue = solutions.QueueManager(names=names, reg_pts=region_points, view_img=False)
|
|
26
26
|
while cap.isOpened():
|
|
@@ -29,8 +29,8 @@ def test_major_solutions():
|
|
|
29
29
|
break
|
|
30
30
|
original_im0 = im0.copy()
|
|
31
31
|
tracks = model.track(im0, persist=True, show=False)
|
|
32
|
-
_ = counter.
|
|
33
|
-
_ = heatmap.generate_heatmap(original_im0.copy()
|
|
32
|
+
_ = counter.count(original_im0.copy())
|
|
33
|
+
_ = heatmap.generate_heatmap(original_im0.copy())
|
|
34
34
|
_ = speed.estimate_speed(original_im0.copy(), tracks)
|
|
35
35
|
_ = queue.process_queue(original_im0.copy(), tracks)
|
|
36
36
|
cap.release()
|
|
@@ -41,16 +41,14 @@ def test_major_solutions():
|
|
|
41
41
|
def test_aigym():
|
|
42
42
|
"""Test the workouts monitoring solution."""
|
|
43
43
|
safe_download(url=WORKOUTS_SOLUTION_DEMO)
|
|
44
|
-
model = YOLO("yolo11n-pose.pt")
|
|
45
44
|
cap = cv2.VideoCapture("solution_ci_pose_demo.mp4")
|
|
46
45
|
assert cap.isOpened(), "Error reading video file"
|
|
47
|
-
|
|
46
|
+
gym = solutions.AIGym(line_width=2, kpts=[5, 11, 13])
|
|
48
47
|
while cap.isOpened():
|
|
49
48
|
success, im0 = cap.read()
|
|
50
49
|
if not success:
|
|
51
50
|
break
|
|
52
|
-
|
|
53
|
-
_ = gym_object.start_counting(im0, results)
|
|
51
|
+
_ = gym.monitor(im0)
|
|
54
52
|
cap.release()
|
|
55
53
|
cv2.destroyAllWindows()
|
|
56
54
|
|
ultralytics/__init__.py
CHANGED
ultralytics/cfg/default.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
|
2
2
|
# Default training settings and hyperparameters for medium-augmentation COCO training
|
|
3
3
|
|
|
4
|
-
task: detect # (str) YOLO task, i.e. detect, segment, classify, pose
|
|
4
|
+
task: detect # (str) YOLO task, i.e. detect, segment, classify, pose, obb
|
|
5
5
|
mode: train # (str) YOLO mode, i.e. train, val, predict, export, track, benchmark
|
|
6
6
|
|
|
7
7
|
# Train settings -------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
|
2
|
+
|
|
3
|
+
# Configuration for Ultralytics Solutions
|
|
4
|
+
|
|
5
|
+
model: "yolo11n.pt" # The Ultralytics YOLO11 model to be used (e.g., yolo11n.pt for YOLO11 nano version)
|
|
6
|
+
|
|
7
|
+
region: # Object counting, queue or speed estimation region points
|
|
8
|
+
line_width: 2 # Thickness of the lines used to draw regions on the image/video frames
|
|
9
|
+
show: True # Flag to control whether to display output image or not
|
|
10
|
+
show_in: True # Flag to display objects moving *into* the defined region
|
|
11
|
+
show_out: True # Flag to display objects moving *out of* the defined region
|
|
12
|
+
classes: # To count specific classes
|
|
13
|
+
up_angle: 145.0 # Workouts up_angle for counts, 145.0 is default value
|
|
14
|
+
down_angle: 90 # Workouts down_angle for counts, 90 is default value
|
|
15
|
+
kpts: [6, 8, 10] # Keypoints for workouts monitoring
|
|
16
|
+
colormap: # Colormap for heatmap
|
ultralytics/data/base.py
CHANGED
|
@@ -90,13 +90,15 @@ class BaseDataset(Dataset):
|
|
|
90
90
|
self.ims, self.im_hw0, self.im_hw = [None] * self.ni, [None] * self.ni, [None] * self.ni
|
|
91
91
|
self.npy_files = [Path(f).with_suffix(".npy") for f in self.im_files]
|
|
92
92
|
self.cache = cache.lower() if isinstance(cache, str) else "ram" if cache is True else None
|
|
93
|
-
if
|
|
94
|
-
if
|
|
93
|
+
if self.cache == "ram" and self.check_cache_ram():
|
|
94
|
+
if hyp.deterministic:
|
|
95
95
|
LOGGER.warning(
|
|
96
96
|
"WARNING ⚠️ cache='ram' may produce non-deterministic training results. "
|
|
97
97
|
"Consider cache='disk' as a deterministic alternative if your disk space allows."
|
|
98
98
|
)
|
|
99
99
|
self.cache_images()
|
|
100
|
+
elif self.cache == "disk" and self.check_cache_disk():
|
|
101
|
+
self.cache_images()
|
|
100
102
|
|
|
101
103
|
# Transforms
|
|
102
104
|
self.transforms = self.build_transforms(hyp=hyp)
|
|
@@ -206,25 +208,55 @@ class BaseDataset(Dataset):
|
|
|
206
208
|
if not f.exists():
|
|
207
209
|
np.save(f.as_posix(), cv2.imread(self.im_files[i]), allow_pickle=False)
|
|
208
210
|
|
|
211
|
+
def check_cache_disk(self, safety_margin=0.5):
|
|
212
|
+
"""Check image caching requirements vs available disk space."""
|
|
213
|
+
import shutil
|
|
214
|
+
|
|
215
|
+
b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
|
|
216
|
+
n = min(self.ni, 30) # extrapolate from 30 random images
|
|
217
|
+
for _ in range(n):
|
|
218
|
+
im_file = random.choice(self.im_files)
|
|
219
|
+
im = cv2.imread(im_file)
|
|
220
|
+
if im is None:
|
|
221
|
+
continue
|
|
222
|
+
b += im.nbytes
|
|
223
|
+
if not os.access(Path(im_file).parent, os.W_OK):
|
|
224
|
+
self.cache = None
|
|
225
|
+
LOGGER.info(f"{self.prefix}Skipping caching images to disk, directory not writeable ⚠️")
|
|
226
|
+
return False
|
|
227
|
+
disk_required = b * self.ni / n * (1 + safety_margin) # bytes required to cache dataset to disk
|
|
228
|
+
total, used, free = shutil.disk_usage(Path(self.im_files[0]).parent)
|
|
229
|
+
if disk_required > free:
|
|
230
|
+
self.cache = None
|
|
231
|
+
LOGGER.info(
|
|
232
|
+
f"{self.prefix}{disk_required / gb:.1f}GB disk space required, "
|
|
233
|
+
f"with {int(safety_margin * 100)}% safety margin but only "
|
|
234
|
+
f"{free / gb:.1f}/{total / gb:.1f}GB free, not caching images to disk ⚠️"
|
|
235
|
+
)
|
|
236
|
+
return False
|
|
237
|
+
return True
|
|
238
|
+
|
|
209
239
|
def check_cache_ram(self, safety_margin=0.5):
|
|
210
240
|
"""Check image caching requirements vs available memory."""
|
|
211
241
|
b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
|
|
212
242
|
n = min(self.ni, 30) # extrapolate from 30 random images
|
|
213
243
|
for _ in range(n):
|
|
214
244
|
im = cv2.imread(random.choice(self.im_files)) # sample image
|
|
245
|
+
if im is None:
|
|
246
|
+
continue
|
|
215
247
|
ratio = self.imgsz / max(im.shape[0], im.shape[1]) # max(h, w) # ratio
|
|
216
248
|
b += im.nbytes * ratio**2
|
|
217
249
|
mem_required = b * self.ni / n * (1 + safety_margin) # GB required to cache dataset into RAM
|
|
218
250
|
mem = psutil.virtual_memory()
|
|
219
|
-
|
|
220
|
-
if not success:
|
|
251
|
+
if mem_required > mem.available:
|
|
221
252
|
self.cache = None
|
|
222
253
|
LOGGER.info(
|
|
223
254
|
f"{self.prefix}{mem_required / gb:.1f}GB RAM required to cache images "
|
|
224
255
|
f"with {int(safety_margin * 100)}% safety margin but only "
|
|
225
256
|
f"{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, not caching images ⚠️"
|
|
226
257
|
)
|
|
227
|
-
|
|
258
|
+
return False
|
|
259
|
+
return True
|
|
228
260
|
|
|
229
261
|
def set_rectangle(self):
|
|
230
262
|
"""Sets the shape of bounding boxes for YOLO detections as rectangles."""
|
ultralytics/data/utils.py
CHANGED
|
@@ -216,7 +216,7 @@ def polygons2masks_overlap(imgsz, segments, downsample_ratio=1):
|
|
|
216
216
|
ms = []
|
|
217
217
|
for si in range(len(segments)):
|
|
218
218
|
mask = polygon2mask(imgsz, [segments[si].reshape(-1)], downsample_ratio=downsample_ratio, color=1)
|
|
219
|
-
ms.append(mask)
|
|
219
|
+
ms.append(mask.astype(masks.dtype))
|
|
220
220
|
areas.append(mask.sum())
|
|
221
221
|
areas = np.asarray(areas)
|
|
222
222
|
index = np.argsort(-areas)
|
|
@@ -452,12 +452,12 @@ class HUBDatasetStats:
|
|
|
452
452
|
path = Path(path).resolve()
|
|
453
453
|
LOGGER.info(f"Starting HUB dataset checks for {path}....")
|
|
454
454
|
|
|
455
|
-
self.task = task # detect, segment, pose, classify
|
|
455
|
+
self.task = task # detect, segment, pose, classify, obb
|
|
456
456
|
if self.task == "classify":
|
|
457
457
|
unzip_dir = unzip_file(path)
|
|
458
458
|
data = check_cls_dataset(unzip_dir)
|
|
459
459
|
data["path"] = unzip_dir
|
|
460
|
-
else: # detect, segment, pose
|
|
460
|
+
else: # detect, segment, pose, obb
|
|
461
461
|
_, data_dir, yaml_path = self._unzip(Path(path))
|
|
462
462
|
try:
|
|
463
463
|
# Load YAML with checks
|
ultralytics/engine/exporter.py
CHANGED
|
@@ -183,11 +183,10 @@ class Exporter:
|
|
|
183
183
|
|
|
184
184
|
# Get the closest match if format is invalid
|
|
185
185
|
matches = difflib.get_close_matches(fmt, fmts, n=1, cutoff=0.6) # 60% similarity required to match
|
|
186
|
-
if matches:
|
|
187
|
-
LOGGER.warning(f"WARNING ⚠️ Invalid export format='{fmt}', updating to format='{matches[0]}'")
|
|
188
|
-
fmt = matches[0]
|
|
189
|
-
else:
|
|
186
|
+
if not matches:
|
|
190
187
|
raise ValueError(f"Invalid export format='{fmt}'. Valid formats are {fmts}")
|
|
188
|
+
LOGGER.warning(f"WARNING ⚠️ Invalid export format='{fmt}', updating to format='{matches[0]}'")
|
|
189
|
+
fmt = matches[0]
|
|
191
190
|
flags = [x == fmt for x in fmts]
|
|
192
191
|
if sum(flags) != 1:
|
|
193
192
|
raise ValueError(f"Invalid export format='{fmt}'. Valid formats are {fmts}")
|
ultralytics/engine/trainer.py
CHANGED
|
@@ -469,11 +469,11 @@ class BaseTrainer:
|
|
|
469
469
|
|
|
470
470
|
if RANK in {-1, 0}:
|
|
471
471
|
# Do final val with best.pt
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
)
|
|
472
|
+
epochs = epoch - self.start_epoch + 1 # total training epochs
|
|
473
|
+
seconds = time.time() - self.train_time_start # total training seconds
|
|
474
|
+
LOGGER.info(f"\n{epochs} epochs completed in {seconds / 3600:.3f} hours.")
|
|
476
475
|
self.final_eval()
|
|
476
|
+
self.validator.metrics.training = {"epochs": epochs, "seconds": seconds} # add training speed
|
|
477
477
|
if self.args.plots:
|
|
478
478
|
self.plot_metrics()
|
|
479
479
|
self.run_callbacks("on_train_end")
|
ultralytics/engine/validator.py
CHANGED
|
@@ -119,6 +119,8 @@ class BaseValidator:
|
|
|
119
119
|
self.args.plots &= trainer.stopper.possible_stop or (trainer.epoch == trainer.epochs - 1)
|
|
120
120
|
model.eval()
|
|
121
121
|
else:
|
|
122
|
+
if str(self.args.model).endswith(".yaml"):
|
|
123
|
+
LOGGER.warning("WARNING ⚠️ validating an untrained model YAML will result in 0 mAP.")
|
|
122
124
|
callbacks.add_integration_callbacks(self)
|
|
123
125
|
model = AutoBackend(
|
|
124
126
|
weights=model or self.args.model,
|
ultralytics/solutions/ai_gym.py
CHANGED
|
@@ -1,127 +1,79 @@
|
|
|
1
1
|
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
from ultralytics.utils.checks import check_imshow
|
|
3
|
+
from ultralytics.solutions.solutions import BaseSolution # Import a parent class
|
|
6
4
|
from ultralytics.utils.plotting import Annotator
|
|
7
5
|
|
|
8
6
|
|
|
9
|
-
class AIGym:
|
|
7
|
+
class AIGym(BaseSolution):
|
|
10
8
|
"""A class to manage the gym steps of people in a real-time video stream based on their poses."""
|
|
11
9
|
|
|
12
|
-
def __init__(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
line_thickness=2,
|
|
16
|
-
view_img=False,
|
|
17
|
-
pose_up_angle=145.0,
|
|
18
|
-
pose_down_angle=90.0,
|
|
19
|
-
pose_type="pullup",
|
|
20
|
-
):
|
|
10
|
+
def __init__(self, **kwargs):
|
|
11
|
+
"""Initialization function for AiGYM class, a child class of BaseSolution class, can be used for workouts
|
|
12
|
+
monitoring.
|
|
21
13
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
# Check if the model name ends with '-pose'
|
|
15
|
+
if "model" in kwargs and "-pose" not in kwargs["model"]:
|
|
16
|
+
kwargs["model"] = "yolo11n-pose.pt"
|
|
17
|
+
elif "model" not in kwargs:
|
|
18
|
+
kwargs["model"] = "yolo11n-pose.pt"
|
|
19
|
+
|
|
20
|
+
super().__init__(**kwargs)
|
|
21
|
+
self.count = [] # List for counts, necessary where there are multiple objects in frame
|
|
22
|
+
self.angle = [] # List for angle, necessary where there are multiple objects in frame
|
|
23
|
+
self.stage = [] # List for stage, necessary where there are multiple objects in frame
|
|
24
|
+
|
|
25
|
+
# Extract details from CFG single time for usage later
|
|
26
|
+
self.initial_stage = None
|
|
27
|
+
self.up_angle = float(self.CFG["up_angle"]) # Pose up predefined angle to consider up pose
|
|
28
|
+
self.down_angle = float(self.CFG["down_angle"]) # Pose down predefined angle to consider down pose
|
|
29
|
+
self.kpts = self.CFG["kpts"] # User selected kpts of workouts storage for further usage
|
|
30
|
+
self.lw = self.CFG["line_width"] # Store line_width for usage
|
|
31
|
+
|
|
32
|
+
def monitor(self, im0):
|
|
31
33
|
"""
|
|
32
|
-
|
|
33
|
-
self.im0 = None
|
|
34
|
-
self.tf = line_thickness
|
|
35
|
-
|
|
36
|
-
# Keypoints and count information
|
|
37
|
-
self.keypoints = None
|
|
38
|
-
self.poseup_angle = pose_up_angle
|
|
39
|
-
self.posedown_angle = pose_down_angle
|
|
40
|
-
self.threshold = 0.001
|
|
41
|
-
|
|
42
|
-
# Store stage, count and angle information
|
|
43
|
-
self.angle = None
|
|
44
|
-
self.count = None
|
|
45
|
-
self.stage = None
|
|
46
|
-
self.pose_type = pose_type
|
|
47
|
-
self.kpts_to_check = kpts_to_check
|
|
48
|
-
|
|
49
|
-
# Visual Information
|
|
50
|
-
self.view_img = view_img
|
|
51
|
-
self.annotator = None
|
|
52
|
-
|
|
53
|
-
# Check if environment supports imshow
|
|
54
|
-
self.env_check = check_imshow(warn=True)
|
|
55
|
-
self.count = []
|
|
56
|
-
self.angle = []
|
|
57
|
-
self.stage = []
|
|
58
|
-
|
|
59
|
-
def start_counting(self, im0, results):
|
|
60
|
-
"""
|
|
61
|
-
Function used to count the gym steps.
|
|
34
|
+
Monitor the workouts using Ultralytics YOLOv8 Pose Model: https://docs.ultralytics.com/tasks/pose/.
|
|
62
35
|
|
|
63
36
|
Args:
|
|
64
|
-
im0 (ndarray):
|
|
65
|
-
|
|
37
|
+
im0 (ndarray): The input image that will be used for processing
|
|
38
|
+
Returns
|
|
39
|
+
im0 (ndarray): The processed image for more usage
|
|
66
40
|
"""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
self.
|
|
90
|
-
|
|
91
|
-
# Check and update pose stages and counts based on angle
|
|
92
|
-
if self.pose_type in {"abworkout", "pullup"}:
|
|
93
|
-
if self.angle[ind] > self.poseup_angle:
|
|
94
|
-
self.stage[ind] = "down"
|
|
95
|
-
if self.angle[ind] < self.posedown_angle and self.stage[ind] == "down":
|
|
96
|
-
self.stage[ind] = "up"
|
|
97
|
-
self.count[ind] += 1
|
|
98
|
-
|
|
99
|
-
elif self.pose_type in {"pushup", "squat"}:
|
|
100
|
-
if self.angle[ind] > self.poseup_angle:
|
|
101
|
-
self.stage[ind] = "up"
|
|
102
|
-
if self.angle[ind] < self.posedown_angle and self.stage[ind] == "up":
|
|
103
|
-
self.stage[ind] = "down"
|
|
41
|
+
# Extract tracks
|
|
42
|
+
tracks = self.model.track(source=im0, persist=True, classes=self.CFG["classes"])[0]
|
|
43
|
+
|
|
44
|
+
if tracks.boxes.id is not None:
|
|
45
|
+
# Extract and check keypoints
|
|
46
|
+
if len(tracks) > len(self.count):
|
|
47
|
+
new_human = len(tracks) - len(self.count)
|
|
48
|
+
self.angle += [0] * new_human
|
|
49
|
+
self.count += [0] * new_human
|
|
50
|
+
self.stage += ["-"] * new_human
|
|
51
|
+
|
|
52
|
+
# Initialize annotator
|
|
53
|
+
self.annotator = Annotator(im0, line_width=self.lw)
|
|
54
|
+
|
|
55
|
+
# Enumerate over keypoints
|
|
56
|
+
for ind, k in enumerate(reversed(tracks.keypoints.data)):
|
|
57
|
+
# Get keypoints and estimate the angle
|
|
58
|
+
kpts = [k[int(self.kpts[i])].cpu() for i in range(3)]
|
|
59
|
+
self.angle[ind] = self.annotator.estimate_pose_angle(*kpts)
|
|
60
|
+
im0 = self.annotator.draw_specific_points(k, self.kpts, radius=self.lw * 3)
|
|
61
|
+
|
|
62
|
+
# Determine stage and count logic based on angle thresholds
|
|
63
|
+
if self.angle[ind] < self.down_angle:
|
|
64
|
+
if self.stage[ind] == "up":
|
|
104
65
|
self.count[ind] += 1
|
|
66
|
+
self.stage[ind] = "down"
|
|
67
|
+
elif self.angle[ind] > self.up_angle:
|
|
68
|
+
self.stage[ind] = "up"
|
|
105
69
|
|
|
70
|
+
# Display angle, count, and stage text
|
|
106
71
|
self.annotator.plot_angle_and_count_and_stage(
|
|
107
|
-
angle_text=self.angle[ind],
|
|
108
|
-
count_text=self.count[ind],
|
|
109
|
-
stage_text=self.stage[ind],
|
|
110
|
-
center_kpt=k[int(self.
|
|
72
|
+
angle_text=self.angle[ind], # angle text for display
|
|
73
|
+
count_text=self.count[ind], # count text for workouts
|
|
74
|
+
stage_text=self.stage[ind], # stage position text
|
|
75
|
+
center_kpt=k[int(self.kpts[1])], # center keypoint for display
|
|
111
76
|
)
|
|
112
77
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# Display the image if environment supports it and view_img is True
|
|
117
|
-
if self.env_check and self.view_img:
|
|
118
|
-
cv2.imshow("Ultralytics YOLOv8 AI GYM", self.im0)
|
|
119
|
-
if cv2.waitKey(1) & 0xFF == ord("q"):
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
return self.im0
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if __name__ == "__main__":
|
|
126
|
-
kpts_to_check = [0, 1, 2] # example keypoints
|
|
127
|
-
aigym = AIGym(kpts_to_check)
|
|
78
|
+
self.display_output(im0) # Display output image, if environment support display
|
|
79
|
+
return im0 # return an image for writing or further usage
|