PierreRouanet commited on
Commit
07d7978
·
1 Parent(s): fa2ca8f

Push hand tracking app.

Browse files
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
 
6
  [project]
7
- name = "reachy_mini_app_example"
8
  version = "0.1.0"
9
  description = "Add your description here"
10
  readme = "README.md"
@@ -12,7 +12,8 @@ requires-python = ">=3.8"
12
  # dependencies = ["reachy-mini"]
13
  dependencies = [
14
  "reachy-mini@git+https://github.com/pollen-robotics/reachy_mini",
15
- ] # TODO open
 
16
 
17
  [project.entry-points."reachy_mini_apps"]
18
- reachy_mini_app_example = "reachy_mini_app_example.main:ExampleApp"
 
4
 
5
 
6
  [project]
7
+ name = "reachy_mini_hand_tracker_app"
8
  version = "0.1.0"
9
  description = "Add your description here"
10
  readme = "README.md"
 
12
  # dependencies = ["reachy-mini"]
13
  dependencies = [
14
  "reachy-mini@git+https://github.com/pollen-robotics/reachy_mini",
15
+ "mediapipe",
16
+ ]
17
 
18
  [project.entry-points."reachy_mini_apps"]
19
+ reachy_mini_hand_tracker = "reachy_mini_hand_tracker.main:HandTrackerApp"
reachy_mini_app_example/main.py DELETED
@@ -1,27 +0,0 @@
1
- import threading
2
- import time
3
-
4
- import numpy as np
5
- from reachy_mini import ReachyMiniApp
6
- from reachy_mini.reachy_mini import ReachyMini
7
- from scipy.spatial.transform import Rotation as R
8
-
9
-
10
- class ExampleApp(ReachyMiniApp):
11
- def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
12
- t0 = time.time()
13
- while not stop_event.is_set():
14
- t = time.time() - t0
15
-
16
- target = np.deg2rad(30) * np.sin(2 * np.pi * 0.5 * t)
17
-
18
- yaw = target
19
- head = np.eye(4)
20
- head[:3, :3] = R.from_euler("xyz", [0, 0, yaw], degrees=False).as_matrix()
21
-
22
- reachy_mini.set_position(head=head, antennas=np.array([target, -target]))
23
-
24
- time.sleep(0.01)
25
- # if more than one second since last ping, print ping
26
- if t % 1 < 0.01:
27
- print("Ping")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
{reachy_mini_app_example → reachy_mini_hand_tracker_app}/__init__.py RENAMED
File without changes
reachy_mini_hand_tracker_app/hand_tracker.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hand Tracker using MediaPipe to detect hand positions in images."""
2
+
3
+ import cv2
4
+ import mediapipe as mp
5
+ import numpy as np
6
+
7
+ mp_drawing = mp.solutions.drawing_utils
8
+ mp_drawing_styles = mp.solutions.drawing_styles
9
+ mp_hands = mp.solutions.hands
10
+
11
+
12
+ class HandTracker:
13
+ """Hand Tracker using MediaPipe Hands to detect hand positions."""
14
+
15
+ def __init__(self, nb_hands=1):
16
+ """Initialize the Hand Tracker."""
17
+ self.hands = mp_hands.Hands(
18
+ static_image_mode=True, max_num_hands=nb_hands, min_detection_confidence=0.5
19
+ )
20
+
21
+ def get_hands_positions(self, img):
22
+ """Get the positions of the hands in the image."""
23
+ img = cv2.flip(img, 1)
24
+
25
+ results = self.hands.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
26
+ if results.multi_hand_landmarks is not None:
27
+ palm_centers = []
28
+ for landmarks in results.multi_hand_landmarks:
29
+ middle_finger_pip_landmark = landmarks.landmark[
30
+ mp_hands.HandLandmark.MIDDLE_FINGER_PIP
31
+ ]
32
+ palm_center = np.array(
33
+ [middle_finger_pip_landmark.x, middle_finger_pip_landmark.y]
34
+ )
35
+
36
+ # Normalize the palm center to the range [-1, 1]
37
+ # Flip the x-axis
38
+ palm_center = [-(palm_center[0] - 0.5) * 2, (palm_center[1] - 0.5) * 2]
39
+ palm_centers.append(palm_center)
40
+
41
+ return palm_centers
42
+ return None
reachy_mini_hand_tracker_app/main.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Reachy Mini Hand Tracker App
2
+
3
+ This app makes Reachy Mini follow your hand.
4
+
5
+ It uses the robot camera to detect the hand position and adjusts the head pose accordingly.
6
+ It uses mediapipe to track the hand and OpenCV for image processing.
7
+ """
8
+
9
+ import threading
10
+
11
+ import cv2
12
+ import numpy as np
13
+ from reachy_mini import ReachyMini, ReachyMiniApp
14
+ from reachy_mini.io.cam_utils import find_camera
15
+ from scipy.spatial.transform import Rotation as R
16
+
17
+ from reachy_mini_hand_tracker_app.hand_tracker import HandTracker
18
+
19
+
20
+ class HandTrackerApp(ReachyMiniApp):
21
+ # Proportional gain for the controller
22
+ # Reduce/Increase to make the head movement smoother or more responsive)
23
+ kp = 0.2
24
+
25
+ # Maximum delta for the head position adjustments
26
+ # This limits how much the head can move in one iteration to prevent abrupt movements
27
+ max_delta = 0.3
28
+
29
+ # Proportional gains for the head height adjustment
30
+ # Adjust this value to control how much the head moves up/down based on vertical error
31
+ kz = 0.04
32
+
33
+ def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
34
+ cap = find_camera()
35
+ if cap is None:
36
+ raise RuntimeError("No camera found. Please connect a camera.")
37
+
38
+ hand_tracker = HandTracker()
39
+
40
+ head_pose = np.eye(4)
41
+ euler_rot = np.array([0.0, 0.0, 0.0])
42
+
43
+ while not stop_event.is_set():
44
+ success, img = cap.read()
45
+
46
+ if not success:
47
+ print("Failed to capture image from camera.")
48
+ continue
49
+
50
+ hands = hand_tracker.get_hands_positions(img)
51
+ if hands:
52
+ hand = hands[0] # Assuming we only track the first detected hand
53
+ draw_hand(img, hand)
54
+
55
+ error = np.array([0, 0]) - hand
56
+ error = np.clip(
57
+ error, -self.max_delta, self.max_delta
58
+ ) # Limit error to avoid extreme movements
59
+ euler_rot += np.array(
60
+ [0.0, -self.kp * 0.1 * error[1], self.kp * error[0]]
61
+ )
62
+
63
+ head_pose[:3, :3] = R.from_euler(
64
+ "xyz", euler_rot, degrees=False
65
+ ).as_matrix()
66
+ head_pose[:3, 3][2] = (
67
+ error[1] * self.kz
68
+ ) # Adjust height based on vertical error
69
+
70
+ reachy_mini.set_target(head=head_pose)
71
+
72
+ cv2.imshow("Reachy Mini Hand Tracker App", img)
73
+ if cv2.waitKey(1) & 0xFF == ord("q"):
74
+ break
75
+
76
+
77
+ def draw_hand(img, hand):
78
+ """Draw debug information on the image."""
79
+ h, w, _ = img.shape
80
+ draw_palm = [(-hand[0] + 1) / 2, (hand[1] + 1) / 2] # [0, 1]
81
+ cv2.circle(
82
+ img,
83
+ (int(w - draw_palm[0] * w), int(draw_palm[1] * h)),
84
+ radius=5,
85
+ color=(0, 0, 255),
86
+ thickness=-1,
87
+ )
88
+
89
+ _target = [0.5, 0.5]
90
+ cv2.circle(
91
+ img,
92
+ (int(_target[0] * w), int(_target[1] * h)),
93
+ radius=5,
94
+ color=(255, 0, 0),
95
+ thickness=-1,
96
+ )
97
+
98
+ cv2.line(
99
+ img,
100
+ (int(draw_palm[0] * w), int(draw_palm[1] * h)),
101
+ (int(_target[0] * w), int(_target[1] * h)),
102
+ color=(0, 255, 0),
103
+ thickness=2,
104
+ )
105
+
106
+
107
+ if __name__ == "__main__":
108
+ # You can run the app directly from this script
109
+ with ReachyMini() as mini:
110
+ app = HandTrackerApp()
111
+
112
+ stop = threading.Event()
113
+
114
+ try:
115
+ print("Running '{{ app_name }}' a ReachyMiniApp...")
116
+ print("Press Ctrl+C to stop the app.")
117
+ app.run(mini, stop)
118
+ print("App has stopped.")
119
+
120
+ except KeyboardInterrupt:
121
+ print("Stopping the app...")
122
+ stop.set()