import numpy as np from ml_gestures.predict import MLGesturePredictor class SpecialGestureDetector: def __init__(self, mode='geometric', model_path=None, class_names=None): self.mode = mode if mode == 'ml': if model_path is None or class_names is None: raise ValueError("Для ML нужны model_path и class_names") self.ml_predictor = MLGesturePredictor(model_path, class_names) print("Использую статический ML классификатор") else: self.ml_predictor = None print("Использую геометрические отношения для детекции специальных жестов") self.debug = False # Включите для отладки def predict(self, landmarks): if self.mode == 'geometric': return self._geometric_predict(landmarks) else: return self.ml_predictor.predict(landmarks) def _geometric_predict(self, landmarks): # Индексы MediaPipe idx = { 'nose': 0, 'left_shoulder': 11, 'right_shoulder': 12, 'left_elbow': 13, 'right_elbow': 14, 'left_wrist': 15, 'right_wrist': 16, 'left_hip': 23, 'right_hip': 24, } # Повышенный порог уверенности для специальных жестов min_conf = 0.6 required = ['left_shoulder', 'right_shoulder', 'left_elbow', 'right_elbow', 'left_wrist', 'right_wrist', 'left_hip', 'right_hip'] for p in required: if landmarks[idx[p]][3] < min_conf: if self.debug: print(f"{p} low confidence") return 'none' # Координаты (x, y) l_sh = landmarks[idx['left_shoulder']][:2] r_sh = landmarks[idx['right_shoulder']][:2] l_el = landmarks[idx['left_elbow']][:2] r_el = landmarks[idx['right_elbow']][:2] l_wr = landmarks[idx['left_wrist']][:2] r_wr = landmarks[idx['right_wrist']][:2] l_hip = landmarks[idx['left_hip']][:2] r_hip = landmarks[idx['right_hip']][:2] hip_center = (l_hip + r_hip) / 2 shoulder_width = np.linalg.norm(r_sh - l_sh) if shoulder_width < 50 or shoulder_width > 300: if self.debug: print("shoulder_width out of range") return 'none' # ----- КРЕСТ (предплечья скрещены на груди) ----- # 1. Запястья перекрещены (левое правее правого) И на уровне груди (ниже плеч, выше бедер) wrists_crossed = l_wr[0] > r_wr[0] wrists_chest_level = (max(l_wr[1], r_wr[1]) > max(l_sh[1], r_sh[1]) and min(l_wr[1], r_wr[1]) < hip_center[1]) # 2. Локти тоже на уровне груди (примерно) elbows_chest_level = (max(l_el[1], r_el[1]) > max(l_sh[1], r_sh[1]) * 0.9 and min(l_el[1], r_el[1]) < hip_center[1] * 1.1) # 3. Руки согнуты (предплечья короче верхней части руки) left_bent = np.linalg.norm(l_wr - l_el) < np.linalg.norm(l_el - l_sh) * 0.9 right_bent = np.linalg.norm(r_wr - r_el) < np.linalg.norm(r_el - r_sh) * 0.9 arms_bent = left_bent and right_bent # 4. Запястья близко к центру тела (не сильно отведены, типично для креста на груди) body_center_x = (l_sh[0] + r_sh[0]) / 2 wrists_near_center = (abs(l_wr[0] - body_center_x) < shoulder_width * 0.6 and abs(r_wr[0] - body_center_x) < shoulder_width * 0.6) cross = (wrists_crossed and wrists_chest_level and elbows_chest_level and arms_bent and wrists_near_center) if self.debug and cross: print(f"CROSS: crossed={wrists_crossed}, chest_w={wrists_chest_level}, " f"chest_e={elbows_chest_level}, bent={arms_bent}, near_center={wrists_near_center}") if cross: return 'cross' # ----- ДОМИК (руки над головой) ----- head_y = landmarks[idx['nose']][1] - 50 arms_up = (l_wr[1] < head_y and r_wr[1] < head_y) if arms_up: dist = np.linalg.norm(l_wr - r_wr) rel_dist = dist / shoulder_width if rel_dist < 1.5: return 'dome' return 'none'