Gesture Hero
Rise of the Motion Masters
Documentation technique complète du projet : contrôler Beach Buggy Racing par les gestes de la main grâce à l'intelligence artificielle embarquée, sans jamais toucher un clavier. 🖐️🎮
Explorer la doc 🚀Présentation du projet
Gesture Hero est un système de contrôle gestuel intelligent développé lors du hackathon Arduino Days 2026 à Cotonou. L'idée centrale est simple et ambitieuse à la fois : remplacer complètement le clavier par les mouvements de la main pour jouer à un jeu vidéo en temps réel.
Le joueur tient dans sa main une carte Arduino Nano 33 BLE Sense. Selon la direction dans laquelle il incline ou déplace sa main, un modèle d'intelligence artificielle — entraîné sur la plateforme Edge Impulse — reconnaît le geste et envoie la commande correspondante au jeu via Bluetooth, un pont ESP32, et un script Python.
Prendre le contrôle d'un véhicule dans Beach Buggy Racing (tourner à gauche, tourner à droite, accélérer, freiner) uniquement avec des gestes de la main, sans aucune modification du jeu.
🎮 Le jeu : Beach Buggy Racing
Beach Buggy Racing est un jeu de course arcade multi-plateforme aux commandes directes et réactives. Il se joue normalement avec les quatre touches directionnelles du clavier. Ce choix est stratégique pour le hackathon : les commandes sont simples (4 touches), la réactivité se teste immédiatement, et aucune modification du jeu n'est nécessaire.
🖐️ Les gestes à reconnaître
La classe idle (main au repos) est indispensable pour éviter les faux positifs.
Sans elle, le modèle interprétera tout bruit comme un geste.
Les composants du système
Le système repose sur trois composants matériels et un logiciel, chacun ayant un rôle précis dans la chaîne de traitement.
pyserial — lecture port sériepynput — simulation clavier🧠 Edge Impulse (plateforme cloud)
Edge Impulse est la plateforme utilisée pour collecter les données d'entraînement, concevoir et entraîner le modèle de classification, et l'exporter en bibliothèque Arduino prête à l'emploi.
Comment tout fonctionne ensemble
Du geste physique jusqu'à l'action dans le jeu — en temps réel.
⏱️ Latence attendue sur la chaîne
| Segment | Technologie | Latence estimée |
|---|---|---|
| Capture IMU → inférence modèle | Arduino + TinyML | ~80–120 ms |
| Transmission BLE | Bluetooth Low Energy 5.0 | ~10–30 ms |
| ESP32 → PC (Serial USB) | UART 115200 baud | < 5 ms |
| Script Python → touche clavier | pynput | < 5 ms |
| TOTAL | — | ~100–160 ms |
✅ Critères de qualité du système
- LATENCE — Réaction < 200 ms entre geste et action
- PRÉCISION — Modèle > 85 % sur les données de test
- STABILITÉ — Connexion BLE maintenue pendant toute la partie
- ROBUSTESSE — Pas de faux positifs sur la classe idle
- FLUIDITÉ — Contrôle naturel et intuitif du véhicule
- PORTÉE — BLE stable jusqu'à ~2–3 mètres du PC
Installer Edge Impulse CLI
Edge Impulse CLI permet de connecter l'Arduino directement à la plateforme cloud.
npm install -g edge-impulse-cli
# Vérifier l'installation :
edge-impulse-daemon --version
Configurer l'IDE Arduino
- Télécharger Arduino IDE 2.x depuis arduino.cc
- Ajouter le support :
Tools → Board Manager → Arduino Mbed OS Nano Boards - Installer la bibliothèque ArduinoBLE via le Library Manager
- Installer la bibliothèque Arduino_LSM9DS1
Installer Python et les dépendances
pip install pyserial pynput
Faire une rapide vérification de toute la chaîne avant de commencer : Arduino reconnu par l'IDE, Edge Impulse CLI connecté, Python fonctionnel. Identifier les problèmes dès le début évite de perdre du temps plus tard.
C'est l'étape la plus importante du projet. La qualité du modèle dépend entièrement de la qualité des données collectées.
Connecter l'Arduino à Edge Impulse
edge-impulse-data-forwarder
Paramètres de collecte
- Fréquence d'échantillonnage : 62.5 Hz ou 100 Hz
- Durée par sample : 1000 ms (1 seconde)
- Colonnes :
accX, accY, accZ, gyrX, gyrY, gyrZ
| Classe | Description du geste | Samples minimum |
|---|---|---|
haut | Incliner/pousser la main vers le haut ou l'avant | 60 samples |
bas | Incliner/pousser la main vers le bas ou l'arrière | 60 samples |
gauche | Balayer ou incliner la main vers la gauche | 60 samples |
droite | Balayer ou incliner la main vers la droite | 60 samples |
idle | Main au repos, sans mouvement intentionnel | 60 samples |
- Faire chaque geste de façon nette et cohérente
- Varier la vitesse (rapide et lente) et la position
- La classe
idleest aussi importante que les autres - Répartition recommandée : 80 % train / 20 % test
1. Configurer l'Impulse
- Window size : 1000 ms
- Window increase : 200 ms
- Bloc de traitement : Spectral Analysis
- Bloc d'apprentissage : Classification (Keras)
2. Configurer Spectral Analysis
- Type : FFT — FFT length : 16 ou 32
- Cocher : Log of spectrum, Noise floor
- Cliquer Save Parameters puis Generate Features
3. Architecture du réseau de neurones
78 features
20 neurons
10 neurons
5 classes
- Epochs : 30 — Learning rate : 0.0005
- Validation set size : 20 %
- Model version : Quantized (int8)
4. Exporter la bibliothèque Arduino
- Onglet Deployment → Sélectionner Arduino Library
- Optimisation : Enable EON Compiler
- Cliquer Build et télécharger le fichier
.zip - Arduino IDE :
Sketch → Include Library → Add .ZIP Library
🏆 Performances du modèle — Résultats finaux
Résultats obtenus après entraînement sur Edge Impulse Studio — validation set (Quantized int8)
| DOWN | IDLE | LEFT | RIGHT | UP | |
|---|---|---|---|---|---|
| DOWN | 92.1% | 0% | 0% | 0% | 7.9% |
| IDLE | 0% | 100% | 0% | 0% | 0% |
| LEFT | 0% | 0% | 70% | 26.7% | 3.3% |
| RIGHT | 0% | 0% | 20% | 66.7% | 13.3% |
| UP | 8.9% | 0% | 0% | 0% | 91.1% |
| F1 SCORE | 0.88 | 1.00 | 0.78 | 0.61 | 0.92 |
- IDLE → 100% F1 = 1.00 — Parfaitement reconnu, zéro faux positif 🎯
- UP → 91.1% F1 = 0.92 — Excellent, léger bruit avec DOWN
- DOWN → 92.1% F1 = 0.88 — Très bon, quelques confusions avec UP
- LEFT → 70% F1 = 0.78 — Correct mais confondu avec RIGHT (26.7%)
- RIGHT → 66.7% F1 = 0.61 — Point faible — confusion avec LEFT et UP
Les gestes LEFT et RIGHT se confondent mutuellement (20–27%). Pour améliorer : collecter +30 samples supplémentaires pour chaque, en exagérant la distinction latérale. Augmenter les epochs à 50 pourrait aussi aider le modèle à mieux séparer ces classes.
📸 Capture Edge Impulse Studio
Cliquez sur l'image pour l'agrandir — vue complète du dashboard d'entraînement
Edge Impulse Studio — Architecture du réseau de neurones, matrice de confusion et métriques du modèle gesture-hero
Chaque ligne = classe réelle, chaque colonne = classe prédite.
Les cases diagonales (vert) sont les bonnes prédictions.
Si idle est confondu avec un geste → collecter plus de samples idle.
Dans notre cas, idle est parfait à 100% ! 🎯
Ce sketch lit l'IMU, fait tourner le modèle d'inférence, et envoie le geste détecté via BLE.
#include <ArduinoBLE.h> #include <NOM_DE_TON_PROJET_inferencing.h> #include <Arduino_LSM9DS1.h> // ── UUIDs du service BLE ── #define SERVICE_UUID "19B10000-E8F2-537E-4F6C-D104768A1214" #define CHAR_UUID "19B10001-E8F2-537E-4F6C-D104768A1214" BLEService gestureService(SERVICE_UUID); BLEStringCharacteristic gestureChar(CHAR_UUID, BLERead | BLENotify, 20); #define CONFIDENCE_THRESHOLD 0.70f #define IDLE_LABEL "idle" float features[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE]; void setup() { Serial.begin(115200); if (!IMU.begin()) { Serial.println("IMU ERREUR"); while(1); } if (!BLE.begin()) { Serial.println("BLE ERREUR"); while(1); } BLE.setLocalName("GestureHero"); BLE.setAdvertisedService(gestureService); gestureService.addCharacteristic(gestureChar); BLE.addService(gestureService); BLE.advertise(); Serial.println("GestureHero prêt — en attente BLE..."); } void loop() { BLEDevice central = BLE.central(); if (!central) return; while (central.connected()) { // 1. Remplir le buffer IMU int samples = EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE / 6; for (int i = 0; i < samples; i++) { float ax, ay, az, gx, gy, gz; while (!IMU.accelerationAvailable()); IMU.readAcceleration(ax, ay, az); IMU.readGyroscope(gx, gy, gz); int idx = i * 6; features[idx] = ax; features[idx+1] = ay; features[idx+2] = az; features[idx+3] = gx; features[idx+4] = gy; features[idx+5] = gz; delay(1000 / EI_CLASSIFIER_FREQUENCY); } // 2. Inférence signal_t signal; numpy::signal_from_buffer(features, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal); ei_impulse_result_t result = { 0 }; run_classifier(&signal, &result, false); // 3. Meilleure classe String best = ""; float score = 0; for (int j = 0; j < EI_CLASSIFIER_LABEL_COUNT; j++) { if (result.classification[j].value > score) { score = result.classification[j].value; best = result.classification[j].label; } } // 4. Envoyer via BLE if (score >= CONFIDENCE_THRESHOLD && best != IDLE_LABEL) { gestureChar.writeValue(best); } } BLE.advertise(); }
Remplacer NOM_DE_TON_PROJET_inferencing.h par le nom exact du fichier
d'en-tête généré par Edge Impulse lors de l'export.
L'ESP32 scanne, se connecte au « GestureHero », et retransmet chaque geste reçu sur le port série USB.
Configuration IDE pour ESP32
- Ajouter l'URL :
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Board Manager → chercher esp32 → installer
- Sélectionner la carte ESP32 Dev Module
#include "BLEDevice.h" static BLEUUID serviceUUID("19B10000-E8F2-537E-4F6C-D104768A1214"); static BLEUUID charUUID ("19B10001-E8F2-537E-4F6C-D104768A1214"); BLEClient* pClient = nullptr; BLERemoteCharacteristic* pChar = nullptr; BLEAdvertisedDevice* myDevice = nullptr; bool doConnect = false, connected = false; // Callback : notification BLE reçue → envoyer au PC void notifyCallback(BLERemoteCharacteristic* pCh, uint8_t* data, size_t length, bool isNotify) { String gesture = ""; for (int i = 0; i < (int)length; i++) gesture += (char)data[i]; Serial.println(gesture); // ← Le PC Python lit cette ligne } // Callback : scan BLE — trouver "GestureHero" class MyCallbacks : public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice adv) { if (adv.getName() == "GestureHero") { BLEDevice::getScan()->stop(); myDevice = new BLEAdvertisedDevice(adv); doConnect = true; } } }; void connectToServer() { pClient = BLEDevice::createClient(); pClient->connect(myDevice); auto pSvc = pClient->getService(serviceUUID); if (!pSvc) { pClient->disconnect(); return; } pChar = pSvc->getCharacteristic(charUUID); if (pChar && pChar->canNotify()) pChar->registerForNotify(notifyCallback); connected = true; } void setup() { Serial.begin(115200); BLEDevice::init(""); auto pScan = BLEDevice::getScan(); pScan->setAdvertisedDeviceCallbacks(new MyCallbacks()); pScan->setActiveScan(true); pScan->start(30, false); } void loop() { if (doConnect && !connected) connectToServer(); if (connected && !pClient->isConnected()) { connected = false; doConnect = false; BLEDevice::getScan()->start(30, false); } delay(100); }
Le dernier maillon : lit le port série, identifie le geste, et simule l'appui clavier via pynput.
import serial import time from pynput.keyboard import Key, Controller # ── Configuration ── SERIAL_PORT = '/dev/ttyUSB0' # Windows: 'COM3' BAUD_RATE = 115200 KEY_PRESS = 0.15 # secondes # ── Mapping geste → touche ── KEY_MAP = { 'haut': Key.up, 'bas': Key.down, 'gauche': Key.left, 'droite': Key.right, } keyboard = Controller() print(f"[GestureHero] Connexion sur {SERIAL_PORT}...") try: port = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) print("[GestureHero] Connecté. En attente de gestes...") while True: try: raw = port.readline() if not raw: continue gesture = raw.decode('utf-8').strip().lower() if not gesture: continue if gesture in KEY_MAP: key = KEY_MAP[gesture] print(f"[GestureHero] {gesture:8s} → touche simulée") keyboard.press(key) time.sleep(KEY_PRESS) keyboard.release(key) except UnicodeDecodeError: pass except serial.SerialException as e: print(f"[ERREUR] Port série : {e}")
- Linux :
ls /dev/ttyUSB*ouls /dev/ttyACM* - macOS :
ls /dev/cu.usb* - Windows : Gestionnaire de périphériques → Ports (COM & LPT)
🚀 Procédure de démarrage
- Alimenter l'Arduino Nano 33 BLE Sense avec la powerbank
- Attendre l'advertising BLE (LED bleue)
- Brancher l'ESP32 au PC via câble USB
- L'ESP32 se connecte automatiquement (~5–10 sec)
- Lancer :
python3 gesture_hero.py - Ouvrir Beach Buggy Racing et lancer une course
- Tester chaque geste un par un 🖐️
📋 Checklist de tests
- Geste HAUT → Voiture accélère immédiatement
- Geste BAS → Voiture freine / recule
- Geste GAUCHE → Voiture tourne à gauche
- Geste DROITE → Voiture tourne à droite
- Main au repos (idle) → Aucune action
- Stabilité BLE 5 min → Connexion maintenue
- Latence perçue → Réaction < 200 ms
🔧 Problèmes courants et solutions
| Problème | Cause | Solution |
|---|---|---|
| ESP32 ne trouve pas l'Arduino | Pas encore en advertising | Attendre 10–15 sec ou redémarrer |
| Faux positifs fréquents | Seuil trop bas | Monter à 0.80 ou plus de données idle |
| Gestes non reconnus | Données insuffisantes | Ajouter 20 samples par classe |
| Latence trop haute | Window size trop grande | Réduire à 750 ms |
| Port série introuvable | Driver manquant | dmesg | tail sur Linux |
🔥 Défis & Difficultés rencontrées
Comme toute équipe de débutants en électronique embarquée assistée par l'IA, nous avons rencontré de nombreux obstacles. Voici notre parcours de combattants — et comment nous avons (parfois) survécu.
- Vérifier tout le matériel et les installations avant le hackathon
- Avoir des câbles USB de rechange
- Tester la chaîne BLE en isolation avant l'intégration complète
- Documenter chaque erreur rencontrée en temps réel
📅 Résumé du week-end
| Période | Étape | Durée | Livrable |
|---|---|---|---|
| Samedi matin | 01 — Installation environnement | ~30 min | Outils installés, compte Edge Impulse créé |
| Samedi matin | 02 — Collecte des données | ~45 min | 300+ samples collectés (5 classes) |
| Samedi après-midi | 03 — Entraînement modèle | ~30 min | Modèle précision > 85 % |
| Samedi après-midi | 04 — Code Arduino | ~45 min | Arduino envoie les gestes en BLE |
| Samedi soir | 05 — Code ESP32 | ~30 min | ESP32 relaie BLE → PC |
| Samedi soir | 06 — Script Python | ~20 min | Script simule les touches |
| Dimanche matin | 07 — Intégration & tests | ~45 min | Système complet fonctionnel |
| Dimanche après-midi | 08 — Doc & présentation | ~30 min | GitHub publié, démo prête |
👥 Les Motion Masters
Notre équipe de Gesture Heroes — des débutants passionnés qui ont relevé le défi de l'IA embarquée lors de l'Arduino Days 2026 à Cotonou 🇧🇯