eye-cv 1.0.0__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.
- eye/__init__.py +115 -0
- eye/__init___supervision_original.py +120 -0
- eye/annotators/__init__.py +0 -0
- eye/annotators/base.py +22 -0
- eye/annotators/core.py +2699 -0
- eye/annotators/line.py +107 -0
- eye/annotators/modern.py +529 -0
- eye/annotators/trace.py +142 -0
- eye/annotators/utils.py +177 -0
- eye/assets/__init__.py +2 -0
- eye/assets/downloader.py +95 -0
- eye/assets/list.py +83 -0
- eye/classification/__init__.py +0 -0
- eye/classification/core.py +188 -0
- eye/config.py +2 -0
- eye/core/__init__.py +0 -0
- eye/core/trackers/__init__.py +1 -0
- eye/core/trackers/botsort_tracker.py +336 -0
- eye/core/trackers/bytetrack_tracker.py +284 -0
- eye/core/trackers/sort_tracker.py +200 -0
- eye/core/tracking.py +146 -0
- eye/dataset/__init__.py +0 -0
- eye/dataset/core.py +919 -0
- eye/dataset/formats/__init__.py +0 -0
- eye/dataset/formats/coco.py +258 -0
- eye/dataset/formats/pascal_voc.py +279 -0
- eye/dataset/formats/yolo.py +272 -0
- eye/dataset/utils.py +259 -0
- eye/detection/__init__.py +0 -0
- eye/detection/auto_convert.py +155 -0
- eye/detection/core.py +1529 -0
- eye/detection/detections_enhanced.py +392 -0
- eye/detection/line_zone.py +859 -0
- eye/detection/lmm.py +184 -0
- eye/detection/overlap_filter.py +270 -0
- eye/detection/tools/__init__.py +0 -0
- eye/detection/tools/csv_sink.py +181 -0
- eye/detection/tools/inference_slicer.py +288 -0
- eye/detection/tools/json_sink.py +142 -0
- eye/detection/tools/polygon_zone.py +202 -0
- eye/detection/tools/smoother.py +123 -0
- eye/detection/tools/smoothing.py +179 -0
- eye/detection/tools/smoothing_config.py +202 -0
- eye/detection/tools/transformers.py +247 -0
- eye/detection/utils.py +1175 -0
- eye/draw/__init__.py +0 -0
- eye/draw/color.py +154 -0
- eye/draw/utils.py +374 -0
- eye/filters.py +112 -0
- eye/geometry/__init__.py +0 -0
- eye/geometry/core.py +128 -0
- eye/geometry/utils.py +47 -0
- eye/keypoint/__init__.py +0 -0
- eye/keypoint/annotators.py +442 -0
- eye/keypoint/core.py +687 -0
- eye/keypoint/skeletons.py +2647 -0
- eye/metrics/__init__.py +21 -0
- eye/metrics/core.py +72 -0
- eye/metrics/detection.py +843 -0
- eye/metrics/f1_score.py +648 -0
- eye/metrics/mean_average_precision.py +628 -0
- eye/metrics/mean_average_recall.py +697 -0
- eye/metrics/precision.py +653 -0
- eye/metrics/recall.py +652 -0
- eye/metrics/utils/__init__.py +0 -0
- eye/metrics/utils/object_size.py +158 -0
- eye/metrics/utils/utils.py +9 -0
- eye/py.typed +0 -0
- eye/quick.py +104 -0
- eye/tracker/__init__.py +0 -0
- eye/tracker/byte_tracker/__init__.py +0 -0
- eye/tracker/byte_tracker/core.py +386 -0
- eye/tracker/byte_tracker/kalman_filter.py +205 -0
- eye/tracker/byte_tracker/matching.py +69 -0
- eye/tracker/byte_tracker/single_object_track.py +178 -0
- eye/tracker/byte_tracker/utils.py +18 -0
- eye/utils/__init__.py +0 -0
- eye/utils/conversion.py +132 -0
- eye/utils/file.py +159 -0
- eye/utils/image.py +794 -0
- eye/utils/internal.py +200 -0
- eye/utils/iterables.py +84 -0
- eye/utils/notebook.py +114 -0
- eye/utils/video.py +307 -0
- eye/utils_eye/__init__.py +1 -0
- eye/utils_eye/geometry.py +71 -0
- eye/utils_eye/nms.py +55 -0
- eye/validators/__init__.py +140 -0
- eye/web.py +271 -0
- eye_cv-1.0.0.dist-info/METADATA +319 -0
- eye_cv-1.0.0.dist-info/RECORD +94 -0
- eye_cv-1.0.0.dist-info/WHEEL +5 -0
- eye_cv-1.0.0.dist-info/licenses/LICENSE +21 -0
- eye_cv-1.0.0.dist-info/top_level.txt +1 -0
eye/keypoint/core.py
ADDED
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.typing as npt
|
|
9
|
+
|
|
10
|
+
from eye.config import CLASS_NAME_DATA_FIELD
|
|
11
|
+
from eye.detection.core import Detections
|
|
12
|
+
from eye.detection.utils import get_data_item, is_data_equal
|
|
13
|
+
from eye.validators import validate_keypoints_fields
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class KeyPoints:
|
|
18
|
+
"""
|
|
19
|
+
The `sv.KeyPoints` class in the eye library standardizes results from
|
|
20
|
+
various keypoint detection and pose estimation models into a consistent format. This
|
|
21
|
+
class simplifies data manipulation and filtering, providing a uniform API for
|
|
22
|
+
integration with eye [keypoints annotators](/latest/keypoint/annotators).
|
|
23
|
+
|
|
24
|
+
=== "Ultralytics"
|
|
25
|
+
|
|
26
|
+
Use [`sv.KeyPoints.from_ultralytics`](/latest/keypoint/core/#eye.keypoint.core.KeyPoints.from_ultralytics)
|
|
27
|
+
method, which accepts [YOLOv8](https://github.com/ultralytics/ultralytics)
|
|
28
|
+
pose result.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import cv2
|
|
32
|
+
import eye as sv
|
|
33
|
+
from ultralytics import YOLO
|
|
34
|
+
|
|
35
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
36
|
+
model = YOLO('yolov8s-pose.pt')
|
|
37
|
+
|
|
38
|
+
result = model(image)[0]
|
|
39
|
+
key_points = sv.KeyPoints.from_ultralytics(result)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
=== "Inference"
|
|
43
|
+
|
|
44
|
+
Use [`sv.KeyPoints.from_inference`](/latest/keypoint/core/#eye.keypoint.core.KeyPoints.from_inference)
|
|
45
|
+
method, which accepts [Inference](https://inference.roboflow.com/) pose result.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import cv2
|
|
49
|
+
import eye as sv
|
|
50
|
+
from inference import get_model
|
|
51
|
+
|
|
52
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
53
|
+
model = get_model(model_id=<POSE_MODEL_ID>, api_key=<ROBOFLOW_API_KEY>)
|
|
54
|
+
|
|
55
|
+
result = model.infer(image)[0]
|
|
56
|
+
key_points = sv.KeyPoints.from_inference(result)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
=== "MediaPipe"
|
|
60
|
+
|
|
61
|
+
Use [`sv.KeyPoints.from_mediapipe`](/latest/keypoint/core/#eye.keypoint.core.KeyPoints.from_mediapipe)
|
|
62
|
+
method, which accepts [MediaPipe](https://github.com/google-ai-edge/mediapipe)
|
|
63
|
+
pose result.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import cv2
|
|
67
|
+
import mediapipe as mp
|
|
68
|
+
import eye as sv
|
|
69
|
+
|
|
70
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
71
|
+
image_height, image_width, _ = image.shape
|
|
72
|
+
mediapipe_image = mp.Image(
|
|
73
|
+
image_format=mp.ImageFormat.SRGB,
|
|
74
|
+
data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
|
|
75
|
+
|
|
76
|
+
options = mp.tasks.vision.PoseLandmarkerOptions(
|
|
77
|
+
base_options=mp.tasks.BaseOptions(
|
|
78
|
+
model_asset_path="pose_landmarker_heavy.task"
|
|
79
|
+
),
|
|
80
|
+
running_mode=mp.tasks.vision.RunningMode.IMAGE,
|
|
81
|
+
num_poses=2)
|
|
82
|
+
|
|
83
|
+
PoseLandmarker = mp.tasks.vision.PoseLandmarker
|
|
84
|
+
with PoseLandmarker.create_from_options(options) as landmarker:
|
|
85
|
+
pose_landmarker_result = landmarker.detect(mediapipe_image)
|
|
86
|
+
|
|
87
|
+
key_points = sv.KeyPoints.from_mediapipe(
|
|
88
|
+
pose_landmarker_result, (image_width, image_height))
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
xy (np.ndarray): An array of shape `(n, m, 2)` containing
|
|
93
|
+
`n` detected objects, each composed of `m` equally-sized
|
|
94
|
+
sets of keypoints, where each point is `[x, y]`.
|
|
95
|
+
confidence (Optional[np.ndarray]): An array of shape
|
|
96
|
+
`(n, m)` containing the confidence scores of each keypoint.
|
|
97
|
+
class_id (Optional[np.ndarray]): An array of shape
|
|
98
|
+
`(n,)` containing the class ids of the detected objects.
|
|
99
|
+
data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional
|
|
100
|
+
data where each key is a string representing the data type, and the value
|
|
101
|
+
is either a NumPy array or a list of corresponding data of length `n`
|
|
102
|
+
(one entry per detected object).
|
|
103
|
+
""" # noqa: E501 // docs
|
|
104
|
+
|
|
105
|
+
xy: npt.NDArray[np.float32]
|
|
106
|
+
class_id: Optional[npt.NDArray[np.int_]] = None
|
|
107
|
+
confidence: Optional[npt.NDArray[np.float32]] = None
|
|
108
|
+
data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict)
|
|
109
|
+
|
|
110
|
+
def __post_init__(self):
|
|
111
|
+
validate_keypoints_fields(
|
|
112
|
+
xy=self.xy,
|
|
113
|
+
confidence=self.confidence,
|
|
114
|
+
class_id=self.class_id,
|
|
115
|
+
data=self.data,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def __len__(self) -> int:
|
|
119
|
+
"""
|
|
120
|
+
Returns the number of keypoints in the `sv.KeyPoints` object.
|
|
121
|
+
"""
|
|
122
|
+
return len(self.xy)
|
|
123
|
+
|
|
124
|
+
def __iter__(
|
|
125
|
+
self,
|
|
126
|
+
) -> Iterator[
|
|
127
|
+
Tuple[
|
|
128
|
+
np.ndarray,
|
|
129
|
+
Optional[np.ndarray],
|
|
130
|
+
Optional[float],
|
|
131
|
+
Optional[int],
|
|
132
|
+
Optional[int],
|
|
133
|
+
Dict[str, Union[np.ndarray, List]],
|
|
134
|
+
]
|
|
135
|
+
]:
|
|
136
|
+
"""
|
|
137
|
+
Iterates over the Keypoint object and yield a tuple of
|
|
138
|
+
`(xy, confidence, class_id, data)` for each object detection.
|
|
139
|
+
"""
|
|
140
|
+
for i in range(len(self.xy)):
|
|
141
|
+
yield (
|
|
142
|
+
self.xy[i],
|
|
143
|
+
self.confidence[i] if self.confidence is not None else None,
|
|
144
|
+
self.class_id[i] if self.class_id is not None else None,
|
|
145
|
+
get_data_item(self.data, i),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def __eq__(self, other: KeyPoints) -> bool:
|
|
149
|
+
return all(
|
|
150
|
+
[
|
|
151
|
+
np.array_equal(self.xy, other.xy),
|
|
152
|
+
np.array_equal(self.class_id, other.class_id),
|
|
153
|
+
np.array_equal(self.confidence, other.confidence),
|
|
154
|
+
is_data_equal(self.data, other.data),
|
|
155
|
+
]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def from_inference(cls, inference_result: Union[dict, Any]) -> KeyPoints:
|
|
160
|
+
"""
|
|
161
|
+
Create a `sv.KeyPoints` object from the [Roboflow](https://roboflow.com/)
|
|
162
|
+
API inference result or the [Inference](https://inference.roboflow.com/)
|
|
163
|
+
package results.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
inference_result (dict, any): The result from the
|
|
167
|
+
Roboflow API or Inference package containing predictions with keypoints.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
|
|
171
|
+
and class names, and confidences of each keypoint.
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
```python
|
|
175
|
+
import cv2
|
|
176
|
+
import eye as sv
|
|
177
|
+
from inference import get_model
|
|
178
|
+
|
|
179
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
180
|
+
model = get_model(model_id=<POSE_MODEL_ID>, api_key=<ROBOFLOW_API_KEY>)
|
|
181
|
+
|
|
182
|
+
result = model.infer(image)[0]
|
|
183
|
+
key_points = sv.KeyPoints.from_inference(result)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
import cv2
|
|
188
|
+
import eye as sv
|
|
189
|
+
from inference_sdk import InferenceHTTPClient
|
|
190
|
+
|
|
191
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
192
|
+
client = InferenceHTTPClient(
|
|
193
|
+
api_url="https://detect.roboflow.com",
|
|
194
|
+
api_key=<ROBOFLOW_API_KEY>
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
result = client.infer(image, model_id=<POSE_MODEL_ID>)
|
|
198
|
+
key_points = sv.KeyPoints.from_inference(result)
|
|
199
|
+
```
|
|
200
|
+
"""
|
|
201
|
+
if isinstance(inference_result, list):
|
|
202
|
+
raise ValueError(
|
|
203
|
+
"from_inference() operates on a single result at a time."
|
|
204
|
+
"You can retrieve it like so: inference_result = model.infer(image)[0]"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
with suppress(AttributeError):
|
|
208
|
+
inference_result = inference_result.dict(exclude_none=True, by_alias=True)
|
|
209
|
+
|
|
210
|
+
if not inference_result.get("predictions"):
|
|
211
|
+
return cls.empty()
|
|
212
|
+
|
|
213
|
+
xy = []
|
|
214
|
+
confidence = []
|
|
215
|
+
class_id = []
|
|
216
|
+
class_names = []
|
|
217
|
+
|
|
218
|
+
for prediction in inference_result["predictions"]:
|
|
219
|
+
prediction_xy = []
|
|
220
|
+
prediction_confidence = []
|
|
221
|
+
for keypoint in prediction["keypoints"]:
|
|
222
|
+
prediction_xy.append([keypoint["x"], keypoint["y"]])
|
|
223
|
+
prediction_confidence.append(keypoint["confidence"])
|
|
224
|
+
xy.append(prediction_xy)
|
|
225
|
+
confidence.append(prediction_confidence)
|
|
226
|
+
|
|
227
|
+
class_id.append(prediction["class_id"])
|
|
228
|
+
class_names.append(prediction["class"])
|
|
229
|
+
|
|
230
|
+
data = {CLASS_NAME_DATA_FIELD: np.array(class_names)}
|
|
231
|
+
|
|
232
|
+
return cls(
|
|
233
|
+
xy=np.array(xy, dtype=np.float32),
|
|
234
|
+
confidence=np.array(confidence, dtype=np.float32),
|
|
235
|
+
class_id=np.array(class_id, dtype=int),
|
|
236
|
+
data=data,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def from_mediapipe(
|
|
241
|
+
cls, mediapipe_results, resolution_wh: Tuple[int, int]
|
|
242
|
+
) -> KeyPoints:
|
|
243
|
+
"""
|
|
244
|
+
Creates a `sv.KeyPoints` instance from a
|
|
245
|
+
[MediaPipe](https://github.com/google-ai-edge/mediapipe)
|
|
246
|
+
pose landmark detection inference result.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
mediapipe_results (Union[PoseLandmarkerResult, FaceLandmarkerResult, SolutionOutputs]):
|
|
250
|
+
The output results from Mediapipe. It support pose and face landmarks
|
|
251
|
+
from `PoseLandmaker`, `FaceLandmarker` and the legacy ones
|
|
252
|
+
from `Pose` and `FaceMesh`.
|
|
253
|
+
resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)`
|
|
254
|
+
representing the resolution of the frame.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
A `sv.KeyPoints` object containing the keypoint coordinates and
|
|
258
|
+
confidences of each keypoint.
|
|
259
|
+
|
|
260
|
+
!!! tip
|
|
261
|
+
Before you start, download model bundles from the
|
|
262
|
+
[MediaPipe website](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker/index#models).
|
|
263
|
+
|
|
264
|
+
Examples:
|
|
265
|
+
```python
|
|
266
|
+
import cv2
|
|
267
|
+
import mediapipe as mp
|
|
268
|
+
import eye as sv
|
|
269
|
+
|
|
270
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
271
|
+
image_height, image_width, _ = image.shape
|
|
272
|
+
mediapipe_image = mp.Image(
|
|
273
|
+
image_format=mp.ImageFormat.SRGB,
|
|
274
|
+
data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
|
|
275
|
+
|
|
276
|
+
options = mp.tasks.vision.PoseLandmarkerOptions(
|
|
277
|
+
base_options=mp.tasks.BaseOptions(
|
|
278
|
+
model_asset_path="pose_landmarker_heavy.task"
|
|
279
|
+
),
|
|
280
|
+
running_mode=mp.tasks.vision.RunningMode.IMAGE,
|
|
281
|
+
num_poses=2)
|
|
282
|
+
|
|
283
|
+
PoseLandmarker = mp.tasks.vision.PoseLandmarker
|
|
284
|
+
with PoseLandmarker.create_from_options(options) as landmarker:
|
|
285
|
+
pose_landmarker_result = landmarker.detect(mediapipe_image)
|
|
286
|
+
|
|
287
|
+
key_points = sv.KeyPoints.from_mediapipe(
|
|
288
|
+
pose_landmarker_result, (image_width, image_height))
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
import cv2
|
|
293
|
+
import mediapipe as mp
|
|
294
|
+
import eye as sv
|
|
295
|
+
|
|
296
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
297
|
+
image_height, image_width, _ = image.shape
|
|
298
|
+
mediapipe_image = mp.Image(
|
|
299
|
+
image_format=mp.ImageFormat.SRGB,
|
|
300
|
+
data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
|
|
301
|
+
|
|
302
|
+
options = mp.tasks.vision.FaceLandmarkerOptions(
|
|
303
|
+
base_options=mp.tasks.BaseOptions(
|
|
304
|
+
model_asset_path="face_landmarker.task"
|
|
305
|
+
),
|
|
306
|
+
output_face_blendshapes=True,
|
|
307
|
+
output_facial_transformation_matrixes=True,
|
|
308
|
+
num_faces=2)
|
|
309
|
+
|
|
310
|
+
FaceLandmarker = mp.tasks.vision.FaceLandmarker
|
|
311
|
+
with FaceLandmarker.create_from_options(options) as landmarker:
|
|
312
|
+
face_landmarker_result = landmarker.detect(mediapipe_image)
|
|
313
|
+
|
|
314
|
+
key_points = sv.KeyPoints.from_mediapipe(
|
|
315
|
+
face_landmarker_result, (image_width, image_height))
|
|
316
|
+
```
|
|
317
|
+
""" # noqa: E501 // docs
|
|
318
|
+
if hasattr(mediapipe_results, "pose_landmarks"):
|
|
319
|
+
results = mediapipe_results.pose_landmarks
|
|
320
|
+
if not isinstance(mediapipe_results.pose_landmarks, list):
|
|
321
|
+
if mediapipe_results.pose_landmarks is None:
|
|
322
|
+
results = []
|
|
323
|
+
else:
|
|
324
|
+
results = [
|
|
325
|
+
[
|
|
326
|
+
landmark
|
|
327
|
+
for landmark in mediapipe_results.pose_landmarks.landmark
|
|
328
|
+
]
|
|
329
|
+
]
|
|
330
|
+
elif hasattr(mediapipe_results, "face_landmarks"):
|
|
331
|
+
results = mediapipe_results.face_landmarks
|
|
332
|
+
elif hasattr(mediapipe_results, "multi_face_landmarks"):
|
|
333
|
+
if mediapipe_results.multi_face_landmarks is None:
|
|
334
|
+
results = []
|
|
335
|
+
else:
|
|
336
|
+
results = [
|
|
337
|
+
face_landmark.landmark
|
|
338
|
+
for face_landmark in mediapipe_results.multi_face_landmarks
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
if len(results) == 0:
|
|
342
|
+
return cls.empty()
|
|
343
|
+
|
|
344
|
+
xy = []
|
|
345
|
+
confidence = []
|
|
346
|
+
for pose in results:
|
|
347
|
+
prediction_xy = []
|
|
348
|
+
prediction_confidence = []
|
|
349
|
+
for landmark in pose:
|
|
350
|
+
keypoint_xy = [
|
|
351
|
+
landmark.x * resolution_wh[0],
|
|
352
|
+
landmark.y * resolution_wh[1],
|
|
353
|
+
]
|
|
354
|
+
prediction_xy.append(keypoint_xy)
|
|
355
|
+
prediction_confidence.append(landmark.visibility)
|
|
356
|
+
|
|
357
|
+
xy.append(prediction_xy)
|
|
358
|
+
confidence.append(prediction_confidence)
|
|
359
|
+
|
|
360
|
+
return cls(
|
|
361
|
+
xy=np.array(xy, dtype=np.float32),
|
|
362
|
+
confidence=np.array(confidence, dtype=np.float32),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
@classmethod
|
|
366
|
+
def from_ultralytics(cls, ultralytics_results) -> KeyPoints:
|
|
367
|
+
"""
|
|
368
|
+
Creates a `sv.KeyPoints` instance from a
|
|
369
|
+
[YOLOv8](https://github.com/ultralytics/ultralytics) pose inference result.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
ultralytics_results (ultralytics.engine.results.Keypoints):
|
|
373
|
+
The output Results instance from YOLOv8
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
|
|
377
|
+
and class names, and confidences of each keypoint.
|
|
378
|
+
|
|
379
|
+
Examples:
|
|
380
|
+
```python
|
|
381
|
+
import cv2
|
|
382
|
+
import eye as sv
|
|
383
|
+
from ultralytics import YOLO
|
|
384
|
+
|
|
385
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
386
|
+
model = YOLO('yolov8s-pose.pt')
|
|
387
|
+
|
|
388
|
+
result = model(image)[0]
|
|
389
|
+
key_points = sv.KeyPoints.from_ultralytics(result)
|
|
390
|
+
```
|
|
391
|
+
"""
|
|
392
|
+
if ultralytics_results.keypoints.xy.numel() == 0:
|
|
393
|
+
return cls.empty()
|
|
394
|
+
|
|
395
|
+
xy = ultralytics_results.keypoints.xy.cpu().numpy()
|
|
396
|
+
class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int)
|
|
397
|
+
class_names = np.array([ultralytics_results.names[i] for i in class_id])
|
|
398
|
+
|
|
399
|
+
confidence = ultralytics_results.keypoints.conf.cpu().numpy()
|
|
400
|
+
data = {CLASS_NAME_DATA_FIELD: class_names}
|
|
401
|
+
return cls(xy, class_id, confidence, data)
|
|
402
|
+
|
|
403
|
+
@classmethod
|
|
404
|
+
def from_yolo_nas(cls, yolo_nas_results) -> KeyPoints:
|
|
405
|
+
"""
|
|
406
|
+
Create a `sv.KeyPoints` instance from a [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS-POSE.md)
|
|
407
|
+
pose inference results.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
yolo_nas_results (ImagePoseEstimationPrediction): The output object from
|
|
411
|
+
YOLO NAS.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
|
|
415
|
+
and class names, and confidences of each keypoint.
|
|
416
|
+
|
|
417
|
+
Examples:
|
|
418
|
+
```python
|
|
419
|
+
import cv2
|
|
420
|
+
import torch
|
|
421
|
+
import eye as sv
|
|
422
|
+
import super_gradients
|
|
423
|
+
|
|
424
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
425
|
+
|
|
426
|
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
427
|
+
model = super_gradients.training.models.get(
|
|
428
|
+
"yolo_nas_pose_s", pretrained_weights="coco_pose").to(device)
|
|
429
|
+
|
|
430
|
+
results = model.predict(image, conf=0.1)
|
|
431
|
+
key_points = sv.KeyPoints.from_yolo_nas(results)
|
|
432
|
+
```
|
|
433
|
+
"""
|
|
434
|
+
if len(yolo_nas_results.prediction.poses) == 0:
|
|
435
|
+
return cls.empty()
|
|
436
|
+
|
|
437
|
+
xy = yolo_nas_results.prediction.poses[:, :, :2]
|
|
438
|
+
confidence = yolo_nas_results.prediction.poses[:, :, 2]
|
|
439
|
+
|
|
440
|
+
# yolo_nas_results treats params differently.
|
|
441
|
+
# prediction.labels may not exist, whereas class_names might be None
|
|
442
|
+
if hasattr(yolo_nas_results.prediction, "labels"):
|
|
443
|
+
class_id = yolo_nas_results.prediction.labels # np.array[int]
|
|
444
|
+
else:
|
|
445
|
+
class_id = None
|
|
446
|
+
|
|
447
|
+
data = {}
|
|
448
|
+
if class_id is not None and yolo_nas_results.class_names is not None:
|
|
449
|
+
class_names = []
|
|
450
|
+
for c_id in class_id:
|
|
451
|
+
name = yolo_nas_results.class_names[c_id] # tuple[str]
|
|
452
|
+
class_names.append(name)
|
|
453
|
+
data[CLASS_NAME_DATA_FIELD] = class_names
|
|
454
|
+
|
|
455
|
+
return cls(
|
|
456
|
+
xy=xy,
|
|
457
|
+
confidence=confidence,
|
|
458
|
+
class_id=class_id,
|
|
459
|
+
data=data,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
@classmethod
|
|
463
|
+
def from_detectron2(cls, detectron2_results: Any) -> KeyPoints:
|
|
464
|
+
"""
|
|
465
|
+
Create a `sv.KeyPoints` object from the
|
|
466
|
+
[Detectron2](https://github.com/facebookresearch/detectron2) inference result.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
detectron2_results (Any): The output of a
|
|
470
|
+
Detectron2 model containing instances with prediction data.
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
|
|
474
|
+
and class names, and confidences of each keypoint.
|
|
475
|
+
|
|
476
|
+
Example:
|
|
477
|
+
```python
|
|
478
|
+
import cv2
|
|
479
|
+
import eye as sv
|
|
480
|
+
from detectron2.engine import DefaultPredictor
|
|
481
|
+
from detectron2.config import get_cfg
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
485
|
+
cfg = get_cfg()
|
|
486
|
+
cfg.merge_from_file(<CONFIG_PATH>)
|
|
487
|
+
cfg.MODEL.WEIGHTS = <WEIGHTS_PATH>
|
|
488
|
+
predictor = DefaultPredictor(cfg)
|
|
489
|
+
|
|
490
|
+
result = predictor(image)
|
|
491
|
+
keypoints = sv.KeyPoints.from_detectron2(result)
|
|
492
|
+
```
|
|
493
|
+
"""
|
|
494
|
+
|
|
495
|
+
if hasattr(detectron2_results["instances"], "pred_keypoints"):
|
|
496
|
+
if detectron2_results["instances"].pred_keypoints.cpu().numpy().size == 0:
|
|
497
|
+
return cls.empty()
|
|
498
|
+
return cls(
|
|
499
|
+
xy=detectron2_results["instances"]
|
|
500
|
+
.pred_keypoints.cpu()
|
|
501
|
+
.numpy()[:, :, :2],
|
|
502
|
+
confidence=detectron2_results["instances"]
|
|
503
|
+
.pred_keypoints.cpu()
|
|
504
|
+
.numpy()[:, :, 2],
|
|
505
|
+
class_id=detectron2_results["instances"]
|
|
506
|
+
.pred_classes.cpu()
|
|
507
|
+
.numpy()
|
|
508
|
+
.astype(int),
|
|
509
|
+
)
|
|
510
|
+
else:
|
|
511
|
+
return cls.empty()
|
|
512
|
+
|
|
513
|
+
def __getitem__(
|
|
514
|
+
self, index: Union[int, slice, List[int], np.ndarray, str]
|
|
515
|
+
) -> Union[KeyPoints, List, np.ndarray, None]:
|
|
516
|
+
"""
|
|
517
|
+
Get a subset of the `sv.KeyPoints` object or access an item from its data field.
|
|
518
|
+
|
|
519
|
+
When provided with an integer, slice, list of integers, or a numpy array, this
|
|
520
|
+
method returns a new `sv.KeyPoints` object that represents a subset of the
|
|
521
|
+
original `sv.KeyPoints`. When provided with a string, it accesses the
|
|
522
|
+
corresponding item in the data dictionary.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
index (Union[int, slice, List[int], np.ndarray, str]): The index, indices,
|
|
526
|
+
or key to access a subset of the `sv.KeyPoints` or an item from the
|
|
527
|
+
data.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
A subset of the `sv.KeyPoints` object or an item from the data field.
|
|
531
|
+
|
|
532
|
+
Examples:
|
|
533
|
+
```python
|
|
534
|
+
import eye as sv
|
|
535
|
+
|
|
536
|
+
key_points = sv.KeyPoints()
|
|
537
|
+
|
|
538
|
+
# access the first keypoint using an integer index
|
|
539
|
+
key_points[0]
|
|
540
|
+
|
|
541
|
+
# access the first 10 keypoints using index slice
|
|
542
|
+
key_points[0:10]
|
|
543
|
+
|
|
544
|
+
# access selected keypoints using a list of indices
|
|
545
|
+
key_points[[0, 2, 4]]
|
|
546
|
+
|
|
547
|
+
# access keypoints with selected class_id
|
|
548
|
+
key_points[key_points.class_id == 0]
|
|
549
|
+
|
|
550
|
+
# access keypoints with confidence greater than 0.5
|
|
551
|
+
key_points[key_points.confidence > 0.5]
|
|
552
|
+
```
|
|
553
|
+
"""
|
|
554
|
+
if isinstance(index, str):
|
|
555
|
+
return self.data.get(index)
|
|
556
|
+
if isinstance(index, int):
|
|
557
|
+
index = [index]
|
|
558
|
+
return KeyPoints(
|
|
559
|
+
xy=self.xy[index],
|
|
560
|
+
confidence=self.confidence[index] if self.confidence is not None else None,
|
|
561
|
+
class_id=self.class_id[index] if self.class_id is not None else None,
|
|
562
|
+
data=get_data_item(self.data, index),
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
def __setitem__(self, key: str, value: Union[np.ndarray, List]):
|
|
566
|
+
"""
|
|
567
|
+
Set a value in the data dictionary of the `sv.KeyPoints` object.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
key (str): The key in the data dictionary to set.
|
|
571
|
+
value (Union[np.ndarray, List]): The value to set for the key.
|
|
572
|
+
|
|
573
|
+
Examples:
|
|
574
|
+
```python
|
|
575
|
+
import cv2
|
|
576
|
+
import eye as sv
|
|
577
|
+
from ultralytics import YOLO
|
|
578
|
+
|
|
579
|
+
image = cv2.imread(<SOURCE_IMAGE_PATH>)
|
|
580
|
+
model = YOLO('yolov8s.pt')
|
|
581
|
+
|
|
582
|
+
result = model(image)[0]
|
|
583
|
+
keypoints = sv.KeyPoints.from_ultralytics(result)
|
|
584
|
+
|
|
585
|
+
keypoints['class_name'] = [
|
|
586
|
+
model.model.names[class_id]
|
|
587
|
+
for class_id
|
|
588
|
+
in keypoints.class_id
|
|
589
|
+
]
|
|
590
|
+
```
|
|
591
|
+
"""
|
|
592
|
+
if not isinstance(value, (np.ndarray, list)):
|
|
593
|
+
raise TypeError("Value must be a np.ndarray or a list")
|
|
594
|
+
|
|
595
|
+
if isinstance(value, list):
|
|
596
|
+
value = np.array(value)
|
|
597
|
+
|
|
598
|
+
self.data[key] = value
|
|
599
|
+
|
|
600
|
+
@classmethod
|
|
601
|
+
def empty(cls) -> KeyPoints:
|
|
602
|
+
"""
|
|
603
|
+
Create an empty Keypoints object with no keypoints.
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
An empty `sv.KeyPoints` object.
|
|
607
|
+
|
|
608
|
+
Examples:
|
|
609
|
+
```python
|
|
610
|
+
import eye as sv
|
|
611
|
+
|
|
612
|
+
key_points = sv.KeyPoints.empty()
|
|
613
|
+
```
|
|
614
|
+
"""
|
|
615
|
+
return cls(xy=np.empty((0, 0, 2), dtype=np.float32))
|
|
616
|
+
|
|
617
|
+
def is_empty(self) -> bool:
|
|
618
|
+
"""
|
|
619
|
+
Returns `True` if the `KeyPoints` object is considered empty.
|
|
620
|
+
"""
|
|
621
|
+
empty_keypoints = KeyPoints.empty()
|
|
622
|
+
empty_keypoints.data = self.data
|
|
623
|
+
return self == empty_keypoints
|
|
624
|
+
|
|
625
|
+
def as_detections(
|
|
626
|
+
self, selected_keypoint_indices: Optional[Iterable[int]] = None
|
|
627
|
+
) -> Detections:
|
|
628
|
+
"""
|
|
629
|
+
Convert a KeyPoints object to a Detections object. This
|
|
630
|
+
approximates the bounding box of the detected object by
|
|
631
|
+
taking the bounding box that fits all keypoints.
|
|
632
|
+
|
|
633
|
+
Arguments:
|
|
634
|
+
selected_keypoint_indices (Optional[Iterable[int]]): The
|
|
635
|
+
indices of the keypoints to include in the bounding box
|
|
636
|
+
calculation. This helps focus on a subset of keypoints,
|
|
637
|
+
e.g. when some are occluded. Captures all keypoints by default.
|
|
638
|
+
|
|
639
|
+
Returns:
|
|
640
|
+
detections (Detections): The converted detections object.
|
|
641
|
+
|
|
642
|
+
Example:
|
|
643
|
+
```python
|
|
644
|
+
keypoints = sv.KeyPoints.from_inference(...)
|
|
645
|
+
detections = keypoints.as_detections()
|
|
646
|
+
```
|
|
647
|
+
"""
|
|
648
|
+
if self.is_empty():
|
|
649
|
+
return Detections.empty()
|
|
650
|
+
|
|
651
|
+
detections_list = []
|
|
652
|
+
for i, xy in enumerate(self.xy):
|
|
653
|
+
if selected_keypoint_indices:
|
|
654
|
+
xy = xy[selected_keypoint_indices]
|
|
655
|
+
|
|
656
|
+
# [0, 0] used by some frameworks to indicate missing keypoints
|
|
657
|
+
xy = xy[~np.all(xy == 0, axis=1)]
|
|
658
|
+
if len(xy) == 0:
|
|
659
|
+
xyxy = np.array([[0, 0, 0, 0]], dtype=np.float32)
|
|
660
|
+
else:
|
|
661
|
+
x_min = xy[:, 0].min()
|
|
662
|
+
x_max = xy[:, 0].max()
|
|
663
|
+
y_min = xy[:, 1].min()
|
|
664
|
+
y_max = xy[:, 1].max()
|
|
665
|
+
xyxy = np.array([[x_min, y_min, x_max, y_max]], dtype=np.float32)
|
|
666
|
+
|
|
667
|
+
if self.confidence is None:
|
|
668
|
+
confidence = None
|
|
669
|
+
else:
|
|
670
|
+
confidence = self.confidence[i]
|
|
671
|
+
if selected_keypoint_indices:
|
|
672
|
+
confidence = confidence[selected_keypoint_indices]
|
|
673
|
+
confidence = np.array([confidence.mean()], dtype=np.float32)
|
|
674
|
+
|
|
675
|
+
detections_list.append(
|
|
676
|
+
Detections(
|
|
677
|
+
xyxy=xyxy,
|
|
678
|
+
confidence=confidence,
|
|
679
|
+
)
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
detections = Detections.merge(detections_list)
|
|
683
|
+
detections.class_id = self.class_id
|
|
684
|
+
detections.data = self.data
|
|
685
|
+
detections = detections[detections.area > 0]
|
|
686
|
+
|
|
687
|
+
return detections
|