L'essence de l'apprentissage par un robot

Premier pas vers un outil d'apprentissage basé sur les standards du Web. C'est aussi un tutoriel sur le dessin animé en JavaScript...

Pour illustrer la façon dont on va doter le robot d'une fonction d'apprentissage, on va commencer par quelque chose de simple: un doigt.

Le doigt théoriquement peut accomplir quatre mouvements:

  1. Se plier. Cela participe à l'acte de préhension.
  2. S'étirer. Pour relâcher sa prise.
  3. Pousser. Pour actionner, repousser un objet.
  4. Reculer. Ce quatrième ordre est en fait transmis à la main dans son ensemble tout comme l'acte d'avancer.

L'apprentissage consiste à commander ces mouvements dans une interface comportant une surface graphique représentant une portion du monde réel. L'action dans son ensemble est enregistrée. En repassant l'enregistrement on reproduit cette action. C'est l'essence de l'apprentissage traduit en termes d'informatique.

Pour que cela soit utile, on devra convertir les ordres ainsi générés en commandes pour les éléments du doigt artificiel, dont le mouvement dépend d'un cable relié à un actuateur. Cela correspond à deux commandes pour l'actuateur (tirer, relâcher) et deux commandes transmises à la main artificielle (avancer, reculer). Et au niveau de l'interface, nous n'avons que quatre fonctions en code JavaScript.

Réalisation d'une interface d'apprentissage

Démonstration basique ci-dessous. Le doigt du robot est réprésenté dans un canevas (les proportions ne sont pas conservées). L'utilisateur clique sur un point dans le cadre pour déplacer le doigt. L'interface enregistre ce que fait l'utilisateur, donc elle mémorise les points successifs et elle peut restituer l'enchaînement des mouvements quand on clique sur Replay.

Cliquer pour déplacer l'image du doigt...

Programmation de l'interface

C'est essentiellement le dessin du doigt dans une balise Canvas.

function phalange3(color, x1, y1, x2, y2, tilt)
{
  ctx.beginPath();
  ctx.strokeStyle = color;	
  ctx.lineWidth = 2;	
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y1 + tilt);
  ctx.quadraticCurveTo(x2, y2 + tilt, x1, y2);
  ctx.lineTo(x1, y2);
  ctx.stroke();	
}

function phalange2(color, x1, y1, x2, y2, tilt)
{
  ctx.beginPath();
  ctx.strokeStyle = color;
  ctx.lineWidth = 2;
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y1 + tilt);
  ctx.moveTo(x1, y2);
  ctx.lineTo(x2, y2 + tilt);
  ctx.stroke();
}

function phalange1(color, x1, y1, x2, y2, tilt)
{
  ctx.beginPath();
  ctx.strokeStyle = color;
  ctx.lineWidth = 2;
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y1 + tilt);
  ctx.moveTo(x1, y2);
  ctx.lineTo(x2, y2 + tilt);
  ctx.stroke();
}

function finger(x, y, tilt1, tilt2, tilt3)
{
  phalange1('#666', x, y, x + 50, y + 50, tilt1);
  phalange2('#888', x + 50, y + tilt2, x + 100, y + 50 + tilt2 , tilt2);
  phalange3('#aaa', x + 100, y + tilt3 , x + 200, y + 50 + tilt3, tilt3 );
}

function redraw(x, y)
{
  var tilt = y / 10;
  ctx.fillStyle = "white";
  ctx.fillRect(0,0, 600, 400);
  finger(10, 10, tilt, tilt, tilt * 2);
}

var canvas = document.getElementById("fakeworld");
var ctx = canvas.getContext("2d");
var fakeworld = document.getElementById("fakeworld");

redraw(10, 10);

Le dessin est décomposé en trois phalanges, ce qui permettrait de plier le doigt, mais nous ne le ferons pas dans cette démo.

Enregistrement

Pour enregistrer le mouvement, on crée un tableau nommé recording et on ajoute au tableau chaque paire de coordonées.

Il faut aussi capter les mouvements de la souris et les clics.

var xMousePosition = 0;
var yMousePosition = 0;

document.onmousemove = function(e)
{
  xMousePosition = e.clientX + window.pageXOffset;
  yMousePosition = e.clientY + window.pageYOffset;
};

function display(arr)
{
  document.getElementById("controler").innerHTML = JSON.stringify(arr, null, 4);
}

var recording = [];

function update()
{
  var x = xMousePosition - fakeworld.offsetLeft;
  var y = yMousePosition - fakeworld.offsetTop;
  redraw(x, y);
  recording.push([x, y]);
  display(recording);
}

fakeworld.onclick = update;

On associe donc la fonction update à l'évènement onclick pour le canvas, cette fonction redessine le doigt par un appel à redraw et ajoute le couple de coordonnées. Le contenu du tableau est affiché par la fonction display.

Reproduction

On reproduit le mouvement comme dans un dessin animé, en affichant les images successives du doigt qui bouge.

Idéalement on devrait enregistrer les délais en même temps que les mouvements, mais dans cette démonstration on va simplifier et instaurer un délai unique entre deux mouvement, et qui est ajouté durant le replay.

var replayed = [];

function step(couple)
{
  var x = couple[0];
  var y = couple[1];
  replayed.push(couple);
  display(replayed);
  redraw(x, y);
}

function iterate(i)
{
  if(i >= recording.length) return;
  setTimeout(function() {
    step(recording[i])
    i++;
    iterate(i);
  }, 700);
}

function replay()
{
  replayed = [];
  var couple = recording[0];
  iterate(0, couple);
}

On crée le tableau replayed pour enregistrer et afficher les coordonnées en cours de déroulement. La fonction iterate est récursive. Cela est nécessaire car pour créer un délai en deux dessins, on fait appel à setTimeout qui est asynchrone. Cela permet d'enchaîner correctement la séquence des dessins.

Ce que l'on fait en replay est aussi ce que fera le robot réel. Les mêmes données au lieu d'être acheminées vers la balise Canvas le seront vers les actuateurs du robot et utilisées comme arguments des commandes électroniques. Bien sûr, cette démonstration n'expose que le principe et est élémentaire. Pour un animation sérieuse en 3D il convient d'utiliser un logiciel comme Anim8or, Blender, OpenFX et de nombreux autres.
Il existe également des protocoles de communication homme-machine que l'on appelle HID (Human Interface Device) dont fait partie Bluetooth HID.