2 Commits
Author SHA1 Message Date
gestures5 76af85ad06 dominika version 2026-07-10 10:07:27 +03:00
gestures5 b73f67182f horisontal arm detection 2026-07-07 11:29:37 +03:00
2 changed files with 134 additions and 425 deletions
+57 -308
View File
@@ -1,324 +1,73 @@
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.elbow_idx = {'left': 13, 'ri ght': 14}
#self.wrist_idx = {'left': 15, 'right': 16}
#self.hip_idx = {'left': 23, 'right': 24}
self.r_wirst_id = 15 #16
self.r_elbow_id = 13 #14
self.r_shoulder_id = 11 # 12
self.error = 23
self.bag = 27
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: def _get_angle(self, x1, y1, x2, y2):
angle = np.arctan2(y2 - y1, x2 - x1)
return angle
def compute_speeds(self, landmarks):
linear = 0.
angular = 0.
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
# Require: shoulders + right wrist right_shoulder_point = landmarks[self.r_shoulder_id]
need = [11, 12, 16] right_shoulder_x = right_shoulder_point[0]
if any(landmarks[i][3] < self.min_conf for i in need): right_shoulder_y = right_shoulder_point[1]
return 0.0, 0.0
shoulder_center, shoulder_width, torso_height, ok = _robust_metrics(landmarks, self.min_conf) right_elbow_point = landmarks[self.r_elbow_id] # [x, y, z, v]
if not ok or shoulder_width < 1e-3 or torso_height < 1e-3: right_elbow_x = right_elbow_point[0]
return 0.0, 0.0 right_elbow_y = right_elbow_point[1]
r_wr = landmarks[16][:2] angle_shoulder_elbow = self._get_angle(right_shoulder_x, right_shoulder_y, right_elbow_x, right_elbow_y)
# Positive linear when wrist above shoulder center (forward) right_wirst_point = landmarks[self.r_wirst_id]
linear = (shoulder_center[1] - r_wr[1]) / torso_height right_wirst_x = right_wirst_point[0]
# Positive angular when wrist to the right of shoulder center right_wirst_y = right_wirst_point[1]
angular = (r_wr[0] - shoulder_center[0]) / shoulder_width
if self.mirror: angle_elbow_wirst = self._get_angle(right_elbow_x, right_elbow_y, right_wirst_x, right_wirst_y)
angular = -angular
linear = _clip_unit(_apply_dead_zone(linear, self.dead_zone)) five_deg_in_rad = np.deg2rad(self.error)
angular = _clip_unit(_apply_dead_zone(angular, self.dead_zone))
if (angle_shoulder_elbow < np.pi/2.5 + five_deg_in_rad) and (angle_shoulder_elbow > np.pi/2.5 -five_deg_in_rad):
print(f"Angle between shoulder and elbow is {angle_shoulder_elbow} {np.rad2deg(angle_shoulder_elbow)}")
#print(f"Angle between elbow and wirst is {angle_elbow_wirst}")
if (angle_elbow_wirst > np.deg2rad(-90 - self.error)) and (angle_elbow_wirst < np.deg2rad(-90 + self.error)):
linear = 1.
if (angle_elbow_wirst > np.deg2rad(90 - self.error)) and (angle_elbow_wirst < np.deg2rad(90 + self.error)):
linear = -1.
if (angle_elbow_wirst > np.deg2rad(-45 - self.bag)) and (angle_elbow_wirst < np.deg2rad(-45 + self.bag)):
angular = -1.
if (angle_elbow_wirst > np.deg2rad(-135 - self.bag)) and (angle_elbow_wirst < np.deg2rad(-135 + self.bag)):
angular = 1.
if self.debug:
print(f"[M1] 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
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.
- Linear: average vertical offset of both wrists from shoulder center (normalized by torso height)
- 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:
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"[M2] L:{linear:.2f} A:{angular:.2f}")
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 [(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:
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))
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
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
+77 -117
View File
@@ -1,131 +1,82 @@
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]
r_hip = landmarks[idx['right_hip']][:2]
nose = np.array(landmarks[idx['nose']][:2])
l_hip = np.array(landmarks[idx['left_hip']][:2], dtype=float) shoulder_center_y = (l_sh[1] + r_sh[1]) / 2
r_hip = np.array(landmarks[idx['right_hip']][:2], dtype=float) hip_center_y = (l_hip[1] + r_hip[1]) / 2
c_lhip = landmarks[idx['left_hip']][3] torso_height = hip_center_y - shoulder_center_y
c_rhip = landmarks[idx['right_hip']][3]
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 < self.th['shoulder_width_min']: if shoulder_width < 30 or torso_height < 10:
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):
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)
cos_a = float(np.clip(cos_a, -1.0, 1.0))
return np.degrees(np.arccos(cos_a))
def joint_angle(p_prev, p_joint, p_next): def elbow_angle(shoulder, elbow, wrist):
v1 = p_prev - p_joint """Угол в локте (плечо-локоть-запястье)"""
v2 = p_next - p_joint v1 = shoulder - elbow
v2 = wrist - elbow
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)
@@ -134,51 +85,60 @@ 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_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
# 4. Расстояние между запястьями < половины ширины плеч
wrist_distance = np.linalg.norm(l_wr - r_wr) wrist_distance = np.linalg.norm(l_wr - r_wr)
wrists_near = wrist_distance < (self.th['wrists_near_factor'] * shoulder_width) wrists_near = wrist_distance < 0.5 * shoulder_width
if self.debug: dome = wrists_above_nose and elbows_above_shoulders and elbows_far_apart and wrists_near
print(f"[SG] dome_check: wrists_above={wrists_above_nose}, elbows_above={elbows_above_shoulders}, " if dome:
f"elbow_d={elbow_distance:.1f}, wrist_d={wrist_distance:.1f}")
if wrists_above_nose and elbows_above_shoulders and elbows_far_apart and wrists_near:
return 'dome' return 'dome'
return 'none' return 'none'