import numpy as np class SpecialGestureDetector: def __init__(self, mode='geometric', model_path=None, class_names=None): self.mode = mode if mode == 'ml': from ml_gestures.predict import MLGesturePredictor 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.5 required = ['left_shoulder', 'right_shoulder', 'left_elbow', 'right_elbow', 'left_wrist', 'right_wrist', 'nose'] 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] nose = np.array(landmarks[idx['nose']][:2]) shoulder_center_y = (l_sh[1] + r_sh[1]) / 2 hip_center_y = (l_hip[1] + r_hip[1]) / 2 torso_height = hip_center_y - shoulder_center_y shoulder_width = np.linalg.norm(r_sh - l_sh) if shoulder_width < 30 or torso_height < 10: return 'none' # ---- Вспомогательные функции ---- def angle_between_vectors(v1, v2): """Угол между двумя векторами в градусах (0..180)""" cos_a = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2) + 1e-6) return np.arccos(np.clip(cos_a, -1.0, 1.0)) * 180 / np.pi def elbow_angle(shoulder, elbow, wrist): """Угол в локте (плечо-локоть-запястье)""" v1 = shoulder - elbow v2 = wrist - elbow return angle_between_vectors(v1, v2) def segments_intersect(p1, p2, p3, p4): def cross(o, a, b): return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o [1])* (b[0] - o[0]) d1 = cross(p3, p4, p1) d2 = cross(p3, p4, p2) d3 = cross(p1, p2, p3) d4 = cross(p1, p2, p4) return (d1 * d2 < 0) and (d3 * d4 < 0) def line_intersection(p1, p2, p3, p4): d1 = p2 - p1 d2 = p4 -p3 denom = d1[0] * d2[1] - d1[1] * d2[0] if abs(denom) < 1e-6: return None t = ((p3[0] - p1[0]) * d2[1] - (p3[1] - p1[1]) * d2[0]) / denom return p1 + t * d1 # ---- Вычисляем углы ---- l_angle = elbow_angle(l_sh, l_el, l_wr) # угол в левом локте r_angle = elbow_angle(r_sh, r_el, r_wr) # угол в правом локте # ---- КРЕСТ ---- ''' # 1. Оба локтя сильно согнуты (< 100°) elbows_bent = (l_angle < 100 and r_angle < 100) # 2. Левое запястье правее правого (перекрест) wrists_crossed = l_wr[0] > r_wr[0] + 5 # небольшой запас в пикселях (можно и 0) # 3. Запястья находятся между плечами и бёдрами по Y (уровень груди) wrists_at_chest = ( shoulder_center_y - 0.3 * torso_height < l_wr[1] < hip_center_y + 0.3 * torso_height and shoulder_center_y - 0.3 * torso_height < r_wr[1] < hip_center_y + 0.3 * torso_height ) cross = elbows_bent and wrists_crossed and wrists_at_chest if cross: return 'cross' ''' forearms_cross = segments_intersect(l_el, l_wr, r_el, r_wr) intersection = line_intersection(l_el, l_wr, r_el, r_wr) intersection_on_chest = False if intersection is not None: intersection_on_chest = (shoulder_center_y - 0.3 * torso_height < intersection[1] < hip_center_y + 0.3 * torso_height) cross = forearms_cross and intersection_on_chest if cross: return 'cross' ## ---- ДОМИК ---- ## 1. Запястья выше носа #wrists_above_nose = (l_wr[1] < nose[1] and r_wr[1] < nose[1]) ## 2. Локти выше плеч (верхняя граница плеч – min по Y среди плеч) #shoulders_top_y = min(l_sh[1], r_sh[1]) #elbows_above_shoulders = (l_el[1] < shoulders_top_y and r_el[1] < shoulders_top_y) ## 3. Расстояние между локтями > расстояние между плечами #elbow_distance = np.linalg.norm(l_el - r_el) #elbows_far_apart = elbow_distance > shoulder_width ## 4. Расстояние между запястьями < половины ширины плеч #wrist_distance = np.linalg.norm(l_wr - r_wr) #wrists_near = wrist_distance < 0.5 * shoulder_width #dome = wrists_above_nose and elbows_above_shoulders and elbows_far_apart and wrists_near #if dome: #return 'dome' # ---- ЛАДОШКА ---- r_sh_z = landmarks[idx['right_shoulder']][2] r_wr_z = landmarks[idx['right_wrist']][2] r_el_z = landmarks[idx['right_elbow']][2] #1. правая рука почти выпрямлена wrist_at_shoulder_y = abs(r_wr[1] - r_sh[1]) < 0.4 * torso_height wrist_at_shoulder_x = abs(r_wr[0] - r_sh[0]) < 0.5 * shoulder_width r_sh_z_divided = (landmarks[12][2] + landmarks[11][2])/2 nose_z = landmarks[0][2] nose_and_sh_distance = r_sh_z_divided - nose_z arm_forward = r_sh_z - r_wr_z print(f"arm_forward = {round(arm_forward, 3)} and nose_and_sh_distance = {round(nose_and_sh_distance, 3)}") if wrist_at_shoulder_y and wrist_at_shoulder_x and (arm_forward > 3.5*nose_and_sh_distance): return "ladoshka" return 'none'