From 6b803b405eaf86c7a48d6cc56a3c10d0711641f2 Mon Sep 17 00:00:00 2001 From: moscovskayaliza Date: Thu, 18 Jun 2026 17:25:32 +0300 Subject: [PATCH] started different cameras --- README.md | 18 +++++----------- camera/base_camera.py | 12 +++++++++++ camera/factory.py | 10 +++++++++ camera/oak_camera.py | 32 ++++++++++++++++++++++++++++ camera/web_camera.py | 20 +++++++++++++++++ gesture_control/feature_extractor.py | 26 ---------------------- utils/capture_photo.py | 19 +++++++++-------- utils/record_dynamic.py | 28 ++++++++++++++---------- 8 files changed, 106 insertions(+), 59 deletions(-) create mode 100644 camera/base_camera.py create mode 100644 camera/factory.py create mode 100644 camera/oak_camera.py create mode 100644 camera/web_camera.py delete mode 100644 gesture_control/feature_extractor.py diff --git a/README.md b/README.md index de5ede7..cb3f777 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,10 @@ ## Структура проекта ``` gesture_robot/ -├── main.py # Основной цикл -├── config.py # Конфигурация ├── skeleton/ # Детекция скелета (MediaPipe) │ └── mediapipe_detector.py ├── gesture_control/ # Управление жестами -│ ├── arm_control.py # Скорости по рукам -│ ├── special_gestures.py # Специальные жесты по геометрии -│ └── state.py # Вкл/выкл режима (определение какой жест какую команду триггерит) +│ └── special_gestures.py # Специальные жесты по геометрии ├── ml_gestures/ # ML для специальных жестов │ ├── feature_extractor.py │ ├── predict.py @@ -22,14 +18,10 @@ gesture_robot/ │ ├── predict.py │ ├── evaluate.py │ └── train.py -├── robot/ # Робот (симулятор или заглушка) -│ ├── map_simulator.py -│ └── dummy.py -├── utils/ # Вспомогательные скрипты -│ ├── annotate.py # Разметка изображений -│ ├── capture_photo.py # Съёмка фото с камеры -│ └── record_dynamic.py # Разметка видеопоследовательности -└── requirements.txt +└── utils/ # Вспомогательные скрипты + ├── annotate.py # Разметка изображений + ├── capture_photo.py # Съёмка фото с камеры + └── record_dynamic.py # Разметка видеопоследовательности ``` ## Подготовка и запуск diff --git a/camera/base_camera.py b/camera/base_camera.py new file mode 100644 index 0000000..9ab4435 --- /dev/null +++ b/camera/base_camera.py @@ -0,0 +1,12 @@ +import abc +import numpy as np + +class Camera(abc.ABC): + @abc.abstractmethod + def get_frame(self): + """BGR (numpy array) или None""" + pass + + @abc.abstractmethod + def release(self): + pass diff --git a/camera/factory.py b/camera/factory.py new file mode 100644 index 0000000..9710c51 --- /dev/null +++ b/camera/factory.py @@ -0,0 +1,10 @@ +from .web_camera import WebCamera +from .oak_camera import OakCamera + +def create_camera(camera_type='web', **kwargs): + if camera_type == 'web': + return WebCamera(**kwargs) + elif camera_type == 'oak': + return OakCamera(**kwargs) + else: + raise ValueError(f"Unknown camera type: {camera_type}") diff --git a/camera/oak_camera.py b/camera/oak_camera.py new file mode 100644 index 0000000..0293f86 --- /dev/null +++ b/camera/oak_camera.py @@ -0,0 +1,32 @@ +import depthai as dai +import numpy as np +from camera.base_camera import Camera + +class OakCamera(Camera): + def __init__(self, resolution=(640, 480), fps=30, mirror=False): + self.pipeline = dai.Pipeline() + cam = self.pipeline.create(dai.node.ColorCamera) + cam.setPreviewSize(resolution[0], resolution[1]) + cam.setInterleaved(False) + cam.setFps(fps) + cam.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) + + xout = self.pipeline.create(dai.node.XLinkOut) + xout.setStreamName("preview") + cam.preview.link(xout.input) + + self.device = dai.Device(self.pipeline) + self.q = self.device.getOutputQueue(name="preview", maxSize=4, blocking=False) + self.mirror = mirror + + def get_frame(self): + in_frame = self.q.tryGet() + if in_frame is None: + return None + frame = in_frame.getCvFrame() + if self.mirror: + frame = cv2.flip(frame, 1) + return frame + + def release(self): + self.device.close() diff --git a/camera/web_camera.py b/camera/web_camera.py new file mode 100644 index 0000000..bee5adc --- /dev/null +++ b/camera/web_camera.py @@ -0,0 +1,20 @@ +import cv2 +from camera.base_camera import Camera + +class WebCamera(Camera): + def __init__(self, camera_id=0, mirror=False): + self.cap = cv2.VideoCapture(camera_id) + self.mirror = mirror + if not self.cap.isOpened(): + raise RuntimeError("Cannot open web camera") + + def get_frame(self): + ret, frame = self.cap.read() + if not ret: + return None + if self.mirror: + frame = cv2.flip(frame, 1) + return frame + + def release(self): + self.cap.release() diff --git a/gesture_control/feature_extractor.py b/gesture_control/feature_extractor.py deleted file mode 100644 index 6371bc2..0000000 --- a/gesture_control/feature_extractor.py +++ /dev/null @@ -1,26 +0,0 @@ -import numpy as np - -def normalize_landmarks(landmarks): - """ - Нормализует полный скелет MediaPipe (33 точки) с использованием x,y,z. - Центрирует относительно центра бёдер и масштабирует по росту. - Возвращает плоский вектор (99,) из x,y,z всех точек. - """ - lm = landmarks[:, :3].copy() # (33,3) - - # Центр бёдер (индексы 23 и 24) - hip_center = (lm[23] + lm[24]) / 2 - - # Центр плеч (11 и 12) - shoulder_center = (lm[11] + lm[12]) / 2 - - # Рост – расстояние от бёдер до плеч - height = np.linalg.norm(shoulder_center - hip_center) - if height < 1e-6: - height = 1.0 - - # Центрируем и масштабируем - lm_centered = lm - hip_center - lm_normalized = lm_centered / height - - return lm_normalized.flatten() # (99,) diff --git a/utils/capture_photo.py b/utils/capture_photo.py index 640656e..7fb21ff 100644 --- a/utils/capture_photo.py +++ b/utils/capture_photo.py @@ -3,8 +3,9 @@ import os import sys import time from pathlib import Path +from camera import create_camera -def capture_photo(output_dir='captured', camera_id=0, mirror=True): +def capture_photo(output_dir='captured', camera): """ Простой инструмент для захвата фото с камеры. При нажатии 's' запускается отсчёт 3 секунды, затем делается снимок. @@ -26,12 +27,9 @@ def capture_photo(output_dir='captured', camera_id=0, mirror=True): countdown_start = 0 while True: - ret, frame = cap.read() - if not ret: - break - - if mirror: - frame = cv2.flip(frame, 1) + frame = camera.get_frame() + if frame is None: + continue display = frame.copy() @@ -65,7 +63,7 @@ def capture_photo(output_dir='captured', camera_id=0, mirror=True): countdown = 1 countdown_start = time.time() - cap.release() + camera.release() cv2.destroyAllWindows() print(f"Завершено. Сохранено {capture_count} фото.") @@ -75,5 +73,8 @@ if __name__ == '__main__': parser.add_argument('--dir', default='captured', help='Папка для сохранения') parser.add_argument('--camera', type=int, default=0, help='ID камеры') parser.add_argument('--no-mirror', action='store_true', help='Отключить зеркалирование') + parser.add_argument('--camera_type', default='web', choices=['web', 'oak']) args = parser.parse_args() - capture_photo(output_dir=args.dir, camera_id=args.camera, mirror=not args.no_mirror) + + camera = create_camera(camera_type=args.camera_type, camera_id=args.camera, mirror=not args.no_mirror) + capture_photo(output_dir=args.dir, camera) diff --git a/utils/record_dynamic.py b/utils/record_dynamic.py index 99cc86e..6a5d23b 100644 --- a/utils/record_dynamic.py +++ b/utils/record_dynamic.py @@ -10,16 +10,20 @@ import pandas as pd #sys.path.append(str(Path(__file__).parent.parent)) from skeleton.mediapipe_detector import MediaPipeDetector from ml_gestures_dynamic.feature_extractor import extract_sequence +from camera import create_camera def main(): parser = argparse.ArgumentParser() parser.add_argument('--label', required=True, help='Название жеста (например, wave_left)') parser.add_argument('--output', default='dynamic_data.csv', help='CSV файл для сохранения') parser.add_argument('--camera', type=int, default=0) + parser.add_argument('--camera_type', default='web', choices=['web', 'oak']) parser.add_argument('--duration', type=float, default=3.0, help='Длительность записи (сек)') + parser.add_argument('--no-mirror', action='store_true', help='Отключить зеркалирование') args = parser.parse_args() output_path = Path(args.output) + camera = create_camera(camera_type=args.camera_type, camera_id=args.camera, mirror=not args.no_mirror) # Создаём файл с заголовком, если его ещё нет if not output_path.exists(): @@ -47,10 +51,10 @@ def main(): sequence = [] while True: - ret, frame = cap.read() - if not ret: - break - frame = cv2.flip(frame, 1) # зеркало для удобства + frame = camera.get_frame() + if frame is None: + continue + #frame = cv2.flip(frame, 1) # зеркало для удобства result = detector.detect(frame) if result['success']: landmarks = result['landmarks'] @@ -90,10 +94,10 @@ def main(): # Обратный отсчёт на живом видео for i in range(3, 0, -1): # Получаем свежий кадр для отсчёта - ret, frame = cap.read() - if not ret: - break - frame = cv2.flip(frame, 1) + frame = camera.get_frame() + if frame is None: + continue + #frame = cv2.flip(frame, 1) result = detector.detect(frame) if result['success']: vis = detector.draw_landmarks(frame, result['pose_landmarks']) @@ -104,9 +108,11 @@ def main(): cv2.imshow('Record dynamic gesture', vis) cv2.waitKey(1000) # Показываем "GO!" - ret, frame = cap.read() - if ret: - frame = cv2.flip(frame, 1) + frame = camera.get_frame() + if frame is None: + continue + else: + #frame = cv2.flip(frame, 1) result = detector.detect(frame) if result['success']: vis = detector.draw_landmarks(frame, result['pose_landmarks'])