# Распознавание жестов на основе скелета Проект позволяет распознавать статические и динамические жесты человека по данным скелета, полученным с помощью MediaPipe (или OAK‑камеры). Реализованы: - детекция скелета (33 ключевые точки); - геометрическое и ML‑распознавание специальных жестов (например, «домик», «крест»); - сбор и разметка собственных наборов данных; - обучение моделей (MLP, Random Forest, Logistic Regression – для статики; LSTM – для динамики); - оценка качества моделей. **Проект не включает симулятор робота или управление движением** – это отдельный репозиторий. Здесь представлена только библиотека распознавания жестов. ## Структура проекта ``` 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 # Оценка 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. Создание виртуального окружения Рекомендуется использовать виртуальное окружение: ``` python3.11 -m venv venv source venv/bin/activate ``` ### 3. Установка зависимостей pip install --upgrade pip pip install numpy==1.24.3 pip install pandas==2.0.3 pip install opencv-python==4.12.0.88 pip install opencv-python-headless==4.12.0.88 pip install matplotlib==3.7.5 pip install protobuf==3.20.3 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install --no-deps mediapipe==0.10.11 pip install attrs pip install scikit-learn==1.3.2 pip install joblib==1.4.2 pip install depthai==2.28.0 pip install --no-deps tensorflow==2.13.1 pip install \ absl-py \ astunparse \ flatbuffers \ gast \ google-pasta \ grpcio \ h5py \ keras==2.13.1 \ libclang \ opt-einsum \ tensorboard==2.13.0 \ tensorflow-estimator==2.13.0 \ termcolor \ wrapt \ requests pip install numpy==1.24.3 pip install scipy==1.11.0 ### 4. Проверка работы камеры Для веб-камеры достаточно, чтобы она была доступна по индексу (встроенная 0). Для OAK-D потребуется подключить устройство и установить права доступа (сделать это надо один раз): 1. Подключите камеру по usb, если сразу запустите скрипт, то вылетит с ошибкой `No available devices`. 2. Выполните команду: ``` echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="03e7", MODE="0666"' | sudo tee /etc/udev/rules.d/80-movidius.rules ``` 3. Затем: ``` sudo udevadm control --reload-rules && sudo udevadm trigger ``` 4. После этого отключите камеру от USB и подключите снова. Теперь камера должна быть доступна. Также OAK-D можно использовать, чтобы прям на камере выполнять вычисление скелета, для этого убедитесь, что модуль `OAK-HumanPoseEstimation` загружен - его код используется в `skeleton/oak_pose_detector.py`. Затем перейдите в эту подпапку и скачайте модели детектирования скелета: ``` 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`. Этот детектор должен принимать на вход изображение: ``` 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']) ``` Параметры: - `model_complexity` – 0 (лёгкая), 1 (средняя) или 2 (тяжёлая). Влияет на точность и FPS. - `min_detection_confidence` – порог уверенности для детекции (0.5–0.9). **Важно**: этой модели нужно получать само изображение, поэтому тип камеры для неё не важен, работает со всеми. #### 0.2. На борту OAK Для экономии ресурсов, можно детектировать скелет сразу с камеры. Такой метод работает **только с OAK камерой, и недоступен для web**. Класс - `skeleton.oak_pose_detector.OakPoseDetector`. Хоть функционал похож с детектором MediaPipe - основное отличие в том, что нельзя подать на вход изображение. ``` 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) ``` Подробнее о параметрах модели в [репозитории](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', чтобы при совпадении условий возвращать строку с именем вашего жеста. Пример добавления жеста "руки в стороны": ``` 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' ``` **Важно:** геометрический режим не требует обучения, но чувствителен к позе и ракурсу. Для более сложных жестов рекомендуем использовать ML. #### 1.2. Использование в коде: ``` from gesture_control.special_gestures import SpecialGestureDetector detector = SpecialGestureDetector(mode='geometric') gesture = detector.predict(landmarks) # вернет none или название жеста ``` ### 2. ML-распознаванием статичных жестов Распознавание отдельных кадров с помощью обученного классификатора. #### 2.1. Сбор датасета Создай папку для изображений. Можешь поместить туда фотографии жестов из интернета. Либо же можешь самостоятельно снять изображения с камеры: Скрипт `utils/capture_photo.py` сохраняет изображения с камеры. ``` python3 utils/capture_photo.py --dir data/raw --camera_type web ``` Параметры запуска: - `--dir` - папка для сохранения (по умолчанию `captured` в корне проекта). - `--camera` - ID веб-камеры (по умолчанию 0). - `--camera_type` - `web` или `oak` (по умолчанию `web`). - `--no-mirror` - отключить зеркалирование (по умолчанию включено). Управление: `s` – начать обратный отсчёт (3 сек) и сохранить фото, `q` – выход. Нажмите `s`, подождите 3 секунды, фото сохранится в папку `data/raw`. Нажмите `q` чтобы закончить. #### 2.2. Разметка Скрипт `utils/annotate.py` показывает каждое фото с распознанным скелетом и позволяет назначить класс: ``` python3 utils/annotate.py --folder data/raw --classes dome,cross,none --output data.csv ``` Параметры запуска: - `--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. Обучение модели ``` python3 ml_gestures/train.py --csv data.csv --model ml_gestures/models/special_model.pkl --type mlp --balance --target_classes dome,cross ``` Параметры запуска: - `--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`). Для повторной оценки используйте: ``` python3 ml_gestures/evaluate.py --csv data.csv --model ml_gestures/models/special_model.pkl --test_size 0.2 ``` - `--csv` – путь к размеченному CSV (обязательно). - `--model` – путь к сохраненной модели (обязательно, расширение .pkl). - `--test_size` – доля тестовой выборки (по умолчанию 0.2). - `--random_state` – seed для воспроизводимости (по умолчанию 42). Печатает результаты тестирования в консоль. #### 2.5. Использование обученной модели ``` from ml_gestures.predict import MLGesturePredictor predictor = MLGesturePredictor('model.pkl', class_names=['dome','cross','none']) gesture = predictor.predict(landmarks) # возвращает строку с классом ``` ### 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` – отключить зеркалирование Управление: нажмите `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). Для 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` (остальные) | | **special_gestures_rf** | Random Forest | 50 деревьев, max_depth=10, random_state=42 | 99 нормализованных координат скелета (33 точки × 3) | 3 класса (dome, cross, none) | **0.87** | **dome**: P=0.67, R=1.00
**cross**: P=0.750, R=1.00
**none**: P=0.90, R=0.90 | [Скачать](https://disk.yandex.ru/d/fcXxQh4LGjqA1g) |`dome` (руки над головой домиком)
`cross` (предплечья скрещены на груди)
`none` (остальные) | | **special_gestures_lin** | Logistic Regression | max_iter=1000, random_state=42 | 99 нормализованных координат скелета (33 точки × 3) | 3 класса (dome, cross, none) | **0.94** | **dome**: P=1.00, R=1.00
**cross**: P=0.750, R=1.00
**none**: P=1.00, R=0.90 | [Скачать](https://disk.yandex.ru/d/4_XCPEeOGvNX6g) |`dome` (руки над головой домиком)
`cross` (предплечья скрещены на груди)
`none` (остальные) | | **dynamic_model** | LSTM | 2 слоя LSTM по 64 нейрона, Dropout(0.3), Dense(3, softmax), optimizer=Adam, loss=sparse_categorical_crossentropy, epochs=50, batch_size=16 | Последовательность из 14 кадров, каждый кадр – 99 признаков (нормализованные координаты скелета) | 3 класса (none, wave_left, wave_right) | 0.8235 | Precision / Recall (по классам) | [Скачать](https://disk.yandex.ru/d/yDol79iB2ZlS6Q) | `wave_left` (махание левой рукой) `wave_right` (махание правой рукой) `none` (отсутствие жеста) |