24 KiB
Распознавание жестов на основе скелета
Проект позволяет распознавать статические и динамические жесты человека по данным скелета, полученным с помощью 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. Создание виртуального окружения
Рекомендуется использовать виртуальное окружение (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.
Затем перейдите в эту подпапку и скачайте модели детектирования скелета:
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)
Подробнее о параметрах модели в репозитории.
1. Геометрическое распознавание жестов
Класс SpecialGestureDetector в режиме mode='geometric' анализирует координаты скелета и применяет набор правил.
На текущий момент геометрически распознаются два жеста:
- "домик"
- "крест"
Все пороги (уверенность min_conf, коэффициенты) заданы внутри _geometric_predict и могут быть подстроены под ваши условия. Чтобы распознавать собственный жест, отредактируйте метод _geometric_predict в файле gesture_control/special_gestures.py.
1.1. Порядок действий:
- Определите, какие ключевые точки MediaPipe участвуют в жесте (список индексов см. в коде или в документации MediaPipe).
- Напишите условие, используя координаты
(x, y)нужных точек. Например, жест «рука в сторону»:left_wrist[0] > left_shoulder[0] + widthиright_wrist[0] < right_shoulder[0] - width. - Вставьте проверку до финального 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, если недостаточно данных/уверенность ниже порога
Возможные проблемы
- Камера не работает - проверьте права доступа, индекс камеры (--camera). Для OAK убедитесь, что устройство подключено и depthai установлен.
- Скелет не определяется – стойте на расстоянии 1–2 м, плечи в кадре. Снизьте min_detection_confidence или увеличьте model_complexity в MediaPipeDetector.
- Ложные срабатывания жестов – в геометрическом режиме увеличьте
min_confвspecial_gestures.pyили переключитесь на ML-режим с большим количеством примеров. - Не хватает памяти для LSTM – уменьшите --max_len или --batch_size, используйте меньше --lstm_units.
- Ошибка импорта при запуске скриптов – все исполняемые скрипты автоматически добавляют корень проекта в sys.path, поэтому запускайте из корня проекта.
Датасеты (на точках MediaPipe)
| Название | Описание | Ссылка | Количество примеров | Классы |
|---|---|---|---|---|
| special_gestures_v1 | Набор фотографий для распознавания специальных жестов (домик, крест, none). Собран с помощью capture_photo.py и стоковых изображений, размечен через annotate.py. |
Скачать | 77 (после балансировки, 13 на класс dome/cross, 51 none) | dome, cross, none |
| dynamic_data_v1 | csv файл с последовательностью точек скелета для 14 кадров | Скачать | 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 |
Скачать | 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 |
Скачать | 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 |
Скачать | 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 (по классам) | Скачать | wave_left (махание левой рукой) wave_right (махание правой рукой) none (отсутствие жеста) |