diff --git a/README.md b/README.md index f5027e7..de5ede7 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ DYNAMIC_GESTURE = { | Название | Описание | Ссылка | Количество примеров | Классы | |----------|----------|--------|---------------------|--------| | **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` | ## Модели | Название | Тип | Архитектура / параметры | Вход | Выход | Точность (test) | Precision / Recall (по классам) | Ссылка | Распознаваемые жесты | @@ -170,3 +171,4 @@ DYNAMIC_GESTURE = { | **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` (отсутствие жеста) | diff --git a/config.py b/config.py index 6e7c125..ef5edfe 100644 --- a/config.py +++ b/config.py @@ -16,18 +16,19 @@ class Config: # ===== Специальные жесты ===== SPECIAL_GESTURE_MODE = 'ml' #ml #geometric - ML_GESTURE_MODEL = '/home/ubuntu/sirius/models/rf/special_model.pkl' + ML_GESTURE_MODEL = '/home/ubuntu/sirius/models/rf/special_gestures_rf.pkl' ML_GESTURE_CLASSES = ['dome', 'cross', 'none'] DYNAMIC_GESTURE = { 'enabled': True, # включить/выключить - 'model_path': 'dynamic_model.h5', - 'classes_path': 'dynamic_model_classes.pkl', + 'model_path': '/home/ubuntu/sirius/models/lstm/dynamic_model.h5', + 'classes_path': '/home/ubuntu/sirius/models/lstm/dynamic_model_classes.pkl', 'window_size': 14, # длина буфера - 'threshold': 0.7, # порог уверенности + 'threshold': 0.8, # порог уверенности 'actions': { 'wave_left': 'reset', # при жесте wave_left – перезапуск симулятора - 'wave_right': 'restart', # при wave_right – рестарт + 'wave_right': 'restart' # при wave_right – рестарт + } } # ===== Робот ===== diff --git a/main.py b/main.py index 13d0141..0d5e2be 100644 --- a/main.py +++ b/main.py @@ -53,6 +53,9 @@ def main(): print("Линейная скорость: правая рука в сторону, угловая: левая рука") print("В симуляторе: R – перезапуск после Game Over/победы") print("q – выход") + + last_dynamic_gesture = None + dynamic_gesture_counter = 0 while True: ret, frame = cap.read() @@ -67,16 +70,17 @@ def main(): if result['success']: landmarks = result['landmarks'] - if result['success']: - if cfg.DYNAMIC_GESTURE['enabled']: - dynamic_predictor.add_frame(landmarks) - gesture = dynamic_predictor.predict() - if gesture: - action = cfg.DYNAMIC_GESTURE['actions'].get(gesture) - if action == 'reset': - robot.reset() - elif action == 'restart': - robot.reset() + if cfg.DYNAMIC_GESTURE['enabled']: + dynamic_predictor.add_frame(landmarks) + gesture = dynamic_predictor.predict() + if gesture: + action = cfg.DYNAMIC_GESTURE['actions'].get(gesture) + if action == 'reset': + robot.reset() + elif action == 'restart': + robot.reset() + last_dynamic_gesture = gesture + dynamic_gesture_counter = 30 vis_frame = detector.draw_landmarks(frame, result['pose_landmarks']) @@ -99,6 +103,12 @@ def main(): if special != 'none': cv2.putText(vis_frame, f"Special: {special}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,0), 2) + + if dynamic_gesture_counter > 0 and last_dynamic_gesture: + cv2.putText(vis_frame, f"Dynamic: {last_dynamic_gesture}", (10, 120), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2) + dynamic_gesture_counter -= 1 + else: vis_frame = frame cv2.putText(vis_frame, "No pose detected", (10, 30), diff --git a/ml_gestures_dynamic/predict.py b/ml_gestures_dynamic/predict.py index a9c59ca..412eae6 100644 --- a/ml_gestures_dynamic/predict.py +++ b/ml_gestures_dynamic/predict.py @@ -14,6 +14,8 @@ class DynamicGesturePredictor: self.threshold = threshold def add_frame(self, landmarks): + if landmarks is None or np.isnan(landmarks).any(): + landmarks = np.zeros(99) self.buffer.append(landmarks) def predict(self):