Compare commits

..

No commits in common. 'alexk' and 'main' have entirely different histories.
alexk ... main

@ -1,324 +1,126 @@
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.dead_zone = config.get('dead_zone', 0.1) self.shoulder_idx = {'left': 11, 'right': 12}
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] def _get_side_indices(self, side):
# 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
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"[M1] L:{linear:.2f} A:{angular:.2f}")
return linear, angular
def draw_overlay(self, frame, landmarks=None):
if frame is None:
return frame
h, w = frame.shape[:2]
# Draw center cross (screen center approximation)
cv2.line(frame, (w // 2, 0), (w // 2, h), (0, 0, 0), 1)
cv2.line(frame, (0, h // 2), (w, h // 2), (0, 0, 0), 1)
# Draw right wrist
if landmarks is not None and landmarks[16][3] > 0.5:
x, y = int(landmarks[16][0]), int(landmarks[16][1])
cv2.circle(frame, (x, y), 8, (0, 255, 255), -1)
return frame
class ArmControllerMethod2:
""" """
Method 2: Two-hand blended control. Возвращает (shoulder_idx, wrist_idx) для заданной стороны (left/right).
- Linear: average vertical offset of both wrists from shoulder center (normalized by torso height) При mirror=True интерпретируем сторону как в реальности: левая/правая рука.
- Angular: horizontal balance of wrists around shoulder center (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 + both wrists
need = [11, 12, 15, 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
l_wr = landmarks[15][:2]
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: if self.mirror:
angular = -angular if side == 'left':
s_idx = 12
w_idx = 16
else:
s_idx = 11
w_idx = 15
else:
if side == 'left':
s_idx = 11
w_idx = 15
else:
s_idx = 12
w_idx = 16
return s_idx, w_idx
def _get_shoulder_width(self, landmarks):
"""Ширина плеч для нормировки горизонтальных смещений."""
left = landmarks[11][:2]
right = landmarks[12][:2]
width = np.linalg.norm(right - left)
if width < 50 or width > 300:
return None
return width
def _get_torso_height(self, landmarks):
"""Высота торса для нормировки вертикальных смещений."""
left_shoulder = landmarks[11][:2]
right_shoulder = landmarks[12][:2]
left_hip = landmarks[23][:2]
right_hip = landmarks[24][:2]
shoulder_center = (left_shoulder + right_shoulder) / 2
hip_center = (left_hip + right_hip) / 2
height = np.linalg.norm(shoulder_center - hip_center)
if height < 50:
return None
return height
def _horizontal_displacement_rel(self, landmarks, side):
"""
Нормированное горизонтальное смещение запястья относительно плеча.
Сторона `side` это реальная сторона руки
"""
s_idx, w_idx = self._get_side_indices(side)
linear = _clip_unit(_apply_dead_zone(linear, self.dead_zone)) if landmarks[s_idx][3] < 0.5 or landmarks[w_idx][3] < 0.5:
angular = _clip_unit(_apply_dead_zone(angular, self.dead_zone)) return 0.0
if self.debug: shoulder = landmarks[s_idx][:2]
print(f"[M2] L:{linear:.2f} A:{angular:.2f}") wrist = landmarks[w_idx][:2]
return linear, angular
def draw_overlay(self, frame, landmarks=None): shoulder_width = self._get_shoulder_width(landmarks)
if frame is None: if shoulder_width is None:
return frame return 0.0
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
disp = wrist[0] - shoulder[0]
return disp / shoulder_width
class ArmControllerMethod3: def _vertical_displacement_rel(self, landmarks, side):
""" """
Method 3: Elbow-augmented control. Вертикальное смещение: верх/низ запястья относительно плеча.
- Linear: average vertical offset of elbows (normalized by torso height) Сторона `side` реальная сторона руки.
- Angular: wrist horizontal balance (normalized by shoulder width)
""" """
def __init__(self, config, mirror=False): s_idx, w_idx = self._get_side_indices(side)
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[s_idx][3] < 0.5 or landmarks[w_idx][3] < 0.5:
if landmarks is None: return 0.0
return 0.0, 0.0
# Require shoulders; prefer elbows for linear; wrists for angular. shoulder = landmarks[s_idx][:2]
need_base = [11, 12] wrist = landmarks[w_idx][:2]
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) torso_height = self._get_torso_height(landmarks)
wrists_ok = (landmarks[15][3] >= self.min_conf and landmarks[16][3] >= self.min_conf) if torso_height is None:
return 0.0
if not elbows_ok and not wrists_ok: disp = shoulder[1] - wrist[1]
return 0.0, 0.0 return disp / torso_height
shoulder_center, shoulder_width, torso_height, ok = _robust_metrics(landmarks, self.min_conf) def compute_speeds(self, landmarks):
if not ok or shoulder_width < 1e-3 or torso_height < 1e-3: 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: prefer elbows, fallback to wrists average if elbows missing linear_side = self.config['linear_arm']
if elbows_ok: angular_side = self.config['angular_arm']
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:
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 wrists_ok:
l_wr = landmarks[15][:2]
r_wr = landmarks[16][:2]
angular = ((r_wr[0] + l_wr[0]) - 2 * shoulder_center[0]) / shoulder_width
else:
angular = 0.0
if self.mirror:
angular = -angular
linear = _clip_unit(_apply_dead_zone(linear, self.dead_zone)) lin_rel = self._horizontal_displacement_rel(landmarks, linear_side)
angular = _clip_unit(_apply_dead_zone(angular, self.dead_zone)) ang_rel = self._vertical_displacement_rel(landmarks, angular_side)
if self.debug: if self.debug:
print(f"[M3] L:{linear:.2f} A:{angular:.2f}") print(f"lin_rel={lin_rel:.3f}, ang_rel={ang_rel:.3f}")
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: if lin_rel < self.dead_zone:
"""
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 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: else:
angular = (x - 3 * w / 5) / (2 * w / 5) linear = min(lin_rel, 1.0) * self.config['max_speed_linear']
# Linear (vertical): center band 2/5..3/5 = 0 # Угловая скорость
if 2 * h / 5 <= y <= 3 * h / 5: if abs(ang_rel) < self.dead_zone:
linear = 0.0 angular = 0.0
elif y > 3 * h / 5:
linear = -((y * 5) / (2 * h) - 1)
else: else:
linear = -(y - 3 * h / 5) / (2 * h / 5) ang_rel_clipped = np.clip(ang_rel, -1.0, 1.0)
angular = ang_rel_clipped * self.config['max_speed_angular']
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): return linear, angular
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,126 +1,77 @@
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("For ML mode, provide model_path and class_names") raise ValueError("Для ML нужны model_path и class_names")
self.ml_predictor = MLGesturePredictor(model_path, class_names) self.ml_predictor = MLGesturePredictor(model_path, class_names)
if self.debug: print("Использую статический ML классификатор")
print("SpecialGestureDetector: Using ML classifier")
else: else:
self.ml_predictor = None self.ml_predictor = None
if self.debug: print("Использую геометрические отношения для детекции специальных жестов")
print("SpecialGestureDetector: Using geometric rules") self.debug = False # Включите для отладки
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, 'right_shoulder': 12, 'left_shoulder': 11,
'left_elbow': 13, 'right_elbow': 14, 'right_shoulder': 12,
'left_wrist': 15, 'right_wrist': 16, 'left_elbow': 13,
'left_hip': 23, 'right_hip': 24, 'right_elbow': 14,
'left_wrist': 15,
'right_wrist': 16,
'left_hip': 23,
'right_hip': 24,
} }
min_conf = self.th['min_conf'] # Повышенный порог уверенности для специальных жестов
# Only upper-body required (hips optional) min_conf = 0.5
required = [ required = ['left_shoulder', 'right_shoulder', 'left_elbow', 'right_elbow',
'left_shoulder', 'right_shoulder', 'left_wrist', 'right_wrist', 'nose']
'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"[SG] Low confidence for {p}: {landmarks[idx[p]][3]:.2f}") print(f"{p} low confidence")
return 'none' return 'none'
l_sh = np.array(landmarks[idx['left_shoulder']][:2], dtype=float) # Координаты (x, y)
r_sh = np.array(landmarks[idx['right_shoulder']][:2], dtype=float) l_sh = landmarks[idx['left_shoulder']][:2]
l_el = np.array(landmarks[idx['left_elbow']][:2], dtype=float) r_sh = landmarks[idx['right_shoulder']][:2]
r_el = np.array(landmarks[idx['right_elbow']][:2], dtype=float) l_el = landmarks[idx['left_elbow']][:2]
l_wr = np.array(landmarks[idx['left_wrist']][:2], dtype=float) r_el = landmarks[idx['right_elbow']][:2]
r_wr = np.array(landmarks[idx['right_wrist']][:2], dtype=float) l_wr = landmarks[idx['left_wrist']][:2]
nose = np.array(landmarks[idx['nose']][:2], dtype=float) r_wr = landmarks[idx['right_wrist']][:2]
l_hip = landmarks[idx['left_hip']][:2]
l_hip = np.array(landmarks[idx['left_hip']][:2], dtype=float) r_hip = landmarks[idx['right_hip']][:2]
r_hip = np.array(landmarks[idx['right_hip']][:2], dtype=float) nose = np.array(landmarks[idx['nose']][:2])
c_lhip = landmarks[idx['left_hip']][3]
c_rhip = landmarks[idx['right_hip']][3] shoulder_center_y = (l_sh[1] + r_sh[1]) / 2
hip_center_y = (l_hip[1] + r_hip[1]) / 2
shoulder_center_y = (l_sh[1] + r_sh[1]) / 2.0
shoulder_width = np.linalg.norm(r_sh - l_sh)
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 torso_height = hip_center_y - shoulder_center_y
else: shoulder_width = np.linalg.norm(r_sh - l_sh)
nose_to_shoulder = abs(nose[1] - shoulder_center_y) if shoulder_width < 30 or torso_height < 10:
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):
n1 = np.linalg.norm(v1) """Угол между двумя векторами в градусах (0..180)"""
n2 = np.linalg.norm(v2) cos_a = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2) + 1e-6)
if n1 < 1e-6 or n2 < 1e-6: return np.arccos(np.clip(cos_a, -1.0, 1.0)) * 180 / np.pi
return 0.0
cos_a = np.dot(v1, v2) / (n1 * n2) def elbow_angle(shoulder, elbow, wrist):
cos_a = float(np.clip(cos_a, -1.0, 1.0)) """Угол в локте (плечо-локоть-запястье)"""
return np.degrees(np.arccos(cos_a)) v1 = shoulder - elbow
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):
@ -141,44 +92,53 @@ class SpecialGestureDetector:
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_elbow_angle = joint_angle(l_sh, l_el, l_wr) l_angle = elbow_angle(l_sh, l_el, l_wr) # угол в левом локте
r_elbow_angle = joint_angle(r_sh, r_el, r_wr) r_angle = elbow_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:
band = self.th['chest_band'] * torso_height intersection_on_chest = (shoulder_center_y - 0.2 * torso_height < intersection[1] < hip_center_y + 0.2 * torso_height)
intersection_on_chest = (shoulder_center_y - band) < intersection[1] < (hip_center_y + band) cross = forearms_cross and intersection_on_chest
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 # ---- ДОМИК ----
if (self.th['light_elbow_min'] < r_elbow_angle < self.th['light_elbow_max'] and # 1. Запястья выше носа
self.th['light_shoulder_min'] < r_shoulder_like_angle < self.th['light_shoulder_max']):
return 'light'
# 3) DOME
wrists_above_nose = (l_wr[1] < nose[1] and r_wr[1] < nose[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]) 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 > (self.th['elbow_far_factor'] * shoulder_width) elbows_far_apart = elbow_distance > shoulder_width
wrist_distance = np.linalg.norm(l_wr - r_wr)
wrists_near = wrist_distance < (self.th['wrists_near_factor'] * shoulder_width)
if self.debug: # 4. Расстояние между запястьями < половины ширины плеч
print(f"[SG] dome_check: wrists_above={wrists_above_nose}, elbows_above={elbows_above_shoulders}, " wrist_distance = np.linalg.norm(l_wr - r_wr)
f"elbow_d={elbow_distance:.1f}, wrist_d={wrist_distance:.1f}") wrists_near = wrist_distance < 0.5 * shoulder_width
if wrists_above_nose and elbows_above_shoulders and elbows_far_apart and wrists_near: dome = 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