forked from phospho-app/phosphobot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhand_tracking.py
More file actions
162 lines (132 loc) · 5.39 KB
/
Copy pathhand_tracking.py
File metadata and controls
162 lines (132 loc) · 5.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import cv2
import requests # type: ignore
import numpy as np
import mediapipe as mp # type: ignore
# TODO: estimate depth using the stero camera
class HandTracker:
def __init__(self):
# MediaPipe hand tracking setup
self.mp_hands = mp.solutions.hands
self.mp_drawing = mp.solutions.drawing_utils
self.hands = self.mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.7,
min_tracking_confidence=0.7,
)
self.left_hand_z = 0
def calculate_hand_open_state(self, hand_landmarks):
"""Calculate hand open state based on thumb and finger tip distances."""
thumb_tip = hand_landmarks.landmark[self.mp_hands.HandLandmark.THUMB_TIP]
index_tip = hand_landmarks.landmark[self.mp_hands.HandLandmark.INDEX_FINGER_TIP]
openess = 1 - max(
0,
min(
1,
1
- np.sqrt(
(thumb_tip.x - index_tip.x) ** 2
+ (thumb_tip.y - index_tip.y) ** 2
+ (thumb_tip.z - index_tip.z) ** 2
)
* 3,
),
)
return 0 if openess < 0.25 else openess
def add_hand_data_overlay(self, image, x, y, z, open):
"""Add overlay with hand tracking data."""
# Prepare text
overlay_text = f"X: {x:.3f} " f"Y: {y:.3f} " f"Z: {z:.3f} " f"Open: {open:.2f}"
# Add background rectangle
cv2.rectangle(image, (10, 30), (550, 70), (255, 255, 255), -1)
# Add text
cv2.putText(
image, overlay_text, (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2
)
return image
def track_hand(self):
"""Open video capture and continuously track hand position."""
cap = cv2.VideoCapture(0)
while cap.isOpened():
success, image = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue
# Flip the image horizontally
image = cv2.flip(image, 1)
# Convert the BGR image to RGB
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Process the image and find hands
results = self.hands.process(image_rgb)
# Reset left hand z
self.left_hand_z = 0
# Draw hand landmarks and send position
if results.multi_hand_landmarks:
hand_data = {"rx": 0, "ry": 0, "rz": 0}
for hand_index, hand_landmarks in enumerate(
results.multi_hand_landmarks
):
# Draw landmarks on the image
self.mp_drawing.draw_landmarks(
image, hand_landmarks, self.mp_hands.HAND_CONNECTIONS
)
# Extract hand position (using wrist as reference)
wrist = hand_landmarks.landmark[self.mp_hands.HandLandmark.WRIST]
# Identify hand side (uses handedness if available)
if results.multi_handedness:
handedness = results.multi_handedness[hand_index]
if handedness.classification[0].label == "Left":
hand_data["x"] = -wrist.y + 0.65
else:
hand_data["y"] = 0 - wrist.x + 0.525
hand_data["z"] = 0 - wrist.y + 0.65
hand_data["open"] = self.calculate_hand_open_state(
hand_landmarks
)
# Send data to endpoint
if (
"x" in hand_data
and "y" in hand_data
and "z" in hand_data
and "open" in hand_data
):
try:
self.add_hand_data_overlay(
image,
hand_data["x"],
hand_data["y"],
hand_data["z"],
hand_data["open"],
)
requests.post(
"http://localhost:80/move/absolute",
json={
**hand_data,
"x": hand_data["x"] * 100,
"y": hand_data["y"] * 100,
"z": hand_data["z"] * 100,
},
timeout=0.2, # Short timeout to prevent blocking
)
except requests.RequestException as e:
print(f"Failed to send data: {e}")
else:
print("Missing hand data")
# Display the image
cv2.imshow("Hand Tracking", image)
# Break loop on 'q' key press
if cv2.waitKey(5) & 0xFF == ord("q"):
break
# Clean up
cap.release()
cv2.destroyAllWindows()
def main():
# Initialize hand tracker
try:
requests.post("http://localhost:80/move/init")
except requests.RequestException as e:
print(f"Failed to connect to server: {e}")
tracker = HandTracker()
tracker.track_hand()
if __name__ == "__main__":
main()