File size: 3,327 Bytes
e47efb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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):
    # Landmark shortcuts for finger tips and lower joints.
    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)

    # Most Gradio webcam frames are RGB. Some environments may provide BGR.
    # Try RGB first, then fallback to BGR->RGB conversion.
    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,
    }