diff --git a/README.md b/README.md index e0cb2de..1358799 100644 --- a/README.md +++ b/README.md @@ -1,168 +1,292 @@ -## О проекте -Проект позволяет управлять роботом (в симуляторе) с помощью жестов рук, распознаваемых через камеру. Используется MediaPipe для детекции скелета. +# Распознавание жестов на основе скелета +Проект позволяет распознавать статические и динамические жесты человека по данным скелета, полученным с помощью MediaPipe (или OAK‑камеры). Реализованы: +- детекция скелета (33 ключевые точки); +- геометрическое и ML‑распознавание специальных жестов (например, «домик», «крест»); +- сбор и разметка собственных наборов данных; +- обучение моделей (MLP, Random Forest, Logistic Regression – для статики; LSTM – для динамики); +- оценка качества моделей. + +**Проект не включает симулятор робота или управление движением** – это отдельный репозиторий. Здесь представлена только библиотека распознавания жестов. ## Структура проекта ``` -gesture_robot/ -├── camera # Методы для захвата видеоряда с разных источников -│ ├── base_camera.py # Базовый класс -│ ├── factory.py # Конвейер создания камеры -│ ├── oak_camera.py # Реализация для oak -│ └── web_camera.py # Реализация для web -├── skeleton/ # Детекция скелета (MediaPipe) -│ └── mediapipe_detector.py -├── gesture_control/ # Управление жестами -│ └── special_gestures.py # Специальные жесты по геометрии -├── ml_gestures/ # ML для специальных жестов -│ ├── feature_extractor.py -│ ├── predict.py -│ └── train.py -├── ml_gestures_dynamic/ # ML для динамических жестов +gesture_rec/ +├── camera # Методы для захвата видеоряда с разных источников +│ ├── base_camera.py # Базовый класс +│ ├── factory.py # Конвейер создания камеры +│ ├── oak_camera.py # Реализация для oak +│ └── web_camera.py # Реализация для web +├── skeleton/ # Детекция скелета +│ ├── oak_pose_detector.py # Детектор на oak (efficienthrnet) +│ └── mediapipe_detector.py # MediaPipe +├── gesture_control/ # Распознавание статичных жестов +│ └── special_gestures.py # Специальные жесты (геометрия или ML) +├── ml_gestures/ # ML для статичных жестов +│ ├── feature_extractor.py # Нормализация landmarks +│ ├── predict.py # класс-обёртка для предсказания +│ └── train.py # обучение классификаторов +├── ml_gestures_dynamic/ # ML для динамических жестов │ ├── feature_extractor.py -│ ├── sequence_utils.py -│ ├── predict.py -│ ├── evaluate.py -│ └── train.py -└── utils/ # Вспомогательные скрипты - ├── annotate.py # Разметка изображений - ├── capture_photo.py # Съёмка фото с камеры - └── record_dynamic.py # Разметка видеопоследовательности +│ ├── sequence_utils.py # Загрузка данных +│ ├── predict.py # Класс для предсказания (с буфером) +│ ├── evaluate.py # Оценка LSTM +│ └── train.py # Обучение LSTM +├── utils/ # Вспомогательные скрипты +│ ├── annotate.py # Разметка изображений +│ ├── capture_photo.py # Съёмка фото с камеры +│ └── record_dynamic.py # Разметка видеопоследовательности +└── submodules # Внешние зависимости (git submodule) + └── OAK-HumanPoseEstimation # Репозиторий для работы с OAK ``` -## Подготовка и запуск +## Установка и настройка +### 1. Клонирование репозитория +``` +git clone --recursive https://git.robofob.ru/sirius/gesture_rec.git +cd gesture_rec +``` +Если вдруг уже склонировали без `--recursive`, выполните подгрузку модулей: +``` +cd gesture_rec +git submodule update --init --recursive +``` +### 2. Создание виртуального окружения +Рекомендуется использовать виртуальное окружение (Python 3.8+): +``` +python3 -m venv venv +source venv/bin/activate +``` -### Установка зависимостей +### 3. Установка зависимостей pip install -r requirements.txt + +### 4. Проверка работы камеры +Для веб-камеры достаточно, чтобы она была доступна по индексу (встроенная 0). Для OAK-D потребуется подключить устройство и установить драйверы. + +Также OAK-D можно использовать, чтобы прям на камере выполнять вычисление скелета, для этого убедитесь, что модуль `OAK-HumanPoseEstimation` загружен - его код используется в `skeleton/oak_pose_detector.py`. + +Затем перейдите в эту подпапку и скачайте модели детектирования скелета: -### Настройка параметров -Все основные параметры вынесены в `config.py`. Основные: -- `CAMERA_ID` - индекс камеры (по умолчанию 0) -- `MIRROR_CAMERA` - зеркальное отображение (True для фронтальной камеры). -- `ARM_CONTROL` - настройки управления жестами (геометрией) управления - - `linear_arm` - рука, отвечающая за изменение линейной скорости - - `angular_arm` - рука, отвечающая за изменение угловой скорости - - `max_speed_linear` - максимальная линейная скорость (по умолчанию 1) - - `max_speed_angular` - максимальная угловая скорость (по умолчанию 1) - - `dead_zone` - мёртвая зона - доля от ширины плеч/высоты торса (по умолчанию 0.2) для предотвращения ложных срабатываний - - `debug` - Режим отладки, вкл/выкл логи (по умолчанию False) -- `SPECIAL_GESTURE_MODE` - способ детекции специальных статических жестов (ml или geometric) -- `ML_GESTURE_MODEL` - путь до весов ml модели статических жестов -- `ML_GESTURE_CLASSES` - список классов детектируемых жестов, + none -- `ROBOT_MODE` - simulator (мини игра проехать роботом мимо препятствий) или dummy (простой модуль, пишуший отправленную команду, для первичной отладки) -- `ROBOT_IMAGE_PATH` - путь до картинки робота, который будет ездить в симуляции (если не указать будет треугольник просто) -- параметры для симулятора карты: - - `MAP_WIDTH` - ширина карты - - `MAP_HEIGHT` - высота карты - - `MAP_OBSTACLES` - лист препятствий в формате (x, y, width, height) - - `START_POS` - стартовая позиция робота - - `FINISH_POS` - позиция финиша - - `ROBOT_RADIUS` - радиус робота - -### Примеры запуска -#### Запуск с геометрическим распознаванием -1. Настройте `config.py`: - - `MIRROR_CAMERA = True` – для фронтальной камеры. - - `SPECIAL_GESTURE_MODE = 'geometric'`. -2. Запустите основной скрипт: -``` -python3 main.py -``` -3. Запустится симулятор и изображение с камеры - -#### Запуск с ML-распознаванием статических жестов -1. Соберите датасет с нужными изображениями фото. Можете заснять собственные через `utils/capture_photo.py`: -``` -python3 -m utils/capture_photo.py --dir data/raw ``` -Нажмите `s`, подождите 3 секунды, фото сохранится в папку `data/raw`. Нажмите `q` чтобы закончить. +cd submodules/OAK-HumanPoseEstimation/models/ +wget "https://drive.google.com/uc?export=download&id=1AUszSCMSc5dCATnZn1jK8PzMMyLQ__5M" -O models.zip +unzip models.zip +``` + +## Работа с проектом + +Все скрипты (обучение, сбор данных, оценка) рассчитаны на запуск из корневой директории проекта. + +### 0. Распознавание скелета +#### 0.1. MediaPipe + +Основной класс – `skeleton.mediapipe_detector.MediaPipeDetector`. Этот детектор должен принимать на вход изображение: -2. Разметьте фото с помощью аннотатора: ``` -python3 -m utils/annotate.py --folder data/raw --classes dome,cross,none --output data.csv +from skeleton.mediapipe_detector import MediaPipeDetector + +detector = MediaPipeDetector(model_complexity=1, min_detection_confidence=0.5) +result = detector.detect(frame_bgr) +if result['success']: + landmarks = result['landmarks'] # (33,4) – x, y, z, visibility + # для отрисовки: + vis = detector.draw_landmarks(frame, result['pose_landmarks']) ``` -Для каждого фото нажмите цифру, соответствующую жесту (1–dome, 2–cross, 3–none), или n для пропуска. При выходе данные сохранятся в `data.csv`. -3. Обучите модель +Параметры: +- `model_complexity` – 0 (лёгкая), 1 (средняя) или 2 (тяжёлая). Влияет на точность и FPS. +- `min_detection_confidence` – порог уверенности для детекции (0.5–0.9). + +**Важно**: этой модели нужно получать само изображение, поэтому тип камеры для неё не важен, работает со всеми. + +#### 0.2. На борту OAK + +Для экономии ресурсов, можно детектировать скелет сразу с камеры. Такой метод работает **только с OAK камерой, и недоступен для web**. Класс - `skeleton.oak_pose_detector.OakPoseDetector`. Хоть функционал похож с детектором MediaPipe - основное отличие в том, что нельзя подать на вход изображение. + ``` -python3 -m ml_gestures/train.py --csv data.csv --model ml_gestures/models/special_model.pkl --type mlp --balance --target_classes dome,cross +from skeleton.oak_pose_detector import OakPoseDetector + +detector = OakPoseDetector( + model_type='efficienthrnet1', + detection_threshold=0.2, + shaves=6 + ) +frame, landmarks = detector.get_frame_and_pose() +# для отрисовки: +vis = detector.draw_landmarks(frame, landmarks) ``` -Параметр `balance` уменьшит целевые классы (в параметре `target_classes`) до размера наименьшего из них, чтобы избежать перекоса. Класс none остаётся неизменным. -4. Оценка модели -После обучения модель сохраняется, и создаётся отчёт `special_model_report.json` с метриками (`accuracy`, `precision`, `recall`, `f1`, `confusion matrix`). Для повторной оценки используйте: +Подробнее о параметрах модели в [репозитории](https://github.com/kschlegel/OAK-HumanPoseEstimation.git). + +### 1. Геометрическое распознавание жестов +Класс `SpecialGestureDetector` в режиме `mode='geometric'` анализирует координаты скелета и применяет набор правил. + +На текущий момент геометрически распознаются два жеста: +- "домик" +- "крест" + +Все пороги (уверенность `min_conf`, коэффициенты) заданы внутри `_geometric_predict` и могут быть подстроены под ваши условия. Чтобы распознавать собственный жест, отредактируйте метод `_geometric_predict` в файле `gesture_control/special_gestures.py`. + +#### 1.1. Порядок действий: +1. Определите, какие ключевые точки MediaPipe участвуют в жесте (список индексов см. в коде или в документации MediaPipe). +2. Напишите условие, используя координаты `(x, y)` нужных точек. Например, жест «рука в сторону»: `left_wrist[0] > left_shoulder[0] + width` и `right_wrist[0] < right_shoulder[0] - width`. +3. Вставьте проверку до финального return 'none', чтобы при совпадении условий возвращать строку с именем вашего жеста. + +Пример добавления жеста "руки в стороны": ``` -python3 -m ml_gestures/evaluate.py --csv data.csv --model ml_gestures/models/special_model.pkl --test_size 0.2 +arms_out = (l_wr[0] > l_sh[0] + shoulder_width*0.5 and + r_wr[0] < r_sh[0] - shoulder_width*0.5) +if arms_out: + return 'arms_out' ``` -5. Подключите модель в `config.py` +**Важно:** геометрический режим не требует обучения, но чувствителен к позе и ракурсу. Для более сложных жестов рекомендуем использовать ML. + +#### 1.2. Использование в коде: ``` -SPECIAL_GESTURE_MODE = 'ml' -ML_GESTURE_MODEL = 'ml_gestures/models/special_model.pkl' -ML_GESTURE_CLASSES = ['dome', 'cross', 'none'] +from gesture_control.special_gestures import SpecialGestureDetector +detector = SpecialGestureDetector(mode='geometric') +gesture = detector.predict(landmarks) # вернет none или название жеста ``` -6. Запустите основной скрипт: +### 2. ML-распознаванием статических жестов +Распознавание отдельных кадров с помощью обученного классификатора. +#### 2.1. Сбор датасета +Создай папку для изображений. Можешь поместить туда фотографии жестов из интернета. Либо же можешь самостоятельно снять изображения с камеры: +Скрипт `utils/capture_photo.py` сохраняет изображения с камеры. ``` -python3 main.py +python3 utils/capture_photo.py --dir data/raw --camera_type web ``` -7. Запустится симулятор и изображение с камеры +Параметры запуска: +- `--dir` - папка для сохранения (по умолчанию `captured` в корне проекта). +- `--camera` - ID веб-камеры (по умолчанию 0). +- `--camera_type` - `web` или `oak` (по умолчанию `web`). +- `--no-mirror` - отключить зеркалирование (по умолчанию включено). + +Управление: `s` – начать обратный отсчёт (3 сек) и сохранить фото, `q` – выход. +Нажмите `s`, подождите 3 секунды, фото сохранится в папку `data/raw`. Нажмите `q` чтобы закончить. -#### Запуск с ML-распознаванием динамических жестов -1. Соберите датасетс нужными последовательностями жестов. +#### 2.2. Разметка +Скрипт `utils/annotate.py` показывает каждое фото и позволяет назначить класс: ``` -python -m utils.record_dynamic --label wave_right --output dynamic_data.csv --duration 2.0 +python3 utils/annotate.py --folder data/raw --classes dome,cross,none --output data.csv ``` -Нажмите `space`, подождите 3 секунды, последовательность точек с меткой сохранится в файл `dynamic_data.csv`. Нажмите `q` чтобы закончить. -2. Обучите модель +Параметры запуска: +- `--folder` – папка с изображениями. +- `--classes` – список классов через запятую (порядок соответствует цифрам 1,2,3…). +- `--output` – выходной CSV (по умолчанию gesture_data.csv). +- `--max_display_size` – размер окна для предпросмотра (ширина,высота, по умолчанию 800,600). + +Управление: для каждого фото нажмите цифру, соответствующую жесту (1–dome, 2–cross, 3–none), или `n` для пропуска или `q` – выйти. При выходе данные сохранятся в `gesture_data.csv`. CSV с колонками `class`, `f0…f98` (99 нормализованных координат). + +#### 2.3. Обучение модели ``` -python -m ml_gestures_dynamic.train --data dynamic_data.csv --model dynamic_model.h5 --max_len 14 --epochs 50 --test_size 0.2 +python3 ml_gestures/train.py --csv data.csv --model ml_gestures/models/special_model.pkl --type mlp --balance --target_classes dome,cross ``` -Укажите путь до вашего файла, укажите путь, куда сохранить модель, а также укажите длину последовательности кадров (зависит от вашей камеры и железа, будет выводится при сборе данных) -3. Оценка модели -После обучения модель сохраняется, и создаётся отчёт `dynamic_model_report.json` с метриками (`accuracy`, `precision`, `recall`, `f1`, `confusion matrix`). Для повторной оценки используйте: +Параметры запуска: +- `--csv` – путь к размеченному CSV (обязательно). +- `--model` – путь для сохранения модели (обязательно, расширение .pkl). +- `--type` – тип модели: linear (Logistic Regression), mlp (MLPClassifier) или rf (RandomForestClassifier). По умолчанию mlp. +- `--test_size` – доля тестовой выборки (по умолчанию 0.2). +- `--random_state` – seed для воспроизводимости (по умолчанию 42). +- `--balance` – если указан, балансирует целевые классы (указанные в --target_classes) до минимального размера среди них. Класс none не трогается. +- `--target_classes` – список классов для балансировки через запятую (по умолчанию все классы, кроме none). + +После обучения сохраняются модель и отчёт `special_model_report.json` с метриками (accuracy, precision/recall/f1 по классам, матрица ошибок). + +#### 2.4. Оценка модели +После обучения модель сохраняется, и создаётся отчёт `special_model_report.json` с метриками (`accuracy`, `precision`, `recall`, `f1`, `confusion matrix`). Для повторной оценки используйте: ``` -python -m ml_gestures_dynamic.evaluate --data dynamic_data.csv --model dynamic_model.h5 --max_len 14 --test_size 0.2 +python3 ml_gestures/evaluate.py --csv data.csv --model ml_gestures/models/special_model.pkl --test_size 0.2 ``` -5. Подключить модель в `config.py` +- `--csv` – путь к размеченному CSV (обязательно). +- `--model` – путь к сохраненной модели (обязательно, расширение .pkl). +- `--test_size` – доля тестовой выборки (по умолчанию 0.2). +- `--random_state` – seed для воспроизводимости (по умолчанию 42). + +Печатает результаты тестирования в консоль. + +#### 2.5. Использование обученной модели ``` -DYNAMIC_GESTURE = { - 'enabled': True, # включить/выключить - 'model_path': 'ml_gestures/models/dynamic_model.h5', - 'classes_path': 'ml_gestures//odels/dynamic_model_classes.pkl', - 'window_size': 14, # длина буфера - 'threshold': 0.7, # порог уверенности - 'actions': { - 'wave_left': 'reset', # при жесте wave_left – перезапуск симулятора - 'wave_right': 'restart', # при wave_right – рестарт - } +from ml_gestures.predict import MLGesturePredictor + +predictor = MLGesturePredictor('model.pkl', class_names=['dome','cross','none']) +gesture = predictor.predict(landmarks) # возвращает строку с классом ``` -### Управление в симуляторе -Симулятор – поле с препятствиями, стартом и финишем. Робот движется согласно командам. При столкновении – игра заканчивается (перезапуск по `r`). Закрытие окна игры или нажатие `q` в окне камеры – выход. -1. Управление скоростями - - Линейная скорость (вперёд) – горизонтальное положение правой руки (рука вдоль тела – 0, вытянута в сторону – максимум). - - Угловая скорость – вертикальное положение левой руки (рука на уровне плеча – 0, вверх – поворот вправо, вниз – поворот влево). - -![Схема управления скоростями](images/arm_control.png) +### 3. ML-распознавание динамических жестов +Распознавание жестов по последовательности кадров с помощью LSTM. +#### 3.1. Сбор датасета +Скрипт `utils/record_dynamic.py` записывает серию кадров (скелет) в течение заданной длительности. +``` +python3 utils/record_dynamic --label wave_right --output dynamic_data.csv --duration 2.0 +``` +Параметры запуска: +- `--label` – название жеста (обязательно). +- `--output` – CSV-файл для сохранения (по умолчанию `dynamic_data.csv`). +- `--duration` – длительность записи в секундах (по умолчанию 3.0). +- `--camera_type` – web или oak (по умолчанию web). +- `--no-mirror` – отключить зеркалирование -2. Специальные жесты. Режим распознавания может быть геометрическим (по правилам) или обучаемым (ML-модель). - - Домик – обе руки над головой (включает управление). - - Крест – предплечья скрещены на груди (выключает управление). - -![Схема управления жестами](images/gesture_control.png) +Управление: нажмите `space`, через 3 секунды начнется запись, последовательность точек с меткой сохранится в файл `dynamic_data.csv`. В CSV сохраняются колонки: `label`, `sequence_id`, `frame_idx`, `f0…f98`. Нажмите `q` чтобы закончить. + +**Важно**: при записи в консоль выводится число кадров последовательности – используйте его как ориентир для `--max_len` в обучении (можно округлить вверх). +#### 3.2. Обучение LSTM +``` +python3 ml_gestures_dynamic/train --data dynamic_data.csv --model dynamic_model.h5 --max_len 14 --epochs 50 --test_size 0.2 +``` +Параметры запуска: +- `--data` – путь к CSV-файлу или папке с несколькими CSV (все будут объединены). +- `--model` – путь для сохранения модели (.h5). +- `--max_len` – длина последовательности (количество кадров). Должен совпадать с длиной, использованной при записи. Если последовательности короче, они дополняются нулями; если длиннее – обрезаются. +- `--lstm_units` – число нейронов в LSTM (по умолчанию 64). +- `--epochs` – количество эпох (по умолчанию 50). +- `--batch_size` – размер батча (по умолчанию 16). +- `--test_size` – доля тестовой выборки (по умолчанию 0.2). + +После обучения сохраняются: модель (`.h5`), файл с классами (`_classes.pkl`) и отчёт (`_report.json`) с метриками. + +#### 3.3. Оценка модели +После обучения модель сохраняется, и создаётся отчёт `dynamic_model_report.json` с метриками (`accuracy`, `precision`, `recall`, `f1`, `confusion matrix`). Для повторной оценки используйте: +``` +python3 ml_gestures_dynamic/evaluate --data dynamic_data.csv --model dynamic_model.h5 --max_len 14 --test_size 0.2 +``` +Параметры запуска: +- `--data` – путь к CSV-файлу или папке с несколькими CSV (все будут объединены). +- `--model` – путь для сохранения модели (.h5). +- `--max_len` – длина последовательности (количество кадров). Должен совпадать с длиной, использованной при записи. Если последовательности короче, они дополняются нулями; если длиннее – обрезаются. +- `--test_size` – доля тестовой выборки (по умолчанию 0.2). + +#### 3.4. Использование в коде +Класс `DynamicGesturePredictor` накапливает кадры в буфере и выдаёт предсказание, когда накоплено достаточно данных. +``` +from ml_gestures_dynamic.predict import DynamicGesturePredictor + +predictor = DynamicGesturePredictor( + model_path='dynamic_model.h5', + classes_path='dynamic_model_classes.pkl', + window_size=14, # должно совпадать с max_len при обучении + threshold=0.5 # минимальная уверенность для выдачи класса +) + +# В цикле обработки кадров: +predictor.add_frame(landmarks) # landmarks (33,4) или None +gesture = predictor.predict() # возвращает класс или None, если недостаточно данных/уверенность ниже порога +``` ## Возможные проблемы -1. Камера не работает - проверьте `CAMERA_ID` в `config.py` (обычно 0 или 1). -2. Скелет не определяется – убедитесь, что человек стоит на расстоянии 1–2 метра, плечи в кадре. Можете также повысить сложность модели определения скелета (`skeleton/mediapipe_detector.py`), но скажется на производительности. -3. Ложные срабатывания жестов – в геометрическом режиме увеличьте `min_conf` в `special_gestures.py` или переключитесь на ML-режим с большим количеством примеров -4. Робот не движется – проверьте, включено ли управление (жест "домик") и видимость рук. +1. Камера не работает - проверьте права доступа, индекс камеры (--camera). Для OAK убедитесь, что устройство подключено и depthai установлен. +2. Скелет не определяется – стойте на расстоянии 1–2 м, плечи в кадре. Снизьте min_detection_confidence или увеличьте model_complexity в MediaPipeDetector. +3. Ложные срабатывания жестов – в геометрическом режиме увеличьте `min_conf` в `special_gestures.py` или переключитесь на ML-режим с большим количеством примеров. +4. Не хватает памяти для LSTM – уменьшите --max_len или --batch_size, используйте меньше --lstm_units. +5. Ошибка импорта при запуске скриптов – все исполняемые скрипты автоматически добавляют корень проекта в sys.path, поэтому запускайте из корня проекта. -## Датасеты +## Датасеты (на точках MediaPipe) | Название | Описание | Ссылка | Количество примеров | Классы | |----------|----------|--------|---------------------|--------| | **special_gestures_v1** | Набор фотографий для распознавания специальных жестов (домик, крест, none). Собран с помощью `capture_photo.py` и стоковых изображений, размечен через `annotate.py`. | [Скачать](https://disk.yandex.ru/d/F25kMjrmZ8xwRA) | 77 (после балансировки, 13 на класс dome/cross, 51 none) | `dome`, `cross`, `none` | | **dynamic_data_v1** | csv файл с последовательностью точек скелета для 14 кадров | [Скачать](https://disk.yandex.ru/d/hnm6apfuxJsEyw) | 81 (по 20 на махи правой и левой рукой, 61 на none) | `none`, `wave_left`, `wave_right` | -## Модели +## Модели (на точках MediaPipe) | Название | Тип | Архитектура / параметры | Вход | Выход | Точность (test) | Precision / Recall (по классам) | Ссылка | Распознаваемые жесты | |----------|-----|-------------------------|------|-------|-----------------|--------------------------------|--------|----------------------| | **special_gestures_mlp** | MLP (scikit-learn) | Скрытые слои: (64, 32), активация ReLU, Adam, early stopping, 500 эпох | 99 нормализованных координат скелета (33 точки × 3) | 3 класса (dome, cross, none) | **0.56** | **dome**: P=0.00, R=0.00
**cross**: P=0.00, R=0.00
**none**: P=0.60, R=0.90 | [Скачать](https://disk.yandex.ru/d/I1WyAfN3PJM9fw) | `dome` (руки над головой домиком)
`cross` (предплечья скрещены на груди)
`none` (остальные) |