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

165 lines
7.1 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
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'