You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gesture_rec/gesture_control/special_gestures.py

117 lines
5.2 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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.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)
# ---- Вычисляем углы ----
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'
# ---- ДОМИК ----
# 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'
return 'none'