hand-tracking-teleop 0.1.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.
Potentially problematic release.
This version of hand-tracking-teleop might be problematic. Click here for more details.
- hand_tracking/__init__.py +17 -0
- hand_tracking/cli.py +279 -0
- hand_tracking/hand_detector.py +475 -0
- hand_tracking/visualizer_3d.py +214 -0
- hand_tracking/visualizer_combined.py +221 -0
- hand_tracking/visualizer_rerun.py +255 -0
- hand_tracking_teleop-0.1.0.dist-info/METADATA +436 -0
- hand_tracking_teleop-0.1.0.dist-info/RECORD +14 -0
- hand_tracking_teleop-0.1.0.dist-info/WHEEL +5 -0
- hand_tracking_teleop-0.1.0.dist-info/entry_points.txt +6 -0
- hand_tracking_teleop-0.1.0.dist-info/top_level.txt +3 -0
- mapping/__init__.py +12 -0
- mapping/kinematic_mapper.py +405 -0
- robot_interface/dual_interface.py +780 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Hand tracking package for robot teleoperation.
|
|
2
|
+
|
|
3
|
+
This package provides real-time hand tracking using MediaPipe with
|
|
4
|
+
scale-invariant 3D coordinate normalization for robust robot control.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__author__ = "Bruk Gebregziabher"
|
|
9
|
+
__email__ = "bruk@signalbotics.com"
|
|
10
|
+
|
|
11
|
+
from .hand_detector import HandDetector, HandLandmarks
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"HandDetector",
|
|
15
|
+
"HandLandmarks",
|
|
16
|
+
"__version__",
|
|
17
|
+
]
|
hand_tracking/cli.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Command-line interface for hand tracking package."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from .hand_detector import HandDetector
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
"""Main CLI entry point for 2D hand tracking."""
|
|
12
|
+
parser = argparse.ArgumentParser(
|
|
13
|
+
description="Hand tracking for robot teleoperation"
|
|
14
|
+
)
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"--config",
|
|
17
|
+
type=str,
|
|
18
|
+
help="Path to configuration YAML file",
|
|
19
|
+
default=None,
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"--camera",
|
|
23
|
+
type=int,
|
|
24
|
+
help="Camera device ID",
|
|
25
|
+
default=0,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
args = parser.parse_args()
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
detector = HandDetector(config_path=args.config)
|
|
32
|
+
print("Starting hand tracking (2D mode)...")
|
|
33
|
+
print("Press 'q' to quit")
|
|
34
|
+
detector.run_interactive(mode='2d')
|
|
35
|
+
except KeyboardInterrupt:
|
|
36
|
+
print("\nShutting down...")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"Error: {e}")
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main_3d():
|
|
43
|
+
"""CLI entry point for 3D hand tracking visualization."""
|
|
44
|
+
parser = argparse.ArgumentParser(
|
|
45
|
+
description="3D hand tracking visualization"
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--config",
|
|
49
|
+
type=str,
|
|
50
|
+
help="Path to configuration YAML file",
|
|
51
|
+
default=None,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
args = parser.parse_args()
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
detector = HandDetector(config_path=args.config)
|
|
58
|
+
print("Starting 3D hand tracking visualization...")
|
|
59
|
+
print("Controls: Rotate (drag), Zoom (scroll), Close window to exit")
|
|
60
|
+
detector.run_interactive(mode='3d')
|
|
61
|
+
except KeyboardInterrupt:
|
|
62
|
+
print("\nShutting down...")
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"Error: {e}")
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main_combined():
|
|
69
|
+
"""CLI entry point for combined 2D + 3D visualization."""
|
|
70
|
+
parser = argparse.ArgumentParser(
|
|
71
|
+
description="Combined 2D + 3D hand tracking visualization"
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--config",
|
|
75
|
+
type=str,
|
|
76
|
+
help="Path to configuration YAML file",
|
|
77
|
+
default=None,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
args = parser.parse_args()
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
detector = HandDetector(config_path=args.config)
|
|
84
|
+
print("Starting combined 2D + 3D visualization...")
|
|
85
|
+
print("Controls: Rotate 3D (drag), Zoom (scroll), Close window to exit")
|
|
86
|
+
detector.run_interactive(mode='combined')
|
|
87
|
+
except KeyboardInterrupt:
|
|
88
|
+
print("\nShutting down...")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"Error: {e}")
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def main_rerun():
|
|
95
|
+
"""CLI entry point for Rerun visualization."""
|
|
96
|
+
parser = argparse.ArgumentParser(
|
|
97
|
+
description="Rerun hand tracking visualization"
|
|
98
|
+
)
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
"--config",
|
|
101
|
+
type=str,
|
|
102
|
+
help="Path to configuration YAML file",
|
|
103
|
+
default=None,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
args = parser.parse_args()
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
detector = HandDetector(config_path=args.config)
|
|
110
|
+
print("Starting Rerun visualization...")
|
|
111
|
+
print("The Rerun viewer will open automatically.")
|
|
112
|
+
print("Press Ctrl+C to stop.")
|
|
113
|
+
detector.run_interactive(mode='rerun')
|
|
114
|
+
except KeyboardInterrupt:
|
|
115
|
+
print("\nShutting down...")
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"Error: {e}")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def calibrate():
|
|
122
|
+
"""CLI entry point for hand calibration."""
|
|
123
|
+
parser = argparse.ArgumentParser(
|
|
124
|
+
description="Calibrate hand tracking system"
|
|
125
|
+
)
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
"--output",
|
|
128
|
+
type=str,
|
|
129
|
+
help="Output configuration file path",
|
|
130
|
+
default="config/calibrated_config.yaml",
|
|
131
|
+
)
|
|
132
|
+
parser.add_argument(
|
|
133
|
+
"--distance",
|
|
134
|
+
type=float,
|
|
135
|
+
help="Reference distance from camera in cm (default: 50cm)",
|
|
136
|
+
default=50.0,
|
|
137
|
+
)
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
"--samples",
|
|
140
|
+
type=int,
|
|
141
|
+
help="Number of samples to collect (default: 30)",
|
|
142
|
+
default=30,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
args = parser.parse_args()
|
|
146
|
+
|
|
147
|
+
print("Hand Tracking Calibration")
|
|
148
|
+
print("=" * 50)
|
|
149
|
+
print(f"\nCalibration parameters:")
|
|
150
|
+
print(f" Reference distance: {args.distance} cm")
|
|
151
|
+
print(f" Samples to collect: {args.samples}")
|
|
152
|
+
print(f" Output file: {args.output}")
|
|
153
|
+
print("\nInstructions:")
|
|
154
|
+
print(f"1. Measure and mark a distance of {args.distance} cm from your camera")
|
|
155
|
+
print("2. Place your hand at this marked distance")
|
|
156
|
+
print("3. Hold your hand steady with fingers spread")
|
|
157
|
+
print("4. Keep hand visible in camera frame")
|
|
158
|
+
print("\nPress Enter to start calibration, or Ctrl+C to cancel...")
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
input()
|
|
162
|
+
|
|
163
|
+
import cv2
|
|
164
|
+
import yaml
|
|
165
|
+
import numpy as np
|
|
166
|
+
|
|
167
|
+
detector = HandDetector()
|
|
168
|
+
print("\nš„ Starting camera...")
|
|
169
|
+
print("Position your hand at the marked distance...")
|
|
170
|
+
|
|
171
|
+
palm_widths = []
|
|
172
|
+
sample_count = 0
|
|
173
|
+
|
|
174
|
+
while sample_count < args.samples:
|
|
175
|
+
frame = detector.get_frame()
|
|
176
|
+
if frame is None:
|
|
177
|
+
print("ā Failed to get frame from camera")
|
|
178
|
+
sys.exit(1)
|
|
179
|
+
|
|
180
|
+
hands = detector.detect_hands(frame)
|
|
181
|
+
|
|
182
|
+
# Draw frame
|
|
183
|
+
frame_display = detector.draw_landmarks(frame.copy(), hands)
|
|
184
|
+
|
|
185
|
+
# Add calibration info
|
|
186
|
+
cv2.putText(frame_display, f"Calibration: {sample_count}/{args.samples}",
|
|
187
|
+
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
|
188
|
+
cv2.putText(frame_display, f"Distance: {args.distance} cm",
|
|
189
|
+
(10, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
|
190
|
+
|
|
191
|
+
if len(hands) > 0:
|
|
192
|
+
hand = hands[0] # Use first detected hand
|
|
193
|
+
palm_width = hand.get_palm_width()
|
|
194
|
+
palm_widths.append(palm_width)
|
|
195
|
+
sample_count += 1
|
|
196
|
+
|
|
197
|
+
cv2.putText(frame_display, f"Palm width: {palm_width:.1f}px",
|
|
198
|
+
(10, 110), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
|
199
|
+
cv2.putText(frame_display, "ā Sample captured",
|
|
200
|
+
(10, 150), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
|
201
|
+
else:
|
|
202
|
+
cv2.putText(frame_display, "No hand detected - please show hand",
|
|
203
|
+
(10, 110), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
|
|
204
|
+
|
|
205
|
+
cv2.imshow("Calibration", frame_display)
|
|
206
|
+
|
|
207
|
+
if cv2.waitKey(100) & 0xFF == ord('q'):
|
|
208
|
+
print("\nā Calibration cancelled by user")
|
|
209
|
+
cv2.destroyAllWindows()
|
|
210
|
+
sys.exit(0)
|
|
211
|
+
|
|
212
|
+
cv2.destroyAllWindows()
|
|
213
|
+
|
|
214
|
+
# Calculate calibrated reference palm width
|
|
215
|
+
palm_widths = np.array(palm_widths)
|
|
216
|
+
mean_palm_width = np.mean(palm_widths)
|
|
217
|
+
std_palm_width = np.std(palm_widths)
|
|
218
|
+
|
|
219
|
+
print(f"\nā Calibration complete!")
|
|
220
|
+
print(f"\nResults:")
|
|
221
|
+
print(f" Mean palm width: {mean_palm_width:.2f} px")
|
|
222
|
+
print(f" Std deviation: {std_palm_width:.2f} px")
|
|
223
|
+
print(f" Min: {np.min(palm_widths):.2f} px")
|
|
224
|
+
print(f" Max: {np.max(palm_widths):.2f} px")
|
|
225
|
+
|
|
226
|
+
# Save calibration
|
|
227
|
+
output_path = Path(args.output)
|
|
228
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
229
|
+
|
|
230
|
+
calibration_data = {
|
|
231
|
+
'calibration': {
|
|
232
|
+
'reference_palm_width': float(mean_palm_width),
|
|
233
|
+
'reference_distance_cm': float(args.distance),
|
|
234
|
+
'std_deviation': float(std_palm_width),
|
|
235
|
+
'samples_collected': len(palm_widths),
|
|
236
|
+
},
|
|
237
|
+
'hand_tracking': {
|
|
238
|
+
'camera': {'device_id': 0, 'width': 1280, 'height': 720, 'fps': 30},
|
|
239
|
+
'mediapipe': {
|
|
240
|
+
'max_num_hands': 2,
|
|
241
|
+
'min_detection_confidence': 0.7,
|
|
242
|
+
'min_tracking_confidence': 0.5,
|
|
243
|
+
'model_complexity': 1
|
|
244
|
+
},
|
|
245
|
+
'landmarks': {'smoothing_factor': 0.8, 'confidence_threshold': 0.6},
|
|
246
|
+
'display': {
|
|
247
|
+
'show_landmarks': True,
|
|
248
|
+
'show_connections': True,
|
|
249
|
+
'show_bounding_box': True,
|
|
250
|
+
'window_name': 'Hand Tracking'
|
|
251
|
+
},
|
|
252
|
+
'normalization': {
|
|
253
|
+
'use_calibrated_reference': True,
|
|
254
|
+
'reference_palm_width': float(mean_palm_width),
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
with open(output_path, 'w') as f:
|
|
260
|
+
yaml.dump(calibration_data, f, default_flow_style=False)
|
|
261
|
+
|
|
262
|
+
print(f"\nā Calibration saved to: {output_path}")
|
|
263
|
+
print(f"\nTo use this calibration:")
|
|
264
|
+
print(f" detector = HandDetector(config_path='{output_path}')")
|
|
265
|
+
print(f" # Or use normalized landmarks with custom reference:")
|
|
266
|
+
print(f" normalized = hand.get_normalized_landmarks(reference_palm_width={mean_palm_width:.2f})")
|
|
267
|
+
|
|
268
|
+
except KeyboardInterrupt:
|
|
269
|
+
print("\n\nā Calibration cancelled")
|
|
270
|
+
sys.exit(0)
|
|
271
|
+
except Exception as e:
|
|
272
|
+
print(f"\nā Error during calibration: {e}")
|
|
273
|
+
import traceback
|
|
274
|
+
traceback.print_exc()
|
|
275
|
+
sys.exit(1)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
if __name__ == "__main__":
|
|
279
|
+
main()
|