| import cv2 |
| import mediapipe as mp |
| import numpy as np |
|
|
|
|
| _MP_HANDS = getattr(getattr(mp, "solutions", None), "hands", None) |
| _HANDS = None |
| if _MP_HANDS is not None: |
| _HANDS = _MP_HANDS.Hands( |
| static_image_mode=False, |
| max_num_hands=1, |
| min_detection_confidence=0.5, |
| min_tracking_confidence=0.5, |
| ) |
|
|
|
|
| def _empty_result(frame): |
| return { |
| "hand_found": False, |
| "hand_center_x": None, |
| "hand_center_y": None, |
| "fingers_extended": { |
| "thumb": False, |
| "index": False, |
| "middle": False, |
| "ring": False, |
| "pinky": False, |
| }, |
| "landmarks": None, |
| "frame": frame, |
| } |
|
|
|
|
| def _finger_status(landmarks, handedness_label): |
| |
| tips = { |
| "thumb": _MP_HANDS.HandLandmark.THUMB_TIP, |
| "index": _MP_HANDS.HandLandmark.INDEX_FINGER_TIP, |
| "middle": _MP_HANDS.HandLandmark.MIDDLE_FINGER_TIP, |
| "ring": _MP_HANDS.HandLandmark.RING_FINGER_TIP, |
| "pinky": _MP_HANDS.HandLandmark.PINKY_TIP, |
| } |
| pips = { |
| "thumb": _MP_HANDS.HandLandmark.THUMB_IP, |
| "index": _MP_HANDS.HandLandmark.INDEX_FINGER_PIP, |
| "middle": _MP_HANDS.HandLandmark.MIDDLE_FINGER_PIP, |
| "ring": _MP_HANDS.HandLandmark.RING_FINGER_PIP, |
| "pinky": _MP_HANDS.HandLandmark.PINKY_PIP, |
| } |
|
|
| status = {} |
| for finger in ("index", "middle", "ring", "pinky"): |
| tip_y = landmarks[tips[finger]].y |
| pip_y = landmarks[pips[finger]].y |
| status[finger] = tip_y < pip_y |
|
|
| thumb_tip_x = landmarks[tips["thumb"]].x |
| thumb_ip_x = landmarks[pips["thumb"]].x |
| if handedness_label == "Right": |
| status["thumb"] = thumb_tip_x < thumb_ip_x |
| else: |
| status["thumb"] = thumb_tip_x > thumb_ip_x |
|
|
| return status |
|
|
|
|
| def detect_hand(frame): |
| """ |
| Detect one hand from a webcam frame and return basic rule-friendly data. |
| """ |
| if _HANDS is None: |
| return _empty_result(frame) |
|
|
| if frame is None: |
| return _empty_result(frame) |
|
|
| if not isinstance(frame, np.ndarray) or frame.size == 0: |
| return _empty_result(frame) |
|
|
| if frame.dtype != np.uint8: |
| frame = np.clip(frame, 0, 255).astype(np.uint8) |
|
|
| |
| |
| results = _HANDS.process(frame) |
| if not results.multi_hand_landmarks and frame.ndim == 3 and frame.shape[2] == 3: |
| rgb_fallback = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) |
| results = _HANDS.process(rgb_fallback) |
|
|
| if not results.multi_hand_landmarks: |
| return _empty_result(frame) |
|
|
| hand_landmarks = results.multi_hand_landmarks[0] |
| handedness_label = "Right" |
| if results.multi_handedness: |
| handedness_label = results.multi_handedness[0].classification[0].label |
|
|
| lm = hand_landmarks.landmark |
| center_x = sum(point.x for point in lm) / len(lm) |
| center_y = sum(point.y for point in lm) / len(lm) |
| fingers = _finger_status(lm, handedness_label) |
|
|
| return { |
| "hand_found": True, |
| "hand_center_x": center_x, |
| "hand_center_y": center_y, |
| "fingers_extended": fingers, |
| "landmarks": hand_landmarks, |
| "frame": frame, |
| } |
|
|