simple works, needs damage and range
commit
4d82dc76da
@ -0,0 +1,7 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.env
|
||||
venv/
|
||||
submodules/
|
||||
@ -0,0 +1,8 @@
|
||||
[submodule "submodules/robot_controller_base"]
|
||||
path = submodules/robot_controller_base
|
||||
url = https://git.robofob.ru/sirius/robot_controller.git
|
||||
branch = main
|
||||
[submodule "submodules/gesture_detection"]
|
||||
path = submodules/gesture_detection
|
||||
url = https://git.robofob.ru/sirius/gesture_rec.git
|
||||
branch = main
|
||||
@ -0,0 +1,8 @@
|
||||
Для работы установите дополнительные модули и файл `requirements.txt`:
|
||||
|
||||
git submodule add --branch main https://git.robofob.ru/sirius/robot_controller.git submodules/robot_controller_base
|
||||
git submodule add --branch main https://git.robofob.ru/sirius/gesture_rec.git submodules/gesture_detection
|
||||
|
||||
Принудительно обновить:
|
||||
|
||||
git submodule update --init --remote --recursive
|
||||
@ -0,0 +1,55 @@
|
||||
class Config:
|
||||
# ===== Камера =====
|
||||
CAMERA_ID = 0
|
||||
MIRROR_CAMERA = True
|
||||
|
||||
# ===== Преобразование позы в скорости =====
|
||||
ARM_CONTROL = {
|
||||
'linear_arm': 'right',
|
||||
'angular_arm': 'left',
|
||||
'max_speed_linear': 1.0,
|
||||
'max_speed_angular': 1.0,
|
||||
'dead_zone': 0.2,
|
||||
'debug': False
|
||||
}
|
||||
|
||||
# ===== Статические жесты (dome, cross) =====
|
||||
SPECIAL_GESTURE_MODE = 'ml' # или 'ml' 'geometric'
|
||||
ML_GESTURE_MODEL = '/home/ubuntu/sirius/models/rf/special_gestures_rf.pkl'
|
||||
ML_GESTURE_CLASSES = ['dome', 'cross', 'none']
|
||||
|
||||
|
||||
# ===== Динамические жесты =====
|
||||
DYNAMIC_GESTURE = {
|
||||
'enabled': True,
|
||||
'model_path': '/home/ubuntu/sirius/models/lstm/dynamic_model.h5',
|
||||
'classes_path': '/home/ubuntu/sirius/models/lstm/dynamic_model_classes.pkl',
|
||||
'window_size': 14,
|
||||
'threshold': 0.8
|
||||
}
|
||||
|
||||
# ===== Симулятор робота =====
|
||||
MAP_WIDTH = 800
|
||||
MAP_HEIGHT = 600
|
||||
MAP_OBSTACLES = [
|
||||
(200, 150, 100, 200),
|
||||
(500, 300, 150, 50),
|
||||
]
|
||||
START_POS = (100, 100)
|
||||
FINISH_POS = (700, 500)
|
||||
ROBOT_RADIUS = 20
|
||||
ROBOT_IMAGE_PATH = 'robot.png' # или None
|
||||
|
||||
INITIAL_HP = 100
|
||||
INITIAL_CHARGE = 100
|
||||
COLLISION_DAMAGE = 10
|
||||
|
||||
# Конус перед роботом
|
||||
EXTINGUISH_RADIUS = 50
|
||||
EXTINGUISH_ANGLE = 60
|
||||
|
||||
NUM_FIRES = 4
|
||||
FIRE_RADIUS = 15
|
||||
SCORE_PER_FIRE = 10
|
||||
|
||||
ROBOT_MODE = 'simulator' # или 'debug'
|
||||
@ -0,0 +1,126 @@
|
||||
import numpy as np
|
||||
|
||||
class ArmController:
|
||||
def __init__(self, config, mirror=False):
|
||||
self.config = config
|
||||
self.mirror = mirror
|
||||
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)
|
||||
|
||||
def _get_side_indices(self, side):
|
||||
"""
|
||||
Возвращает (shoulder_idx, wrist_idx) для заданной стороны (left/right).
|
||||
При mirror=True интерпретируем сторону как в реальности: левая/правая рука.
|
||||
"""
|
||||
if self.mirror:
|
||||
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)
|
||||
|
||||
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]
|
||||
|
||||
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
|
||||
|
||||
linear_side = self.config['linear_arm']
|
||||
angular_side = self.config['angular_arm']
|
||||
|
||||
lin_rel = self._horizontal_displacement_rel(landmarks, linear_side)
|
||||
ang_rel = self._vertical_displacement_rel(landmarks, angular_side)
|
||||
|
||||
if self.debug:
|
||||
print(f"lin_rel={lin_rel:.3f}, ang_rel={ang_rel:.3f}")
|
||||
|
||||
# Линейная скорость (только вперёд)
|
||||
if lin_rel < self.dead_zone:
|
||||
linear = 0.0
|
||||
else:
|
||||
linear = min(lin_rel, 1.0) * self.config['max_speed_linear']
|
||||
|
||||
# Угловая скорость
|
||||
if abs(ang_rel) < self.dead_zone:
|
||||
angular = 0.0
|
||||
else:
|
||||
ang_rel_clipped = np.clip(ang_rel, -1.0, 1.0)
|
||||
angular = ang_rel_clipped * self.config['max_speed_angular']
|
||||
|
||||
return linear, angular
|
||||
|
||||
@ -0,0 +1,130 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'submodules', 'gesture_detection'))
|
||||
|
||||
import cv2
|
||||
from config import Config
|
||||
from skeleton.mediapipe_detector import MediaPipeDetector
|
||||
from gesture_control.special_gestures import SpecialGestureDetector
|
||||
from ml_gestures_dynamic.predict import DynamicGesturePredictor
|
||||
|
||||
from geom_controll.arm_control import ArmController
|
||||
from robot.simulated_robot import DummySimRobot
|
||||
from robot.debug_robot import DebugRobot
|
||||
|
||||
def main():
|
||||
cfg = Config()
|
||||
detector = MediaPipeDetector()
|
||||
special_detector = SpecialGestureDetector(
|
||||
mode=cfg.SPECIAL_GESTURE_MODE,
|
||||
model_path=cfg.ML_GESTURE_MODEL,
|
||||
class_names=cfg.ML_GESTURE_CLASSES
|
||||
)
|
||||
|
||||
dynamic_predictor = None
|
||||
|
||||
if cfg.DYNAMIC_GESTURE['enabled']:
|
||||
dynamic_predictor = DynamicGesturePredictor(
|
||||
cfg.DYNAMIC_GESTURE['model_path'],
|
||||
cfg.DYNAMIC_GESTURE['classes_path'],
|
||||
cfg.DYNAMIC_GESTURE['window_size'],
|
||||
cfg.DYNAMIC_GESTURE['threshold']
|
||||
)
|
||||
|
||||
arm_control = ArmController(cfg.ARM_CONTROL, mirror=cfg.MIRROR_CAMERA)
|
||||
if cfg.ROBOT_MODE == 'simulator':
|
||||
robot = DummySimRobot(cfg)
|
||||
else:
|
||||
robot = DebugRobot()
|
||||
|
||||
cap = cv2.VideoCapture(cfg.CAMERA_ID)
|
||||
if not cap.isOpened():
|
||||
print("Ошибка: не удалось открыть камеру")
|
||||
sys.exit(1)
|
||||
|
||||
enabled = False
|
||||
# включение управления жестами
|
||||
cross_cooldown = 0
|
||||
CROSS_COOLDOWN_FRAMES = 30
|
||||
last_dynamic = None
|
||||
dynamic_counter = 0
|
||||
dome_processed = False
|
||||
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
break
|
||||
if cfg.MIRROR_CAMERA:
|
||||
frame = cv2.flip(frame, 1)
|
||||
|
||||
result = detector.detect(frame)
|
||||
if result['success']:
|
||||
landmarks = result['landmarks']
|
||||
|
||||
# Статический жест
|
||||
|
||||
# Вкл/выкл управления жестами
|
||||
if special == 'cross' and cross_cooldown == 0:
|
||||
enabled = not enabled
|
||||
cross_cooldown = CROSS_COOLDOWN_FRAMES
|
||||
if cross_cooldown > 0:
|
||||
cross_cooldown -= 1
|
||||
|
||||
# Тушение
|
||||
if special == 'dome' and not dome_processed:
|
||||
robot.send_fire_ext_burst_cmd()
|
||||
dome_processed = True
|
||||
elif special != 'dome':
|
||||
dome_processed = False
|
||||
|
||||
# Скорости из рук
|
||||
linear, angular = arm_control.compute_speeds(landmarks)
|
||||
if enabled:
|
||||
robot.send_speed_cmd(linear, angular)
|
||||
else:
|
||||
robot.send_speed_cmd(0.0, 0.0)
|
||||
|
||||
# Динамический жест
|
||||
dynamic_gesture = None
|
||||
if dynamic_predictor:
|
||||
dynamic_predictor.add_frame(landmarks)
|
||||
dynamic_gesture = dynamic_predictor.predict()
|
||||
if dynamic_gesture:
|
||||
last_dynamic = dynamic_gesture
|
||||
dynamic_counter = 30
|
||||
|
||||
if dynamic_gesture == 'wave_left':
|
||||
robot.reset_position()
|
||||
elif dynamic_gesture == 'wave_right':
|
||||
robot.restart_game()
|
||||
|
||||
# Отрисовка
|
||||
vis = detector.draw_landmarks(frame, result['pose_landmarks'])
|
||||
cv2.putText(vis, f"Move: {'ON' if enabled else 'OFF'}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0) if enabled else (0,0,255), 2)
|
||||
cv2.putText(vis, f"L:{linear:.2f} A:{angular:.2f}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,0), 2)
|
||||
if special != 'none':
|
||||
cv2.putText(vis, f"Special: {special}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,0), 2)
|
||||
if dynamic_counter > 0 and last_dynamic:
|
||||
cv2.putText(vis, f"Dynamic: {last_dynamic}", (10, 120), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,255), 2)
|
||||
dynamic_counter -= 1
|
||||
|
||||
else:
|
||||
vis = frame
|
||||
cv2.putText(vis, "No pose detected", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
|
||||
|
||||
cv2.imshow('Camera', vis)
|
||||
|
||||
if not robot.step():
|
||||
break
|
||||
|
||||
if cv2.waitKey(1) & 0xFF == ord('q'):
|
||||
break
|
||||
|
||||
cap.release()
|
||||
cv2.destroyAllWindows()
|
||||
robot.quit()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
opencv-python
|
||||
mediapipe
|
||||
numpy
|
||||
pygame
|
||||
scikit-learn
|
||||
pandas
|
||||
joblib
|
||||
@ -0,0 +1,12 @@
|
||||
class DebugRobot:
|
||||
def send_speed_cmd(self, linear, angular):
|
||||
print(f"Robot: linear={linear:.2f}, angular={angular:.2f}")
|
||||
|
||||
def step(self):
|
||||
pass
|
||||
|
||||
def quit(self):
|
||||
pass
|
||||
|
||||
def send_fire_ext_burst_cmd(self):
|
||||
print("Robot: Pshhhhh putting out the fire!")
|
||||
@ -0,0 +1 @@
|
||||
Subproject commit 988ce22d01ab7cb25442915d01c1fe53c09b5583
|
||||
@ -0,0 +1 @@
|
||||
Subproject commit 9f65622b633900e07ee1f9dbe9e33a5add3167ae
|
||||
Loading…
Reference in New Issue