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 _get_side_indices(self, side):
def compute_speeds(self, landmarks, frame_shape=None): """
if landmarks is None: Возвращает (shoulder_idx, wrist_idx) для заданной стороны (left/right).
return 0.0, 0.0 При mirror=True интерпретируем сторону как в реальности: левая/правая рука.
"""
# 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
if self.mirror: if self.mirror:
angular = -angular if side == 'left':
s_idx = 12
linear = _clip_unit(_apply_dead_zone(linear, self.dead_zone)) w_idx = 16
angular = _clip_unit(_apply_dead_zone(angular, self.dead_zone)) else:
s_idx = 11
if self.debug: w_idx = 15
print(f"[M1] L:{linear:.2f} A:{angular:.2f}") else:
return linear, angular if side == 'left':
s_idx = 11
def draw_overlay(self, frame, landmarks=None): w_idx = 15
if frame is None: else:
return frame s_idx = 12
h, w = frame.shape[:2] w_idx = 16
# Draw center cross (screen center approximation) return s_idx, w_idx
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) def _get_shoulder_width(self, landmarks):
# Draw right wrist """Ширина плеч для нормировки горизонтальных смещений."""
if landmarks is not None and landmarks[16][3] > 0.5: left = landmarks[11][:2]
x, y = int(landmarks[16][0]), int(landmarks[16][1]) right = landmarks[12][:2]
cv2.circle(frame, (x, y), 8, (0, 255, 255), -1) width = np.linalg.norm(right - left)
return frame if width < 50 or width > 300:
return None
return width
class ArmControllerMethod2:
""" def _get_torso_height(self, landmarks):
Method 2: Two-hand blended control. """Высота торса для нормировки вертикальных смещений."""
- Linear: average vertical offset of both wrists from shoulder center (normalized by torso height) left_shoulder = landmarks[11][:2]
- Angular: horizontal balance of wrists around shoulder center (normalized by shoulder width) right_shoulder = landmarks[12][:2]
""" left_hip = landmarks[23][:2]
def __init__(self, config, mirror=False): right_hip = landmarks[24][:2]
self.config = config shoulder_center = (left_shoulder + right_shoulder) / 2
self.mirror = mirror hip_center = (left_hip + right_hip) / 2
self.dead_zone = config.get('dead_zone', 0.1) height = np.linalg.norm(shoulder_center - hip_center)
self.debug = config.get('debug', False) if height < 50:
self.min_conf = config.get('min_conf', 0.5) return None
return height
def compute_speeds(self, landmarks, frame_shape=None):
if landmarks is None: def _horizontal_displacement_rel(self, landmarks, side):
return 0.0, 0.0 """
Нормированное горизонтальное смещение запястья относительно плеча.
# Require: shoulders + both wrists Сторона `side` это реальная сторона руки
need = [11, 12, 15, 16] """
if any(landmarks[i][3] < self.min_conf for i in need): s_idx, w_idx = self._get_side_indices(side)
return 0.0, 0.0
if landmarks[s_idx][3] < 0.5 or landmarks[w_idx][3] < 0.5:
shoulder_center, shoulder_width, torso_height, ok = _robust_metrics(landmarks, self.min_conf) return 0.0
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
l_wr = landmarks[15][:2] linear_side = self.config['linear_arm']
r_wr = landmarks[16][:2] angular_side = self.config['angular_arm']
# 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: lin_rel = self._horizontal_displacement_rel(landmarks, linear_side)
angular = -angular ang_rel = self._vertical_displacement_rel(landmarks, angular_side)
linear = _clip_unit(_apply_dead_zone(linear, self.dead_zone))
angular = _clip_unit(_apply_dead_zone(angular, self.dead_zone))
if self.debug: if self.debug:
print(f"[M2] 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 [(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: if lin_rel < self.dead_zone:
l_wr = landmarks[15][:2] linear = 0.0
r_wr = landmarks[16][:2]
angular = ((r_wr[0] + l_wr[0]) - 2 * shoulder_center[0]) / shoulder_width
else: else:
angular = 0.0 linear = min(lin_rel, 1.0) * self.config['max_speed_linear']
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: if abs(ang_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
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 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: 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,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]
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 torso_height = hip_center_y - shoulder_center_y
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) 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):
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
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