from __future__ import annotations import os import time from pathlib import Path os.environ.setdefault("GLOG_minloglevel", "3") import numpy as np from hand import Hand class MediaPipeTracker: """MediaPipe hand landmarker.""" def __init__( self, model_path: str | Path, conf: float = 0.4, max_hands: int = 2, video_mode: bool = True, **_ignored, ) -> None: import mediapipe as mp from mediapipe.tasks.python import BaseOptions, vision model_path = Path(model_path) if not model_path.exists(): raise FileNotFoundError(f"Model not found: {model_path}") self._mp = mp self.device = "cpu" self.max_hands = max_hands self.video_mode = video_mode mode = vision.RunningMode.VIDEO if video_mode else vision.RunningMode.IMAGE options = vision.HandLandmarkerOptions( base_options=BaseOptions(model_asset_path=str(model_path)), running_mode=mode, num_hands=max_hands, min_hand_detection_confidence=conf, min_hand_presence_confidence=conf, min_tracking_confidence=0.5, ) self.detector = vision.HandLandmarker.create_from_options(options) self._t0 = time.perf_counter() self._last_ts = -1 def __call__(self, frame: np.ndarray) -> list[Hand]: """Detect hands in frame.""" h, w = frame.shape[:2] rgb = frame[:, :, ::-1].copy() image = self._mp.Image(image_format=self._mp.ImageFormat.SRGB, data=rgb) if self.video_mode: ts = max(int((time.perf_counter() - self._t0) * 1000), self._last_ts + 1) self._last_ts = ts result = self.detector.detect_for_video(image, ts) else: result = self.detector.detect(image) hands: list[Hand] = [] for i, lms in enumerate(result.hand_landmarks): kp = np.array([[lm.x * w, lm.y * h] for lm in lms], dtype=np.float64) conf = 1.0 if result.handedness and i < len(result.handedness): conf = float(result.handedness[i][0].score) box = np.array([kp[:, 0].min(), kp[:, 1].min(), kp[:, 0].max(), kp[:, 1].max()], dtype=np.float64) hands.append(Hand(keypoints=kp, scores=np.ones(21), box=box, conf=conf)) hands.sort(key=lambda x: x.conf, reverse=True) return hands[: self.max_hands] def close(self) -> None: """Release detector.""" self.detector.close()