This commit is contained in:
DuOtto
2025-11-04 21:40:06 +01:00
parent 1d63671fad
commit 225ecc2e17
16 changed files with 244 additions and 109 deletions
+106 -2
View File
@@ -3,12 +3,18 @@ import cv2, numpy as np, time, fitz
from helper import compute_distance
from config import PINCH_THRESHOLD, RELEASE_THRESHOLD
# hand_state.py
from dataclasses import dataclass, field
from typing import Tuple, Dict, Optional
from helper import finger_straight
# ----- Initialize Hand Detector -----
mp_hands = mp.solutions.hands
mp_draw = mp.solutions.drawing_utils
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
max_num_hands=8,
min_detection_confidence=0.7,
min_tracking_confidence=0.5
)
@@ -16,10 +22,106 @@ hands = mp_hands.Hands(
start_pt = None # measurement start point
measuring = False # measurement in progress
@dataclass
class HandState:
id: int # a persistent identifier for this hand
landmarks: Dict[str, Tuple[int,int]] = field(default_factory=dict)
finger_straightness: Dict[str, float] = field(default_factory=dict)
gesture: Optional[str] = None # e.g. "pinch", "fist", "open"
gesture_persistence: int = 0 # how many frames the current gesture has held
#handList = Dict[int, HandState] = {}
next_id = 0
def update(mp_results, frame_vis):
h, w, _ = frame_vis.shape
detected_centroids = []
landmarks_list = []
# 1) pull out centroids & raw landmarks
if mp_results.multi_hand_landmarks:
for hand in mp_results.multi_hand_landmarks:
pts = []
for lm in hand.landmark:
pts.append((int(lm.x*w), int(lm.y*h)))
centroid = np.mean(pts, axis=0)
detected_centroids.append(tuple(centroid.astype(int)))
landmarks_list.append((hand, pts))
# 2) match to existing by nearest centroid
new_hands = {}
used_ids = set()
for (hand, pts), centroid in zip(landmarks_list, detected_centroids):
# find best existing hand
best_id, best_dist = None, 1e9
for hid, state in handList.items():
dx, dy = np.array(state.landmarks['centroid']) - centroid
d = np.hypot(dx, dy)
if d < best_dist and d < 100: # 100px max match distance
best_dist, best_id = d, hid
if best_id is None:
hid = next_id
next_id += 1
state = HandState(id=hid)
else:
hid = best_id
state = handList[hid]
used_ids.add(hid)
# 3) update state
state.persistence += 1
state.landmarks['centroid'] = centroid
# compute fingertip positions
idx_tip = pts[mp.solutions.hands.HandLandmark.INDEX_FINGER_TIP]
mid_tip = pts[mp.solutions.hands.HandLandmark.MIDDLE_FINGER_TIP]
state.landmarks['index_tip'] = idx_tip
state.landmarks['middle_tip'] = mid_tip
for name, tip_i, pip_i, mcp_i in [
('index', mp.solutions.hands.HandLandmark.INDEX_FINGER_TIP,
mp.solutions.hands.HandLandmark.INDEX_FINGER_PIP,
mp.solutions.hands.HandLandmark.INDEX_FINGER_MCP),
('middle', mp.solutions.hands.HandLandmark.MIDDLE_FINGER_TIP,
mp.solutions.hands.HandLandmark.MIDDLE_FINGER_PIP,
mp.solutions.hands.HandLandmark.MIDDLE_FINGER_MCP),
# add ring, pinky, thumb similarly...
]:
p_tip = pts[tip_i]
p_pip = pts[pip_i]
p_mcp = pts[mcp_i]
state.finger_straightness[name] = finger_straight(p_tip, p_pip, p_mcp)
# 5) simple gesture detection
if state.finger_straightness['index'] < 0.2 and \
state.finger_straightness['middle'] < 0.2:
gesture = 'fist'
elif state.finger_straightness['index'] > 0.8 and \
state.finger_straightness['middle'] > 0.8:
gesture = 'open'
else:
gesture = None
if gesture == state.gesture:
state.gesture_persistence += 1
else:
state.gesture = gesture
state.gesture_persistence = 0
new_hands[hid] = state
# 6) drop hands not seen this frame
handList = new_hands
return list(hands.values())
def detect_hands(frame):
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
rgb.flags.writeable = False
results = hands.process(rgb)
results = hands.process(rgb)
return results
@@ -47,6 +149,8 @@ def check_hand(hand, frame_vis, object_calibrating):
return fingertip_idx, measuring, start_pt
def is_measuring(frame_vis, hand, object_calibrating):
global measuring, start_pt
h, w, _ = frame_vis.shape