Compare commits

...

3 Commits
main ... alexk

Author SHA1 Message Date
gestures4 08c5b66ace mount versions of pose-detectors combined 5 days ago
gestures4 8a43b58710 sasha mod 2 weeks ago
gestures4 e26b312b2f last alex 2 weeks ago

@ -1,126 +1,324 @@
import numpy as np import numpy as np
import cv2
class ArmController:
def _clip_unit(v):
return float(np.clip(v, -1.0, 1.0))
def _apply_dead_zone(v, dz):
return 0.0 if abs(v) < dz else v
def _robust_metrics(landmarks, min_conf=0.5):
"""
Compute shoulder_center, shoulder_width, torso_height robustly.
Uses hips if available; otherwise falls back to nose/shoulder geometry.
Returns:
shoulder_center (np.array shape (2,))
shoulder_width (float)
torso_height (float)
ok (bool)
"""
def pt(i):
return np.array(landmarks[i][:2], dtype=float), float(landmarks[i][3])
l_sh, c_lsh = pt(11)
r_sh, c_rsh = pt(12)
if c_lsh < min_conf or c_rsh < min_conf:
return None, 0.0, 0.0, False
shoulder_center = (l_sh + r_sh) / 2.0
shoulder_width = float(np.linalg.norm(r_sh - l_sh))
if shoulder_width < 1e-3:
return shoulder_center, 0.0, 0.0, False
# Try hips
l_hip, c_lhip = pt(23)
r_hip, c_rhip = pt(24)
if c_lhip >= min_conf and c_rhip >= min_conf:
hip_center = (l_hip + r_hip) / 2.0
torso_height = float(np.linalg.norm(hip_center - shoulder_center))
if torso_height >= 1e-3:
return shoulder_center, shoulder_width, torso_height, True
# Fallbacks (upper-body only)
nose, c_nose = pt(0)
if c_nose >= min_conf:
nose_to_shoulder = abs(nose[1] - shoulder_center[1])
torso_height = max(1.6 * nose_to_shoulder, 0.9 * shoulder_width)
else:
torso_height = max(1.2 * shoulder_width, 1.0)
return shoulder_center, shoulder_width, float(torso_height), True
class ArmControllerMethod1:
"""
Method 1: Single-hand driving with right wrist.
- Linear: vertical offset of right wrist from shoulder center (normalized by torso height)
- Angular: horizontal offset of right wrist from shoulder center (normalized by shoulder width)
"""
def __init__(self, config, mirror=False): def __init__(self, config, mirror=False):
self.config = config self.config = config
self.mirror = mirror self.mirror = mirror
self.shoulder_idx = {'left': 11, 'right': 12} self.dead_zone = config.get('dead_zone', 0.1)
self.wrist_idx = {'left': 15, 'right': 16}
self.hip_idx = {'left': 23, 'right': 24}
self.dead_zone = config.get('dead_zone', 0.2)
self.debug = config.get('debug', False) self.debug = config.get('debug', False)
self.min_conf = config.get('min_conf', 0.5)
def compute_speeds(self, landmarks, frame_shape=None):
if landmarks is None:
return 0.0, 0.0
# Require: shoulders + right wrist
need = [11, 12, 16]
if any(landmarks[i][3] < self.min_conf for i in need):
return 0.0, 0.0
shoulder_center, shoulder_width, torso_height, ok = _robust_metrics(landmarks, self.min_conf)
if not ok or shoulder_width < 1e-3 or torso_height < 1e-3:
return 0.0, 0.0
r_wr = landmarks[16][:2]
# Positive linear when wrist above shoulder center (forward)
linear = (shoulder_center[1] - r_wr[1]) / torso_height
# Positive angular when wrist to the right of shoulder center
angular = (r_wr[0] - shoulder_center[0]) / shoulder_width
def _get_side_indices(self, side):
"""
Возвращает (shoulder_idx, wrist_idx) для заданной стороны (left/right).
При mirror=True интерпретируем сторону как в реальности: левая/правая рука.
"""
if self.mirror: if self.mirror:
if side == 'left': angular = -angular
s_idx = 12
w_idx = 16 linear = _clip_unit(_apply_dead_zone(linear, self.dead_zone))
else: angular = _clip_unit(_apply_dead_zone(angular, self.dead_zone))
s_idx = 11
w_idx = 15 if self.debug:
else: print(f"[M1] L:{linear:.2f} A:{angular:.2f}")
if side == 'left': return linear, angular
s_idx = 11
w_idx = 15 def draw_overlay(self, frame, landmarks=None):
else: if frame is None:
s_idx = 12 return frame
w_idx = 16 h, w = frame.shape[:2]
return s_idx, w_idx # Draw center cross (screen center approximation)
cv2.line(frame, (w // 2, 0), (w // 2, h), (0, 0, 0), 1)
def _get_shoulder_width(self, landmarks): cv2.line(frame, (0, h // 2), (w, h // 2), (0, 0, 0), 1)
"""Ширина плеч для нормировки горизонтальных смещений.""" # Draw right wrist
left = landmarks[11][:2] if landmarks is not None and landmarks[16][3] > 0.5:
right = landmarks[12][:2] x, y = int(landmarks[16][0]), int(landmarks[16][1])
width = np.linalg.norm(right - left) cv2.circle(frame, (x, y), 8, (0, 255, 255), -1)
if width < 50 or width > 300: return frame
return None
return width
class ArmControllerMethod2:
def _get_torso_height(self, landmarks): """
"""Высота торса для нормировки вертикальных смещений.""" Method 2: Two-hand blended control.
left_shoulder = landmarks[11][:2] - Linear: average vertical offset of both wrists from shoulder center (normalized by torso height)
right_shoulder = landmarks[12][:2] - Angular: horizontal balance of wrists around shoulder center (normalized by shoulder width)
left_hip = landmarks[23][:2] """
right_hip = landmarks[24][:2] def __init__(self, config, mirror=False):
shoulder_center = (left_shoulder + right_shoulder) / 2 self.config = config
hip_center = (left_hip + right_hip) / 2 self.mirror = mirror
height = np.linalg.norm(shoulder_center - hip_center) self.dead_zone = config.get('dead_zone', 0.1)
if height < 50: self.debug = config.get('debug', False)
return None self.min_conf = config.get('min_conf', 0.5)
return height
def compute_speeds(self, landmarks, frame_shape=None):
def _horizontal_displacement_rel(self, landmarks, side): if landmarks is None:
""" return 0.0, 0.0
Нормированное горизонтальное смещение запястья относительно плеча.
Сторона `side` это реальная сторона руки # Require: shoulders + both wrists
""" need = [11, 12, 15, 16]
s_idx, w_idx = self._get_side_indices(side) if any(landmarks[i][3] < self.min_conf for i in need):
return 0.0, 0.0
if landmarks[s_idx][3] < 0.5 or landmarks[w_idx][3] < 0.5:
return 0.0 shoulder_center, shoulder_width, torso_height, ok = _robust_metrics(landmarks, self.min_conf)
if not ok or shoulder_width < 1e-3 or torso_height < 1e-3:
shoulder = landmarks[s_idx][:2]
wrist = landmarks[w_idx][:2]
shoulder_width = self._get_shoulder_width(landmarks)
if shoulder_width is None:
return 0.0
disp = wrist[0] - shoulder[0]
return disp / shoulder_width
def _vertical_displacement_rel(self, landmarks, side):
"""
Вертикальное смещение: верх/низ запястья относительно плеча.
Сторона `side` реальная сторона руки.
"""
s_idx, w_idx = self._get_side_indices(side)
if landmarks[s_idx][3] < 0.5 or landmarks[w_idx][3] < 0.5:
return 0.0
shoulder = landmarks[s_idx][:2]
wrist = landmarks[w_idx][:2]
torso_height = self._get_torso_height(landmarks)
if torso_height is None:
return 0.0
disp = shoulder[1] - wrist[1]
return disp / torso_height
def compute_speeds(self, landmarks):
if (landmarks[11][3] < 0.5 or landmarks[12][3] < 0.5 or
landmarks[15][3] < 0.5 or landmarks[16][3] < 0.5):
if self.debug:
print("Руки не видны")
return 0.0, 0.0 return 0.0, 0.0
linear_side = self.config['linear_arm'] l_wr = landmarks[15][:2]
angular_side = self.config['angular_arm'] r_wr = landmarks[16][:2]
# Linear: average elevation of both wrists
lin_l = (shoulder_center[1] - l_wr[1]) / torso_height
lin_r = (shoulder_center[1] - r_wr[1]) / torso_height
linear = 0.5 * (lin_l + lin_r)
# Angular: horizontal balance
ang = ((r_wr[0] - shoulder_center[0]) - (shoulder_center[0] - l_wr[0])) / shoulder_width
angular = ang
if self.mirror:
angular = -angular
lin_rel = self._horizontal_displacement_rel(landmarks, linear_side) linear = _clip_unit(_apply_dead_zone(linear, self.dead_zone))
ang_rel = self._vertical_displacement_rel(landmarks, angular_side) angular = _clip_unit(_apply_dead_zone(angular, self.dead_zone))
if self.debug: if self.debug:
print(f"lin_rel={lin_rel:.3f}, ang_rel={ang_rel:.3f}") print(f"[M2] L:{linear:.2f} A:{angular:.2f}")
return linear, angular
# Линейная скорость (только вперёд) def draw_overlay(self, frame, landmarks=None):
if lin_rel < self.dead_zone: if frame is None:
linear = 0.0 return frame
if landmarks is not None:
for idx, color in [(15, (255, 0, 255)), (16, (0, 255, 255))]:
if landmarks[idx][3] > 0.5:
x, y = int(landmarks[idx][0]), int(landmarks[idx][1])
cv2.circle(frame, (x, y), 8, color, -1)
return frame
class ArmControllerMethod3:
"""
Method 3: Elbow-augmented control.
- Linear: average vertical offset of elbows (normalized by torso height)
- Angular: wrist horizontal balance (normalized by shoulder width)
"""
def __init__(self, config, mirror=False):
self.config = config
self.mirror = mirror
self.dead_zone = config.get('dead_zone', 0.1)
self.debug = config.get('debug', False)
self.min_conf = config.get('min_conf', 0.5)
def compute_speeds(self, landmarks, frame_shape=None):
if landmarks is None:
return 0.0, 0.0
# Require shoulders; prefer elbows for linear; wrists for angular.
need_base = [11, 12]
if any(landmarks[i][3] < self.min_conf for i in need_base):
return 0.0, 0.0
elbows_ok = (landmarks[13][3] >= self.min_conf and landmarks[14][3] >= self.min_conf)
wrists_ok = (landmarks[15][3] >= self.min_conf and landmarks[16][3] >= self.min_conf)
if not elbows_ok and not wrists_ok:
return 0.0, 0.0
shoulder_center, shoulder_width, torso_height, ok = _robust_metrics(landmarks, self.min_conf)
if not ok or shoulder_width < 1e-3 or torso_height < 1e-3:
return 0.0, 0.0
# Linear: prefer elbows, fallback to wrists average if elbows missing
if elbows_ok:
l_el = landmarks[13][:2]
r_el = landmarks[14][:2]
lin_l = (shoulder_center[1] - l_el[1]) / torso_height
lin_r = (shoulder_center[1] - r_el[1]) / torso_height
linear = 0.5 * (lin_l + lin_r)
else: else:
linear = min(lin_rel, 1.0) * self.config['max_speed_linear'] l_wr = landmarks[15][:2]
r_wr = landmarks[16][:2]
lin_l = (shoulder_center[1] - l_wr[1]) / torso_height
lin_r = (shoulder_center[1] - r_wr[1]) / torso_height
linear = 0.5 * (lin_l + lin_r)
# Угловая скорость # Angular: use wrists if available, else 0
if abs(ang_rel) < self.dead_zone: if wrists_ok:
angular = 0.0 l_wr = landmarks[15][:2]
r_wr = landmarks[16][:2]
angular = ((r_wr[0] + l_wr[0]) - 2 * shoulder_center[0]) / shoulder_width
else: else:
ang_rel_clipped = np.clip(ang_rel, -1.0, 1.0) angular = 0.0
angular = ang_rel_clipped * self.config['max_speed_angular']
if self.mirror:
angular = -angular
linear = _clip_unit(_apply_dead_zone(linear, self.dead_zone))
angular = _clip_unit(_apply_dead_zone(angular, self.dead_zone))
if self.debug:
print(f"[M3] L:{linear:.2f} A:{angular:.2f}")
return linear, angular return linear, angular
def draw_overlay(self, frame, landmarks=None):
if frame is None:
return frame
if landmarks is not None:
for idx, color in [(13, (0, 200, 0)), (14, (0, 200, 0)), (15, (0, 255, 255)), (16, (255, 0, 255))]:
if landmarks[idx][3] > 0.5:
x, y = int(landmarks[idx][0]), int(landmarks[idx][1])
cv2.circle(frame, (x, y), 6, color, -1)
return frame
class ArmControllerMethod4:
"""
Method 4: 3x3 grid based on landmark 19 (right index finger tip).
Screen split at 2/5 and 3/5 (both axes). Center band = 0.
Proportional speed away from the center bands.
"""
def __init__(self, config, mirror=False):
self.config = config
self.mirror = mirror
self.finger_idx = 19 # right index finger tip
self.debug = config.get('debug', False)
def compute_speeds(self, landmarks, frame_shape=None):
linear = 0.0
angular = 0.0
if frame_shape is None or landmarks is None:
return 0.0, 0.0
if landmarks[self.finger_idx][3] < 0.5:
return 0.0, 0.0
h, w = int(frame_shape[0]), int(frame_shape[1])
x = landmarks[self.finger_idx][0]
y = landmarks[self.finger_idx][1]
# Angular (horizontal): center band 2/5..3/5 = 0
if 2 * w / 5 <= x <= 3 * w / 5:
angular = 0.0
elif x > 3 * w / 5:
angular = (x * 5) / (2 * w) - 1
else:
angular = (x - 3 * w / 5) / (2 * w / 5)
# Linear (vertical): center band 2/5..3/5 = 0
if 2 * h / 5 <= y <= 3 * h / 5:
linear = 0.0
elif y > 3 * h / 5:
linear = -((y * 5) / (2 * h) - 1)
else:
linear = -(y - 3 * h / 5) / (2 * h / 5)
if self.mirror:
angular = -angular
if self.debug:
print(f"[M4] L:{linear:.2f} A:{angular:.2f}")
return _clip_unit(linear), _clip_unit(angular)
def draw_overlay(self, frame, landmarks=None):
if frame is None:
return frame
h, w = frame.shape[:2]
x1, x2 = int(w * 2 / 5), int(w * 3 / 5)
y1, y2 = int(h * 2 / 5), int(h * 3 / 5)
# Grid lines
cv2.line(frame, (x1, 0), (x1, h), (0, 0, 0), 2)
cv2.line(frame, (x2, 0), (x2, h), (0, 0, 0), 2)
cv2.line(frame, (0, y1), (w, y1), (0, 0, 0), 2)
cv2.line(frame, (0, y2), (w, y2), (0, 0, 0), 2)
# Highlight active cell + finger
if landmarks is not None and landmarks[self.finger_idx][3] > 0.5:
fx, fy = int(landmarks[self.finger_idx][0]), int(landmarks[self.finger_idx][1])
cx0, cx1 = (0, x1) if fx < x1 else ((x2, w) if fx > x2 else (x1, x2))
cy0, cy1 = (0, y1) if fy < y1 else ((y2, h) if fy > y2 else (y1, y2))
overlay = frame.copy()
cv2.rectangle(overlay, (cx0, cy0), (cx1, cy1), (0, 255, 255), -1)
frame = cv2.addWeighted(overlay, 0.2, frame, 0.8, 0)
cv2.circle(frame, (fx, fy), 8, (0, 255, 255), -1)
cv2.circle(frame, (fx, fy), 12, (0, 120, 120), 2)
return frame

@ -1,82 +1,131 @@
import numpy as np import numpy as np
class SpecialGestureDetector: class SpecialGestureDetector:
def __init__(self, mode='geometric', model_path=None, class_names=None): """
Detects special static gestures using either simple geometric rules or an ML classifier.
Supported gesture labels:
- 'cross' : forearms crossed near the chest
- 'light' : right arm pose approximating a 'light' toggle
- 'dome' : arms forming a dome above the head
- 'none' : no special gesture detected
"""
def __init__(self, mode='geometric', model_path=None, class_names=None, debug=False, thresholds=None):
self.mode = mode self.mode = mode
self.debug = debug
# Defaults for geometric detection
self.th = {
'min_conf': 0.5,
'shoulder_width_min': 30.0,
'torso_height_min': 10.0,
'chest_band': 0.25, # widened to be more forgiving
'wrists_near_factor': 0.6, # relaxed for dome
'elbow_far_factor': 0.9, # relaxed for dome
'light_elbow_min': 45.0,
'light_elbow_max': 120.0,
'light_shoulder_min': -5.0,
'light_shoulder_max': 20.0
}
if thresholds:
self.th.update(thresholds)
if mode == 'ml': if mode == 'ml':
from ml_gestures.predict import MLGesturePredictor from ml_gestures.predict import MLGesturePredictor
if model_path is None or class_names is None: if model_path is None or class_names is None:
raise ValueError("Для ML нужны model_path и class_names") raise ValueError("For ML mode, provide model_path and class_names")
self.ml_predictor = MLGesturePredictor(model_path, class_names) self.ml_predictor = MLGesturePredictor(model_path, class_names)
print("Использую статический ML классификатор") if self.debug:
print("SpecialGestureDetector: Using ML classifier")
else: else:
self.ml_predictor = None self.ml_predictor = None
print("Использую геометрические отношения для детекции специальных жестов") if self.debug:
self.debug = False # Включите для отладки print("SpecialGestureDetector: Using geometric rules")
def predict(self, landmarks): def predict(self, landmarks):
if landmarks is None:
return 'none'
if self.mode == 'geometric': if self.mode == 'geometric':
return self._geometric_predict(landmarks) return self._geometric_predict(landmarks)
else: else:
return self.ml_predictor.predict(landmarks) return self.ml_predictor.predict(landmarks)
def _geometric_predict(self, landmarks): def _geometric_predict(self, landmarks):
# Индексы MediaPipe
idx = { idx = {
'nose': 0, 'nose': 0,
'left_shoulder': 11, 'left_shoulder': 11, 'right_shoulder': 12,
'right_shoulder': 12, 'left_elbow': 13, 'right_elbow': 14,
'left_elbow': 13, 'left_wrist': 15, 'right_wrist': 16,
'right_elbow': 14, 'left_hip': 23, 'right_hip': 24,
'left_wrist': 15,
'right_wrist': 16,
'left_hip': 23,
'right_hip': 24,
} }
# Повышенный порог уверенности для специальных жестов min_conf = self.th['min_conf']
min_conf = 0.5 # Only upper-body required (hips optional)
required = ['left_shoulder', 'right_shoulder', 'left_elbow', 'right_elbow', required = [
'left_wrist', 'right_wrist', 'nose'] 'left_shoulder', 'right_shoulder',
'left_elbow', 'right_elbow',
'left_wrist', 'right_wrist',
'nose'
]
for p in required: for p in required:
if landmarks[idx[p]][3] < min_conf: if landmarks[idx[p]][3] < min_conf:
if self.debug: if self.debug:
print(f"{p} low confidence") print(f"[SG] Low confidence for {p}: {landmarks[idx[p]][3]:.2f}")
return 'none' return 'none'
# Координаты (x, y) l_sh = np.array(landmarks[idx['left_shoulder']][:2], dtype=float)
l_sh = landmarks[idx['left_shoulder']][:2] r_sh = np.array(landmarks[idx['right_shoulder']][:2], dtype=float)
r_sh = landmarks[idx['right_shoulder']][:2] l_el = np.array(landmarks[idx['left_elbow']][:2], dtype=float)
l_el = landmarks[idx['left_elbow']][:2] r_el = np.array(landmarks[idx['right_elbow']][:2], dtype=float)
r_el = landmarks[idx['right_elbow']][:2] l_wr = np.array(landmarks[idx['left_wrist']][:2], dtype=float)
l_wr = landmarks[idx['left_wrist']][:2] r_wr = np.array(landmarks[idx['right_wrist']][:2], dtype=float)
r_wr = landmarks[idx['right_wrist']][:2] nose = np.array(landmarks[idx['nose']][:2], dtype=float)
l_hip = landmarks[idx['left_hip']][:2]
r_hip = landmarks[idx['right_hip']][:2] l_hip = np.array(landmarks[idx['left_hip']][:2], dtype=float)
nose = np.array(landmarks[idx['nose']][:2]) r_hip = np.array(landmarks[idx['right_hip']][:2], dtype=float)
c_lhip = landmarks[idx['left_hip']][3]
shoulder_center_y = (l_sh[1] + r_sh[1]) / 2 c_rhip = landmarks[idx['right_hip']][3]
hip_center_y = (l_hip[1] + r_hip[1]) / 2
torso_height = hip_center_y - shoulder_center_y shoulder_center_y = (l_sh[1] + r_sh[1]) / 2.0
shoulder_width = np.linalg.norm(r_sh - l_sh) shoulder_width = np.linalg.norm(r_sh - l_sh)
if shoulder_width < 30 or torso_height < 10: if shoulder_width < self.th['shoulder_width_min']:
if self.debug:
print(f"[SG] Shoulder width too small: {shoulder_width:.1f}")
return 'none'
# Torso height: prefer hips if visible, otherwise fallback using nose/shoulders
if c_lhip >= min_conf and c_rhip >= min_conf:
hip_center_y = (l_hip[1] + r_hip[1]) / 2.0
torso_height = hip_center_y - shoulder_center_y
else:
nose_to_shoulder = abs(nose[1] - shoulder_center_y)
torso_height = max(1.6 * nose_to_shoulder, 0.9 * shoulder_width)
hip_center_y = shoulder_center_y + torso_height
if torso_height < self.th['torso_height_min']:
if self.debug:
print(f"[SG] Torso height too small: {torso_height:.1f}")
return 'none' return 'none'
# ---- Вспомогательные функции ----
def angle_between_vectors(v1, v2): def angle_between_vectors(v1, v2):
"""Угол между двумя векторами в градусах (0..180)""" n1 = np.linalg.norm(v1)
cos_a = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2) + 1e-6) n2 = np.linalg.norm(v2)
return np.arccos(np.clip(cos_a, -1.0, 1.0)) * 180 / np.pi if n1 < 1e-6 or n2 < 1e-6:
return 0.0
def elbow_angle(shoulder, elbow, wrist): cos_a = np.dot(v1, v2) / (n1 * n2)
"""Угол в локте (плечо-локоть-запястье)""" cos_a = float(np.clip(cos_a, -1.0, 1.0))
v1 = shoulder - elbow return np.degrees(np.arccos(cos_a))
v2 = wrist - elbow
def joint_angle(p_prev, p_joint, p_next):
v1 = p_prev - p_joint
v2 = p_next - p_joint
return angle_between_vectors(v1, v2) return angle_between_vectors(v1, v2)
def segments_intersect(p1, p2, p3, p4): def segments_intersect(p1, p2, p3, p4):
def cross(o, a, b): def cross(o, a, b):
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o [1])* (b[0] - o[0]) return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])
d1 = cross(p3, p4, p1) d1 = cross(p3, p4, p1)
d2 = cross(p3, p4, p2) d2 = cross(p3, p4, p2)
d3 = cross(p1, p2, p3) d3 = cross(p1, p2, p3)
@ -85,60 +134,51 @@ class SpecialGestureDetector:
def line_intersection(p1, p2, p3, p4): def line_intersection(p1, p2, p3, p4):
d1 = p2 - p1 d1 = p2 - p1
d2 = p4 -p3 d2 = p4 - p3
denom = d1[0] * d2[1] - d1[1] * d2[0] denom = d1[0] * d2[1] - d1[1] * d2[0]
if abs(denom) < 1e-6: if abs(denom) < 1e-6:
return None return None
t = ((p3[0] - p1[0]) * d2[1] - (p3[1] - p1[1]) * d2[0]) / denom t = ((p3[0] - p1[0]) * d2[1] - (p3[1] - p1[1]) * d2[0]) / denom
return p1 + t * d1 return p1 + t * d1
# ---- Вычисляем углы ---- # Angles (geometric cues)
l_angle = elbow_angle(l_sh, l_el, l_wr) # угол в левом локте l_elbow_angle = joint_angle(l_sh, l_el, l_wr)
r_angle = elbow_angle(r_sh, r_el, r_wr) # угол в правом локте r_elbow_angle = joint_angle(r_sh, r_el, r_wr)
r_shoulder_like_angle = joint_angle(r_hip, r_sh, r_el)
# ---- КРЕСТ ----
''' # 1) CROSS
# 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) forearms_cross = segments_intersect(l_el, l_wr, r_el, r_wr)
intersection = line_intersection(l_el, l_wr, r_el, r_wr) intersection = line_intersection(l_el, l_wr, r_el, r_wr)
intersection_on_chest = False intersection_on_chest = False
if intersection is not None: if intersection is not None:
intersection_on_chest = (shoulder_center_y - 0.2 * torso_height < intersection[1] < hip_center_y + 0.2 * torso_height) band = self.th['chest_band'] * torso_height
cross = forearms_cross and intersection_on_chest intersection_on_chest = (shoulder_center_y - band) < intersection[1] < (hip_center_y + band)
if cross:
if self.debug:
print(f"[SG] cross_check: intersect={forearms_cross}, chest={intersection_on_chest}")
if forearms_cross and intersection_on_chest:
return 'cross' return 'cross'
# ---- ДОМИК ---- # 2) LIGHT
# 1. Запястья выше носа if (self.th['light_elbow_min'] < r_elbow_angle < self.th['light_elbow_max'] and
wrists_above_nose = (l_wr[1] < nose[1] and r_wr[1] < nose[1]) self.th['light_shoulder_min'] < r_shoulder_like_angle < self.th['light_shoulder_max']):
return 'light'
# 2. Локти выше плеч (верхняя граница плеч min по Y среди плеч) # 3) DOME
wrists_above_nose = (l_wr[1] < nose[1] and r_wr[1] < nose[1])
shoulders_top_y = min(l_sh[1], r_sh[1]) 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) 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) elbow_distance = np.linalg.norm(l_el - r_el)
elbows_far_apart = elbow_distance > shoulder_width elbows_far_apart = elbow_distance > (self.th['elbow_far_factor'] * shoulder_width)
# 4. Расстояние между запястьями < половины ширины плеч
wrist_distance = np.linalg.norm(l_wr - r_wr) wrist_distance = np.linalg.norm(l_wr - r_wr)
wrists_near = wrist_distance < 0.5 * shoulder_width wrists_near = wrist_distance < (self.th['wrists_near_factor'] * shoulder_width)
if self.debug:
print(f"[SG] dome_check: wrists_above={wrists_above_nose}, elbows_above={elbows_above_shoulders}, "
f"elbow_d={elbow_distance:.1f}, wrist_d={wrist_distance:.1f}")
dome = wrists_above_nose and elbows_above_shoulders and elbows_far_apart and wrists_near if wrists_above_nose and elbows_above_shoulders and elbows_far_apart and wrists_near:
if dome:
return 'dome' return 'dome'
return 'none' return 'none'

Loading…
Cancel
Save