Aller au contenu
← Tous les articles
Article

Kinectron + p5.js — gestes, contrôle du sketch et export GIF

Écouter en version balado

Gestes Kinect dans p5.js, avec export GIF · 1:57 · voix synthétiques, script rédigé avec l’IA

Transcription

Sylvie : Que se passe-t-il quand tu tapes des mains devant une Kinect et que l'écran répond par un feu d'artifice ?

Thierry : C'est le croquis qu'Antoine a construit pour un cours de création numérique. Il fait passer les squelettes de la Kinect v2 par Kinectron jusque dans p5.js, et dessine chaque articulation suivie sous forme de point.

Sylvie : Et les gestes ?

Thierry : Six gestes, vérifiés à chaque image du corps. Tu tapes des mains, ça lance un feu d'artifice. Une main au-dessus des hanches, des bulles montent. Une main en dessous, des étoiles tombent.

Sylvie : Comment le code sait qu'on a applaudi ?

Thierry : Les positions des articulations sont normalisées entre zéro et un, donc chaque seuil est une fraction de l'image. Un applaudissement, c'est simplement deux mains qui passent sous une petite distance.

Sylvie : On peut tester sans se tenir devant le capteur ?

Thierry : Oui. Un drapeau, liveData, bascule vers un fichier de squelette enregistré, et des boutons Stop et Play apparaissent. Le bouton Save as GIF marche dans les deux modes, et p5 s'occupe lui-même de l'enregistrement et du téléchargement.

Sylvie : Antoine liste aussi ses erreurs. Deux copies de p5 dans l'en-tête de la page. Et une fonction de table des articulations nommée Kinectron, qui masquait le constructeur de la bibliothèque.

Thierry : Ah, celle-là est vicieuse.

Sylvie : Il y en a d'autres. Son geste du OK demande des articulations qui n'existent pas sur la Kinect v2, donc il ne se déclenche jamais. Et le drapeau de l'applaudissement se remet à zéro à l'image suivante : les feux d'artifice partent une image sur deux tant que les mains restent jointes.

Thierry : Il y a même une expérience facultative où un applaudissement allume ou éteint un appareil connecté, via un petit serveur Node local.

Sylvie : Parfait pour une démo de cours, pas pour du suivi corporel en production. L'article complet détaille chaque étape et les pièges.

Kinectron + p5.js — gestes, contrôle du sketch et export GIF
Table des matières

Pour un cours de creative coding, j’avais besoin des données squelette Kinectron dans p5.js, avec lecture / pause et un export GIF court pour les démos. Le sketch dessine chaque articulation suivie sous forme de point, teste six gestes à chaque image du corps et y répond avec des particules : feux d’artifice sur un clap, bulles qui montent quand une main est au-dessus des hanches, étoiles qui tombent quand elle est en dessous. En expérience annexe, un clap pouvait aussi basculer un appareil connecté via un petit serveur Node local. Les notes ci-dessous suivent le code que j’ai utilisé.

Prérequis

  • Bases JavaScript et p5.js.
  • Une Kinect v2 et une machine qui fait tourner le serveur Kinectron. Le sketch s’y connecte sur le réseau local par adresse IP.
  • Le build client de Kinectron (kinectron-client.js) et p5.js chargés dans la page.
  • Optionnel : Node.js pour le pont « clap → appareil » à la fin.

Les indices d’articulations de mon sketch suivent l’ordre de la Kinect v2 (25 articulations). Le suivi corporel de l’Azure Kinect DK utilise une autre liste : avec ce capteur, il faut remapper les constantes avant que le moindre geste fonctionne.

Étape 1 : la page

Charger un seul build de p5.js, puis le client Kinectron, puis le sketch. Ma première version avait à la fois p5.min.js et p5.js dans le head, ce qui est une erreur : la deuxième copie redéfinit les globales. Le sketch n’utilise pas p5.sound, on peut donc l’enlever aussi.

<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script>
    <!-- le chemin dépend de l’emplacement du sketch par rapport au build client de Kinectron -->
    <script src="./client/dist/kinectron-client.js"></script>
    <script src="sketch.js"></script>
  </head>
  <body></body>
</html>

Étape 2 : table des articulations et variables

Dans sketch.js, je garde un petit objet qui associe les noms d’articulations aux indices Kinect v2 (SPINEBASE = 0 … THUMBRIGHT = 24). Ne l’appelez pas Kinectron : la bibliothèque client définit déjà un constructeur Kinectron, et une fonction du même nom le masquerait. Le mien s’appelle Kinectron2.

function Kinectron2() {
  this.SPINEBASE = 0;
  this.HEAD = 3;
  this.HANDLEFT = 7;
  this.HANDRIGHT = 11;
  // ... les 25 articulations Kinect v2, jusqu’à THUMBRIGHT = 24
}

let kinectron = new Kinectron2();       // remplacé par le vrai client en mode direct
const liveData = true;                  // false = rejouer un fichier JSON enregistré
const recorded_data_file = '../shared/recorded_skeleton.json';
let recorded_skeleton, stopButton, playButton, saveGifButton;
let currentFrame = 0;

function preload() {
  if (!liveData) recorded_skeleton = loadJSON(recorded_data_file);
}

Étape 3 : canvas, connexion et boutons

En mode direct, le sketch ouvre la connexion et enregistre bodyTracked comme callback pour chaque corps suivi. En mode enregistré, il ajoute plutôt des boutons Stop et Play. Save as GIF est créé dans les deux modes. Tous les boutons sont placés juste sous le canvas 640×480 (height + 10).

function setup() {
  createCanvas(640, 480);
  background(0);
  if (liveData) {
    kinectron = new Kinectron("192.0.2.10"); // adresse réseau local du serveur Kinectron
    kinectron.makeConnection();
    kinectron.startTrackedBodies(bodyTracked);
  } else {
    // createButton('Stop') -> stopSketch, createButton('Play') -> playSketch
  }
  saveGifButton = createButton('Save as GIF');
  saveGifButton.position(90, height + 10);
  saveGifButton.mousePressed(startCreatingGif);
}

Étape 4 : boucle draw et callback du corps

Les deux modes alimentent la même fonction. Les données en direct arrivent par le callback Kinectron. Les données enregistrées sont un objet JSON indexé par numéro d’image, et draw() en joue une par tick, avec un modulo pour boucler l’extrait. Ensuite, draw() met à jour les trois listes de particules et retire celles qui ont disparu ou quitté le canvas.

function draw() {
  if (!liveData && recorded_skeleton) {
    currentFrame = currentFrame % Object.keys(recorded_skeleton).length;
    bodyTracked(recorded_skeleton[currentFrame]);
    currentFrame++;
  }
  // mise à jour + affichage des feux, bulles, étoiles ; splice des particules finies
}

function bodyTracked(body) {
  background(0);
  drawSkeleton(body);    // un point par articulation en (depthX * width, depthY * height)
  checkForGestures(body);
}

Les positions arrivent en depthX / depthY normalisés entre 0 et 1 : tous les seuils ci-dessous sont des fractions de l’image, pas des pixels.

Étape 5 : détection des gestes

Chaque geste est une simple fonction qui prend le corps et renvoie un booléen. Plutôt qu’une longue chaîne de if, le sketch construit le nom de la fonction à partir d’une étiquette, la cherche sur window, puis affiche les étiquettes détectées en haut du canvas :

function checkForGestures(body) {
  let detected = [];
  const gestures = ["Clapping", "OK Sign", "Hand Up", "Hand Down",
                    "Hands Crossed", "Both Hands Raised"];
  gestures.forEach(gesture => {
    let fn = `is${gesture.replace(/\s/g, '')}`;   // "OK Sign" -> isOKSign
    if (typeof window[fn] === 'function' && window[fn](body)) {
      detected.push(gesture);
    }
  });
  if (detected.length > 0) displayText(detected.join(", "));
}

Ça ne marche que parce que les tests sont des déclarations de fonctions au premier niveau d’un script classique, ce qui les place sur window. Si vous passez le sketch en modules ES, utilisez plutôt un objet de correspondance explicite.

Ce que compare chaque test :

GesteRègle dans le codeEffet
Clappingdistance entre les deux mains < 0,0550 particules de feu d’artifice à la main droite
OK Signdistances main–pouce et main–index < 0,03texte seulement
Hand Upune des mains au-dessus de SPINEBASE10 bulles par main
Hand Downune des mains sous SPINEBASE15 étoiles par main
Both Hands Raisedles deux mains au-dessus de HEADtexte seulement
Hands Crossedx de la main gauche plus grand que x de la main droitetexte seulement

Le clap est le seul test avec un état. Il lève un drapeau, lance les feux d’artifice et programme une requête une seconde plus tard :

if (distance < 0.05 && !isClapped) {
  isClapped = true;
  triggerFireworks(rightHand.depthX * width, rightHand.depthY * height);
  if (clapTimeout) clearTimeout(clapTimeout);
  clapTimeout = setTimeout(() => {
    const req = new XMLHttpRequest();
    req.open("GET", "http://localhost:3000/toggleDevice");
    req.send();
    isClapped = false;
  }, clapWaitTime);   // 1000 ms
  return true;
} else {
  isClapped = false;
}

Comme chaque nouveau déclenchement annule le timeout en attente, la requête ne part qu’une fois, une seconde après la dernière image de clap. Par contre, la branche else remet le drapeau à false dès l’image suivante : tant que les mains restent collées, les feux d’artifice partent une image sur deux. Un vrai délai de récupération laisserait le drapeau tranquille jusqu’à ce que le timeout le remette à zéro.

Étape 6 : stop, play et GIF

function stopSketch() { noLoop(); }
function playSketch() { loop(); }

function startCreatingGif() {
  saveGif('mySketch', 5);   // enregistre 5 secondes du canvas, puis télécharge mySketch.gif
}

saveGif enregistre et lance le téléchargement tout seul. Ma première version appelait en plus downloadGif(), qui n’existe nulle part et ne faisait que lever une erreur une fois l’enregistrement lancé.

Optionnel : un clap pour basculer un appareil

La requête du test de clap arrive sur un petit serveur Express qui utilise tuyapi pour basculer un appareil connecté Tuya sur le réseau local. Il lit l’état de l’appareil, inverse le booléen du point de données 1, puis se déconnecte :

async function toggleDevice(deviceId, deviceKey) {
  const device = new TuyAPI({ id: deviceId, key: deviceKey });
  try {
    await device.find();
    await device.connect();
    const status = await device.get({ schema: true });
    await device.set({ set: !status.dps['1'] });
  } finally {
    device.disconnect();
  }
}
app.get('/toggleDevice', (req, res) => {
  toggleDevice('<device-id>', '<local-key>');
  res.send('ok');
});

Les dépendances sont express, cors et tuyapi. CORS est nécessaire parce que la page du sketch et le serveur n’ont pas la même origine. Ma version acceptait toutes les origines et répondait avant la fin de la bascule : acceptable pour une démo sur un bureau, pas au-delà. L’identifiant de l’appareil et sa clé locale sont ce dont tuyapi a besoin pour le joindre en local ; gardez-les hors du dépôt.

Étape 7 : lancer le sketch

  1. Démarrer le serveur Kinectron sur la machine reliée à la Kinect et noter son adresse sur le réseau local.
  2. Servir le dossier du sketch en HTTP (n’importe quel serveur statique). Ouvrir index.html depuis le disque casse loadJSON en mode enregistré.
  3. Optionnel : npm install puis node server.js pour le pont vers l’appareil.
  4. Se placer devant le capteur, ou mettre liveData = false pour rejouer un fichier de squelette enregistré.

1. Quand utiliser Kinectron + p5

Cas d’usageAdéquation
Démo de cours sur les données squeletteForte
Suivi corporel en productionPréférer le SDK du fabricant et une pile maintenue
Performance enregistrée hors ligneMettre liveData = false
Navigateurs mobilesFaible — labo sur poste de travail

2. Pièges

  • Balises p5 en double dans le HTML — charger un seul build de p5.
  • Masquer le client — une fonction du sketch nommée Kinectron cache le constructeur de la bibliothèque. Donnez un autre nom à votre table d’articulations.
  • Serveur Kinectron arrêté — le client ne se connecte à rien, sans bruit, tant qu’on ne regarde pas la console.
  • Articulations qui n’existent pas — mon test « OK » demande INDEXLEFT / INDEXRIGHT, qui ne font pas partie des articulations Kinect v2. La garde renvoie false, donc ce geste ne se déclenche jamais. HANDTIPLEFT / HANDTIPRIGHT sont les articulations les plus proches que le capteur fournit.
  • Tests qui se chevauchent — « Hand Up » et « Hand Down » testent l’une ou l’autre main : une main levée et une main baissée déclenchent les deux. Le test des mains croisées compare deux fois la même paire de x, c’est en réalité une seule comparaison.
  • Effacement du canvas en mode direct — background(0) est appelé dans bodyTracked, donc le canvas n’est effacé qu’à l’arrivée d’une image de squelette. Sans personne devant le capteur, les particules laissent des traînées.
  • Durée de saveGif — les captures longues figent les vieux portables ; restez sous ~5 s pour les démos.

3. Articles liés

À partir de là, vous pouvez brancher vos propres gestes ou basculer liveData pour rejouer des extraits enregistrés. Les boutons, la détection des gestes par nom de fonction et le hook saveGif sont les morceaux que je réutilisais d’un devoir à l’autre.

Références

  1. antoinebou12. (). Article manuscript and bundled media. [Article source] github.com.

Les références utilisent la date de publication de cet article.