lilblueyes commited on
Commit
8f01235
·
1 Parent(s): c6057fb

Draw MediaPipe landmarks in live debug

Browse files
signspeak/asl/mediapipe_utils.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import numpy as np
6
+
7
+
8
+ FACE_POINTS = 468
9
+ HAND_POINTS = 21
10
+ POSE_POINTS = 33
11
+
12
+
13
+ def extract_keypoints_from_holistic(results: Any, missing_value: float = np.nan) -> np.ndarray:
14
+ face = _landmark_array(getattr(results, "face_landmarks", None), FACE_POINTS, missing_value)
15
+ left = _landmark_array(getattr(results, "left_hand_landmarks", None), HAND_POINTS, missing_value)
16
+ pose = _landmark_array(getattr(results, "pose_landmarks", None), POSE_POINTS, missing_value)
17
+ right = _landmark_array(getattr(results, "right_hand_landmarks", None), HAND_POINTS, missing_value)
18
+ return np.vstack([face, left, pose, right]).astype(np.float32)
19
+
20
+
21
+ def draw_holistic_landmarks(mp: Any, image: np.ndarray, results: Any) -> None:
22
+ drawing = mp.solutions.drawing_utils
23
+ styles = mp.solutions.drawing_styles
24
+ holistic = mp.solutions.holistic
25
+
26
+ drawing.draw_landmarks(
27
+ image,
28
+ getattr(results, "face_landmarks", None),
29
+ holistic.FACEMESH_CONTOURS,
30
+ landmark_drawing_spec=None,
31
+ connection_drawing_spec=styles.get_default_face_mesh_contours_style(),
32
+ )
33
+ drawing.draw_landmarks(
34
+ image,
35
+ getattr(results, "pose_landmarks", None),
36
+ holistic.POSE_CONNECTIONS,
37
+ landmark_drawing_spec=styles.get_default_pose_landmarks_style(),
38
+ )
39
+ drawing.draw_landmarks(
40
+ image,
41
+ getattr(results, "left_hand_landmarks", None),
42
+ holistic.HAND_CONNECTIONS,
43
+ landmark_drawing_spec=styles.get_default_hand_landmarks_style(),
44
+ connection_drawing_spec=styles.get_default_hand_connections_style(),
45
+ )
46
+ drawing.draw_landmarks(
47
+ image,
48
+ getattr(results, "right_hand_landmarks", None),
49
+ holistic.HAND_CONNECTIONS,
50
+ landmark_drawing_spec=styles.get_default_hand_landmarks_style(),
51
+ connection_drawing_spec=styles.get_default_hand_connections_style(),
52
+ )
53
+
54
+
55
+ def _landmark_array(landmark_list: Any, expected_points: int, missing_value: float) -> np.ndarray:
56
+ empty = np.full((expected_points, 3), missing_value, dtype=np.float32)
57
+ if landmark_list is None or not getattr(landmark_list, "landmark", None):
58
+ return empty
59
+
60
+ points = landmark_list.landmark[:expected_points]
61
+ for idx, point in enumerate(points):
62
+ empty[idx] = [point.x, point.y, point.z]
63
+ return empty
64
+
signspeak/debug_video.py CHANGED
@@ -5,9 +5,12 @@ import time
5
  from pathlib import Path
6
  from typing import Any
7
 
 
 
8
 
9
  def create_debug_overlay_video(video_path: str | Path, result: dict[str, Any]) -> str:
10
  cv2 = _load_cv2()
 
11
  path = Path(video_path)
12
  cap = cv2.VideoCapture(str(path))
13
  if not cap.isOpened():
@@ -49,10 +52,16 @@ def create_debug_overlay_video(video_path: str | Path, result: dict[str, Any]) -
49
  ok, frame = cap.read()
50
  if not ok or frame is None:
51
  break
 
 
 
 
52
  _draw_overlay(cv2, frame, gloss_text, emotion_text, status_text)
53
  writer.write(frame)
54
  finally:
55
  cap.release()
 
 
56
  writer.release()
57
 
58
  return str(output_path)
@@ -94,3 +103,18 @@ def _load_cv2():
94
  return cv2
95
  except Exception as exc:
96
  raise RuntimeError("OpenCV is required for debug overlay video generation.") from exc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from pathlib import Path
6
  from typing import Any
7
 
8
+ from .asl.mediapipe_utils import draw_holistic_landmarks
9
+
10
 
11
  def create_debug_overlay_video(video_path: str | Path, result: dict[str, Any]) -> str:
12
  cv2 = _load_cv2()
13
+ mp, holistic = _load_holistic()
14
  path = Path(video_path)
15
  cap = cv2.VideoCapture(str(path))
16
  if not cap.isOpened():
 
52
  ok, frame = cap.read()
53
  if not ok or frame is None:
54
  break
55
+ if mp is not None and holistic is not None:
56
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
57
+ results = holistic.process(frame_rgb)
58
+ draw_holistic_landmarks(mp, frame, results)
59
  _draw_overlay(cv2, frame, gloss_text, emotion_text, status_text)
60
  writer.write(frame)
61
  finally:
62
  cap.release()
63
+ if holistic is not None:
64
+ holistic.close()
65
  writer.release()
66
 
67
  return str(output_path)
 
103
  return cv2
104
  except Exception as exc:
105
  raise RuntimeError("OpenCV is required for debug overlay video generation.") from exc
106
+
107
+
108
+ def _load_holistic():
109
+ try:
110
+ import mediapipe as mp
111
+
112
+ return (
113
+ mp,
114
+ mp.solutions.holistic.Holistic(
115
+ min_detection_confidence=0.5,
116
+ min_tracking_confidence=0.5,
117
+ ),
118
+ )
119
+ except Exception:
120
+ return None, None
signspeak/live_debug.py CHANGED
@@ -6,12 +6,11 @@ from typing import Any
6
  import numpy as np
7
 
8
  from .asl.asl_detector import ASLDetector
9
- from .asl.landmarks_detector import LandmarksDetector
10
 
11
 
12
  class LiveASLSession:
13
  def __init__(self) -> None:
14
- self.detector = LandmarksDetector(missing_value=np.nan)
15
  self.asl = ASLDetector()
16
  self.interpreter: Any | None = None
17
  self.frame_keypoints: list[np.ndarray] = []
@@ -19,16 +18,21 @@ class LiveASLSession:
19
  self.latest_emotion = "unknown"
20
  self.latest_status = "Waiting for 30 frames."
21
  self.frames_seen = 0
 
 
 
22
 
23
  def process_frame(self, frame_rgb: np.ndarray) -> tuple[np.ndarray, str]:
24
  output = frame_rgb.copy()
25
  self.frames_seen += 1
26
- if self.detector.mp is None:
27
- self.latest_status = self.detector.error or "MediaPipe unavailable."
28
  return self._draw(output), self.latest_status
29
 
30
  try:
31
- keypoints = self.detector._detect_frame(frame_rgb)
 
 
32
  self.frame_keypoints.append(keypoints)
33
  self.frame_keypoints = self.frame_keypoints[-30:]
34
  if self.frames_seen % 15 == 0:
@@ -40,6 +44,18 @@ class LiveASLSession:
40
 
41
  return self._draw(output), self.latest_status
42
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  def _predict_latest(self) -> None:
44
  if not self.asl.model_path.exists():
45
  self.latest_prediction = ""
 
6
  import numpy as np
7
 
8
  from .asl.asl_detector import ASLDetector
9
+ from .asl.mediapipe_utils import draw_holistic_landmarks, extract_keypoints_from_holistic
10
 
11
 
12
  class LiveASLSession:
13
  def __init__(self) -> None:
 
14
  self.asl = ASLDetector()
15
  self.interpreter: Any | None = None
16
  self.frame_keypoints: list[np.ndarray] = []
 
18
  self.latest_emotion = "unknown"
19
  self.latest_status = "Waiting for 30 frames."
20
  self.frames_seen = 0
21
+ self.mp: Any | None = None
22
+ self.holistic: Any | None = None
23
+ self._load_mediapipe()
24
 
25
  def process_frame(self, frame_rgb: np.ndarray) -> tuple[np.ndarray, str]:
26
  output = frame_rgb.copy()
27
  self.frames_seen += 1
28
+ if self.mp is None or self.holistic is None:
29
+ self.latest_status = "MediaPipe unavailable."
30
  return self._draw(output), self.latest_status
31
 
32
  try:
33
+ results = self.holistic.process(frame_rgb)
34
+ draw_holistic_landmarks(self.mp, output, results)
35
+ keypoints = extract_keypoints_from_holistic(results, missing_value=np.nan)
36
  self.frame_keypoints.append(keypoints)
37
  self.frame_keypoints = self.frame_keypoints[-30:]
38
  if self.frames_seen % 15 == 0:
 
44
 
45
  return self._draw(output), self.latest_status
46
 
47
+ def _load_mediapipe(self) -> None:
48
+ try:
49
+ import mediapipe as mp
50
+
51
+ self.mp = mp
52
+ self.holistic = mp.solutions.holistic.Holistic(
53
+ min_detection_confidence=0.5,
54
+ min_tracking_confidence=0.5,
55
+ )
56
+ except Exception as exc:
57
+ self.latest_status = f"MediaPipe unavailable: {type(exc).__name__}: {exc}"
58
+
59
  def _predict_latest(self) -> None:
60
  if not self.asl.model_path.exists():
61
  self.latest_prediction = ""