You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gesture_rec/README.md

384 lines
28 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Распознавание жестов на основе скелета
Проект позволяет распознавать статические и динамические жесты человека по данным скелета, полученным с помощью 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.11:
```
python3.11 -m venv venv
source venv/bin/activate
```
Если у вас не скачен питон этой версии, сначала выполните:
```
sudo apt install python3.11 python3.11-venv
```
Если у нас не устанавливается питон 3.11, то это потому что он отсутствует в официальных репозиториях по умолчанию, надо добавить репозиторий перед скачиванием:
```
sudo apt update && sudo apt install -y software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
```
### 3. Установка зависимостей
### 3.1. Способ 1
Сделайте bash скрипт исполняемым и запустите последовательность установки:
chmod +x install_deps.sh
./install_deps.sh
### 3.2. Способ 2
Вручную:
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
#### Типичные проблемы во время установки:
Важно: красные предупреждения о нехватке зависимостей для tensorflow будут, но можно их игнорировать, т.к. эти модули не используются в данном проекте, а их установка мешает зависимостям MediaPipe.
1. Если будет ошибка с `"AttributeError: google..."` но это потому что `tensorflow` подменяет версию библиотеки `protobuf`, выполните:
```
pip uninstall protobuf google protobuf
pip install protobuf==3.20.3
```
Примечание: если у вас слабая видеокарта или нет CUDA, используйте `tensorflow-cpu` вместо `tensorflow`.
2. Если будет проблема с функцией cv2.imshow() то, переустановите cv2 без заголовков:
```
pip uninstall opencv-python pip install opencv-python-headless
pip install opencv-python==4.12.0.88
```
3. Если будет ошибка с PIL, попробуйте обновить библиотеку:
```
pip upgrage pillow
```
4. **Внимание:** если вам пришлось делать после основной установки какие-то дополнительные, обязательно зафиксируйте еще раз версию numpy!
```
pip install numpy==1.24.3
```
### 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.50.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).
Управление: для каждого фото нажмите цифру, соответствующую жесту (1dome, 2cross, 3none), или `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. Скелет не определяется стойте на расстоянии 12 м, плечи в кадре. Снизьте 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<br>**cross**: P=0.00, R=0.00<br>**none**: P=0.60, R=0.90 | [Скачать](https://disk.yandex.ru/d/I1WyAfN3PJM9fw) | `dome` (руки над головой домиком)<br>`cross` (предплечья скрещены на груди)<br>`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<br>**cross**: P=0.750, R=1.00<br>**none**: P=0.90, R=0.90 | [Скачать](https://disk.yandex.ru/d/fcXxQh4LGjqA1g) |`dome` (руки над головой домиком)<br>`cross` (предплечья скрещены на груди)<br>`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<br>**cross**: P=0.750, R=1.00<br>**none**: P=1.00, R=0.90 | [Скачать](https://disk.yandex.ru/d/4_XCPEeOGvNX6g) |`dome` (руки над головой домиком)<br>`cross` (предплечья скрещены на груди)<br>`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` (отсутствие жеста) |