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
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from collections import defaultdict, deque
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from eye.detection.core import Detections
|
|
9
|
+
from eye.utils.internal import EyeWarnings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DetectionsSmoother:
|
|
13
|
+
"""
|
|
14
|
+
A utility class for smoothing detections over multiple frames in video tracking.
|
|
15
|
+
It maintains a history of detections for each track and provides smoothed
|
|
16
|
+
predictions based on these histories.
|
|
17
|
+
|
|
18
|
+
<video controls>
|
|
19
|
+
<source
|
|
20
|
+
src="https://media.roboflow.com/eye-detection-smoothing.mp4"
|
|
21
|
+
type="video/mp4">
|
|
22
|
+
</video>
|
|
23
|
+
|
|
24
|
+
!!! warning
|
|
25
|
+
|
|
26
|
+
- `DetectionsSmoother` requires the `tracker_id` for each detection. Refer to
|
|
27
|
+
Eye Trackers for information on integrating tracking into your inference pipeline.
|
|
28
|
+
- This class is not compatible with segmentation models.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
```python
|
|
32
|
+
import eye as sv
|
|
33
|
+
|
|
34
|
+
from ultralytics import YOLO
|
|
35
|
+
|
|
36
|
+
video_info = sv.VideoInfo.from_video_path(video_path=<SOURCE_FILE_PATH>)
|
|
37
|
+
frame_generator = sv.get_video_frames_generator(source_path=<SOURCE_FILE_PATH>)
|
|
38
|
+
|
|
39
|
+
model = YOLO(<MODEL_PATH>)
|
|
40
|
+
tracker = sv.ByteTrack(frame_rate=video_info.fps)
|
|
41
|
+
smoother = sv.DetectionsSmoother()
|
|
42
|
+
|
|
43
|
+
box_annotator = sv.BoxAnnotator()
|
|
44
|
+
|
|
45
|
+
with sv.VideoSink(<TARGET_FILE_PATH>, video_info=video_info) as sink:
|
|
46
|
+
for frame in frame_generator:
|
|
47
|
+
result = model(frame)[0]
|
|
48
|
+
detections = sv.Detections.from_ultralytics(result)
|
|
49
|
+
detections = tracker.update_with_detections(detections)
|
|
50
|
+
detections = smoother.update_with_detections(detections)
|
|
51
|
+
|
|
52
|
+
annotated_frame = box_annotator.annotate(frame.copy(), detections)
|
|
53
|
+
sink.write_frame(annotated_frame)
|
|
54
|
+
```
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, length: int = 5) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Args:
|
|
60
|
+
length (int): The maximum number of frames to consider for smoothing
|
|
61
|
+
detections. Defaults to 5.
|
|
62
|
+
"""
|
|
63
|
+
self.tracks = defaultdict(lambda: deque(maxlen=length))
|
|
64
|
+
|
|
65
|
+
def update_with_detections(self, detections: Detections) -> Detections:
|
|
66
|
+
"""
|
|
67
|
+
Updates the smoother with a new set of detections from a frame.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
detections (Detections): The detections to add to the smoother.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
if detections.tracker_id is None:
|
|
74
|
+
warnings.warn(
|
|
75
|
+
"Smoothing skipped. DetectionsSmoother requires tracker_id. Refer to "
|
|
76
|
+
"Eye trackers are required for smoothing. "
|
|
77
|
+
"Ensure tracker_id is available.",
|
|
78
|
+
category=EyeWarnings,
|
|
79
|
+
)
|
|
80
|
+
return detections
|
|
81
|
+
|
|
82
|
+
for detection_idx in range(len(detections)):
|
|
83
|
+
tracker_id = detections.tracker_id[detection_idx]
|
|
84
|
+
|
|
85
|
+
self.tracks[tracker_id].append(detections[detection_idx])
|
|
86
|
+
|
|
87
|
+
for track_id in self.tracks.keys():
|
|
88
|
+
if track_id not in detections.tracker_id:
|
|
89
|
+
self.tracks[track_id].append(None)
|
|
90
|
+
|
|
91
|
+
for track_id in list(self.tracks.keys()):
|
|
92
|
+
if all([d is None for d in self.tracks[track_id]]):
|
|
93
|
+
del self.tracks[track_id]
|
|
94
|
+
|
|
95
|
+
return self.get_smoothed_detections()
|
|
96
|
+
|
|
97
|
+
def get_track(self, track_id: int) -> Optional[Detections]:
|
|
98
|
+
track = self.tracks.get(track_id, None)
|
|
99
|
+
if track is None:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
track = [d for d in track if d is not None]
|
|
103
|
+
if len(track) == 0:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
ret = deepcopy(track[0])
|
|
107
|
+
ret.xyxy = np.mean([d.xyxy for d in track], axis=0)
|
|
108
|
+
ret.confidence = np.mean([d.confidence for d in track], axis=0)
|
|
109
|
+
|
|
110
|
+
return ret
|
|
111
|
+
|
|
112
|
+
def get_smoothed_detections(self) -> Detections:
|
|
113
|
+
tracked_detections = []
|
|
114
|
+
for track_id in self.tracks:
|
|
115
|
+
track = self.get_track(track_id)
|
|
116
|
+
if track is not None:
|
|
117
|
+
tracked_detections.append(track)
|
|
118
|
+
|
|
119
|
+
detections = Detections.merge(tracked_detections)
|
|
120
|
+
if len(detections) == 0:
|
|
121
|
+
detections.tracker_id = np.array([], dtype=int)
|
|
122
|
+
|
|
123
|
+
return detections
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Advanced jitter reduction and smoothing for tracking."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from filterpy.kalman import KalmanFilter
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KalmanSmoothing:
|
|
9
|
+
"""Kalman filter for smooth tracking (jitter reduction).
|
|
10
|
+
|
|
11
|
+
Use for: Smooth camera movement, reduce box jitter with predictive modeling.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
process_noise: float = 0.01,
|
|
17
|
+
measurement_noise: float = 0.1
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Args:
|
|
21
|
+
process_noise: How much the object can move between frames (lower = smoother)
|
|
22
|
+
measurement_noise: Trust in measurements (lower = trust more)
|
|
23
|
+
"""
|
|
24
|
+
self.process_noise = process_noise
|
|
25
|
+
self.measurement_noise = measurement_noise
|
|
26
|
+
self.filters: Dict[int, KalmanFilter] = {}
|
|
27
|
+
|
|
28
|
+
def update(self, tracker_id: int, box: np.ndarray) -> np.ndarray:
|
|
29
|
+
"""Smooth box using Kalman filter.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
tracker_id: Object ID
|
|
33
|
+
box: [x1, y1, x2, y2]
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Smoothed box
|
|
37
|
+
"""
|
|
38
|
+
if tracker_id not in self.filters:
|
|
39
|
+
self._init_filter(tracker_id, box)
|
|
40
|
+
|
|
41
|
+
kf = self.filters[tracker_id]
|
|
42
|
+
|
|
43
|
+
# Measurement: [x_center, y_center, width, height]
|
|
44
|
+
cx = (box[0] + box[2]) / 2
|
|
45
|
+
cy = (box[1] + box[3]) / 2
|
|
46
|
+
w = box[2] - box[0]
|
|
47
|
+
h = box[3] - box[1]
|
|
48
|
+
measurement = np.array([cx, cy, w, h])
|
|
49
|
+
|
|
50
|
+
# Predict and update
|
|
51
|
+
kf.predict()
|
|
52
|
+
kf.update(measurement)
|
|
53
|
+
|
|
54
|
+
# Get smoothed state
|
|
55
|
+
state = kf.x.flatten()
|
|
56
|
+
smoothed_cx, smoothed_cy, smoothed_w, smoothed_h = state[:4]
|
|
57
|
+
|
|
58
|
+
# Convert back to xyxy
|
|
59
|
+
return np.array([
|
|
60
|
+
smoothed_cx - smoothed_w / 2,
|
|
61
|
+
smoothed_cy - smoothed_h / 2,
|
|
62
|
+
smoothed_cx + smoothed_w / 2,
|
|
63
|
+
smoothed_cy + smoothed_h / 2
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
def _init_filter(self, tracker_id: int, box: np.ndarray):
|
|
67
|
+
"""Initialize Kalman filter for new track."""
|
|
68
|
+
kf = KalmanFilter(dim_x=8, dim_z=4)
|
|
69
|
+
|
|
70
|
+
# State: [cx, cy, w, h, vcx, vcy, vw, vh]
|
|
71
|
+
cx = (box[0] + box[2]) / 2
|
|
72
|
+
cy = (box[1] + box[3]) / 2
|
|
73
|
+
w = box[2] - box[0]
|
|
74
|
+
h = box[3] - box[1]
|
|
75
|
+
kf.x = np.array([cx, cy, w, h, 0, 0, 0, 0]).reshape(8, 1)
|
|
76
|
+
|
|
77
|
+
# State transition matrix
|
|
78
|
+
kf.F = np.eye(8)
|
|
79
|
+
kf.F[:4, 4:] = np.eye(4)
|
|
80
|
+
|
|
81
|
+
# Measurement matrix
|
|
82
|
+
kf.H = np.zeros((4, 8))
|
|
83
|
+
kf.H[:4, :4] = np.eye(4)
|
|
84
|
+
|
|
85
|
+
# Covariance matrices
|
|
86
|
+
kf.P *= 10
|
|
87
|
+
kf.R *= self.measurement_noise
|
|
88
|
+
kf.Q[4:, 4:] *= self.process_noise
|
|
89
|
+
|
|
90
|
+
self.filters[tracker_id] = kf
|
|
91
|
+
|
|
92
|
+
def reset(self, tracker_id: int = None):
|
|
93
|
+
"""Reset filter(s)."""
|
|
94
|
+
if tracker_id is None:
|
|
95
|
+
self.filters.clear()
|
|
96
|
+
elif tracker_id in self.filters:
|
|
97
|
+
del self.filters[tracker_id]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ExponentialSmoothing:
|
|
101
|
+
"""Exponential moving average for simple smoothing.
|
|
102
|
+
|
|
103
|
+
Use for: Quick smoothing, less computational overhead than Kalman.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(self, alpha: float = 0.3):
|
|
107
|
+
"""
|
|
108
|
+
Args:
|
|
109
|
+
alpha: Smoothing factor (0-1)
|
|
110
|
+
Lower = smoother but slower response
|
|
111
|
+
Higher = faster response but less smooth
|
|
112
|
+
"""
|
|
113
|
+
self.alpha = alpha
|
|
114
|
+
self.previous: Dict[int, np.ndarray] = {}
|
|
115
|
+
|
|
116
|
+
def update(self, tracker_id: int, box: np.ndarray) -> np.ndarray:
|
|
117
|
+
"""Smooth box using exponential moving average.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
tracker_id: Object ID
|
|
121
|
+
box: [x1, y1, x2, y2]
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Smoothed box
|
|
125
|
+
"""
|
|
126
|
+
if tracker_id not in self.previous:
|
|
127
|
+
self.previous[tracker_id] = box
|
|
128
|
+
return box
|
|
129
|
+
|
|
130
|
+
# EMA: smoothed = alpha * new + (1 - alpha) * previous
|
|
131
|
+
smoothed = self.alpha * box + (1 - self.alpha) * self.previous[tracker_id]
|
|
132
|
+
self.previous[tracker_id] = smoothed
|
|
133
|
+
|
|
134
|
+
return smoothed
|
|
135
|
+
|
|
136
|
+
def reset(self, tracker_id: int = None):
|
|
137
|
+
"""Reset smoothing history."""
|
|
138
|
+
if tracker_id is None:
|
|
139
|
+
self.previous.clear()
|
|
140
|
+
elif tracker_id in self.previous:
|
|
141
|
+
del self.previous[tracker_id]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def smooth_detections(
|
|
145
|
+
detections,
|
|
146
|
+
smoother,
|
|
147
|
+
inplace: bool = False
|
|
148
|
+
):
|
|
149
|
+
"""Apply smoothing to all detections.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
detections: Detections object
|
|
153
|
+
smoother: KalmanSmoothing or ExponentialSmoothing instance
|
|
154
|
+
inplace: Modify in place (faster) or return new
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Smoothed detections
|
|
158
|
+
"""
|
|
159
|
+
if detections.tracker_id is None or len(detections) == 0:
|
|
160
|
+
return detections
|
|
161
|
+
|
|
162
|
+
smoothed_boxes = np.empty_like(detections.xyxy)
|
|
163
|
+
|
|
164
|
+
for i in range(len(detections)):
|
|
165
|
+
tid = detections.tracker_id[i]
|
|
166
|
+
smoothed_boxes[i] = smoother.update(tid, detections.xyxy[i])
|
|
167
|
+
|
|
168
|
+
if inplace:
|
|
169
|
+
object.__setattr__(detections, 'xyxy', smoothed_boxes)
|
|
170
|
+
return detections
|
|
171
|
+
else:
|
|
172
|
+
from eye.detection.core import Detections
|
|
173
|
+
return Detections(
|
|
174
|
+
xyxy=smoothed_boxes,
|
|
175
|
+
confidence=detections.confidence,
|
|
176
|
+
class_id=detections.class_id,
|
|
177
|
+
tracker_id=detections.tracker_id,
|
|
178
|
+
data=detections.data
|
|
179
|
+
)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Easy smoothing configuration with presets and recommendations."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SmoothingPreset(Enum):
|
|
8
|
+
"""Predefined smoothing presets for common scenarios."""
|
|
9
|
+
NONE = "none"
|
|
10
|
+
LIGHT = "light"
|
|
11
|
+
MEDIUM = "medium"
|
|
12
|
+
HEAVY = "heavy"
|
|
13
|
+
ULTRA = "ultra"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SmoothingConfig:
|
|
17
|
+
"""Easy-to-configure smoothing settings.
|
|
18
|
+
|
|
19
|
+
Just pick a preset or customize!
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
```python
|
|
23
|
+
import eye as eye
|
|
24
|
+
|
|
25
|
+
# Use preset
|
|
26
|
+
config = eye.SmoothingConfig(eye.SmoothingPreset.HEAVY)
|
|
27
|
+
|
|
28
|
+
# Get recommendation
|
|
29
|
+
config = eye.SmoothingConfig.get_recommendation("highway")
|
|
30
|
+
|
|
31
|
+
# Custom alpha
|
|
32
|
+
config = eye.SmoothingConfig(eye.SmoothingPreset.MEDIUM, custom_alpha=0.25)
|
|
33
|
+
|
|
34
|
+
# Use with ExponentialSmoothing
|
|
35
|
+
smoother = eye.ExponentialSmoothing(alpha=config.alpha)
|
|
36
|
+
```
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
PRESETS = {
|
|
40
|
+
SmoothingPreset.NONE: {
|
|
41
|
+
'enabled': False,
|
|
42
|
+
'alpha': 1.0,
|
|
43
|
+
'description': 'No smoothing (raw detections)'
|
|
44
|
+
},
|
|
45
|
+
SmoothingPreset.LIGHT: {
|
|
46
|
+
'enabled': True,
|
|
47
|
+
'alpha': 0.5,
|
|
48
|
+
'description': 'Light smoothing (fast response, minimal jitter reduction)'
|
|
49
|
+
},
|
|
50
|
+
SmoothingPreset.MEDIUM: {
|
|
51
|
+
'enabled': True,
|
|
52
|
+
'alpha': 0.3,
|
|
53
|
+
'description': 'Medium smoothing (balanced - RECOMMENDED)'
|
|
54
|
+
},
|
|
55
|
+
SmoothingPreset.HEAVY: {
|
|
56
|
+
'enabled': True,
|
|
57
|
+
'alpha': 0.15,
|
|
58
|
+
'description': 'Heavy smoothing (slow response, max jitter reduction)'
|
|
59
|
+
},
|
|
60
|
+
SmoothingPreset.ULTRA: {
|
|
61
|
+
'enabled': True,
|
|
62
|
+
'alpha': 0.05,
|
|
63
|
+
'description': 'Ultra smooth (very slow response, cinematic look)'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
preset: SmoothingPreset = SmoothingPreset.MEDIUM,
|
|
70
|
+
custom_alpha: Optional[float] = None
|
|
71
|
+
):
|
|
72
|
+
"""
|
|
73
|
+
Args:
|
|
74
|
+
preset: Preset smoothing level
|
|
75
|
+
custom_alpha: Override alpha (0-1, lower = smoother)
|
|
76
|
+
"""
|
|
77
|
+
self.preset = preset
|
|
78
|
+
self.config = self.PRESETS[preset].copy()
|
|
79
|
+
|
|
80
|
+
if custom_alpha is not None:
|
|
81
|
+
self.config['alpha'] = custom_alpha
|
|
82
|
+
self.config['enabled'] = True
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def enabled(self) -> bool:
|
|
86
|
+
"""Is smoothing enabled?"""
|
|
87
|
+
return self.config['enabled']
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def alpha(self) -> float:
|
|
91
|
+
"""Smoothing alpha value."""
|
|
92
|
+
return self.config['alpha']
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def description(self) -> str:
|
|
96
|
+
"""Description of current settings."""
|
|
97
|
+
return self.config['description']
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def get_recommendation(cls, use_case: str) -> 'SmoothingConfig':
|
|
101
|
+
"""Get recommended smoothing for use case.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
use_case: Scenario name (highway, city, sports, surveillance, etc.)
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
SmoothingConfig with recommended preset
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> # For Dakar racing (fast vehicles)
|
|
111
|
+
>>> config = SmoothingConfig.get_recommendation("highway")
|
|
112
|
+
>>> smoother = eye.ExponentialSmoothing(alpha=config.alpha)
|
|
113
|
+
"""
|
|
114
|
+
recommendations = {
|
|
115
|
+
# Traffic scenarios
|
|
116
|
+
'highway': SmoothingPreset.LIGHT, # Fast vehicles (Dakar!)
|
|
117
|
+
'racing': SmoothingPreset.LIGHT, # Racing vehicles
|
|
118
|
+
'dakar': SmoothingPreset.LIGHT, # Dakar racing
|
|
119
|
+
'city': SmoothingPreset.MEDIUM, # Mixed speeds
|
|
120
|
+
'parking': SmoothingPreset.HEAVY, # Slow, stable
|
|
121
|
+
|
|
122
|
+
# People scenarios
|
|
123
|
+
'pedestrians': SmoothingPreset.MEDIUM, # Walking
|
|
124
|
+
'crowd': SmoothingPreset.HEAVY, # Dense, slow
|
|
125
|
+
'running': SmoothingPreset.LIGHT, # Fast motion
|
|
126
|
+
|
|
127
|
+
# Sports
|
|
128
|
+
'sports': SmoothingPreset.LIGHT, # Fast, dynamic
|
|
129
|
+
'soccer': SmoothingPreset.LIGHT,
|
|
130
|
+
'basketball': SmoothingPreset.LIGHT,
|
|
131
|
+
|
|
132
|
+
# Surveillance
|
|
133
|
+
'surveillance': SmoothingPreset.HEAVY, # Stable, professional
|
|
134
|
+
'security': SmoothingPreset.HEAVY,
|
|
135
|
+
|
|
136
|
+
# Drones/aerial
|
|
137
|
+
'drone': SmoothingPreset.ULTRA, # Camera shake
|
|
138
|
+
'aerial': SmoothingPreset.ULTRA,
|
|
139
|
+
|
|
140
|
+
# Warehouse/retail
|
|
141
|
+
'warehouse': SmoothingPreset.MEDIUM,
|
|
142
|
+
'retail': SmoothingPreset.MEDIUM,
|
|
143
|
+
|
|
144
|
+
# Default
|
|
145
|
+
'default': SmoothingPreset.MEDIUM
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
preset = recommendations.get(use_case.lower(), SmoothingPreset.MEDIUM)
|
|
149
|
+
return cls(preset)
|
|
150
|
+
|
|
151
|
+
def __repr__(self) -> str:
|
|
152
|
+
return f"SmoothingConfig(preset={self.preset.value}, alpha={self.alpha}, enabled={self.enabled})"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_smoothing_guide() -> Dict[str, Any]:
|
|
156
|
+
"""Get complete smoothing configuration guide.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Dictionary with presets, parameters, and tips
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
>>> guide = eye.get_smoothing_guide()
|
|
163
|
+
>>> print(guide['tips'])
|
|
164
|
+
"""
|
|
165
|
+
return {
|
|
166
|
+
'presets': {
|
|
167
|
+
preset.value: {
|
|
168
|
+
'alpha': config['alpha'],
|
|
169
|
+
'description': config['description'],
|
|
170
|
+
'use_for': _get_use_cases_for_preset(preset)
|
|
171
|
+
}
|
|
172
|
+
for preset, config in SmoothingConfig.PRESETS.items()
|
|
173
|
+
},
|
|
174
|
+
'parameters': {
|
|
175
|
+
'alpha': {
|
|
176
|
+
'range': '0.0 - 1.0',
|
|
177
|
+
'low': 'Very smooth, slow response (0.05-0.15)',
|
|
178
|
+
'medium': 'Balanced (0.2-0.4)',
|
|
179
|
+
'high': 'Fast response, less smooth (0.5-0.8)'
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
'tips': [
|
|
183
|
+
'Start with MEDIUM preset (alpha=0.3)',
|
|
184
|
+
'Lower alpha for more smoothing but slower tracking',
|
|
185
|
+
'Higher alpha for faster response but more jitter',
|
|
186
|
+
'Use ULTRA for drone/handheld footage',
|
|
187
|
+
'Use LIGHT for fast-moving objects (Dakar, sports)',
|
|
188
|
+
'Use HEAVY for surveillance/security cameras'
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _get_use_cases_for_preset(preset: SmoothingPreset) -> list:
|
|
194
|
+
"""Get use cases for a preset."""
|
|
195
|
+
mapping = {
|
|
196
|
+
SmoothingPreset.NONE: ['Debugging', 'Raw data analysis'],
|
|
197
|
+
SmoothingPreset.LIGHT: ['Fast vehicles', 'Sports', 'Running', 'Highway', 'Dakar racing'],
|
|
198
|
+
SmoothingPreset.MEDIUM: ['City traffic', 'Pedestrians', 'Warehouse', 'General use'],
|
|
199
|
+
SmoothingPreset.HEAVY: ['Surveillance', 'Parking lots', 'Crowds', 'Security'],
|
|
200
|
+
SmoothingPreset.ULTRA: ['Drones', 'Handheld cameras', 'Cinematic footage']
|
|
201
|
+
}
|
|
202
|
+
return mapping.get(preset, [])
|