Making Of : Magic Letter of Santa
Vive la décroissance ! Revenons au papier ! Voici le chatbot 1.0 du Père Noël capable de lire et répondre aux lettres des enfants.
Voici un petit Proof of Concept, mélangeant hardware, reconnaissance visuel, assistant conversationnels, et plein d’autres briques technos…

Le Plotter AxiDraw
La première étape est de pouvoir écrire des messages sur du papier avec un stylo. Axidraw par Evil Mad Scientist propose différents plotter USB. Il existe des copies chinoises mais je ne sais pas si elles sont précises et sont fournit avec un SDK.

Normalement elle s’utilise avec des plugins spécifiques d’Inkscape. Mais il existe une API Python et ligne de commande.
import sys
from pyaxidraw import axidraw
ad = axidraw.AxiDraw()
walk(ad, args.walk_x, args.walk_y)
ad.plot_setup(args.svg)
ad.options.speed_pendown = 50
ad.plot_run()
walk(ad, args.back_x, args.back_y)
disable()
Le code se positionne en x0, y0 puis imprime un fichier svg puis se replace en x1, y1. Il est possible de dessiner en mode « tortue » mais c’est plus lent.
Bubble en SVG
Pour dessiner du contenu dynamique le plus simple est de modifier un template SVG existant en faisant un chercher/remplacer d’un commentaire XML par exemple <!-- My Text -->
.
const svgText = hersheyText.renderTextSVG(text, {
id: 'hershey-id',
font: 'hershey_script_1',
charWidth: 10,
charHeight: 28,
scale: 0.007
});
Pour « dessiner » du texte, il faut construire un <path>
SVG dont le tracé est construit à partir d’une « single stroke font ». La librairie hersheytext fait cette conversion Text to SVG.
Electron
Electron est un framework qui fusionne NodeJS et Chromium afin de faire communiquer magiquement les composants du Front et du Back. C’est un moyen simple d’avoir une interface graphique pour NodeJS.

Un clic sur un lien (en front) permet de déclencher un spawn (en back) et ainsi lancé l’impression. L’usage est vraiment magique.
L’accès à la caméra
L’accès au périphérique sous NodeJS sous Windows est compliqué, plusieurs librairies appellent du code natif… Depuis Electron l’usage est transparent et cross-platform.
let imageContext = undefined;
const initCamera = () => {
navigator.mediaDevices.getUserMedia({
video: {
'frameRate': { ideal: 10, max: 30 },
'width': { min: 1920 }, height: { min: 1080 },
}
}).then(function (stream) {
document.getElementById('camera').srcObject = stream
let track = stream.getVideoTracks()[0];
imageContext = new ImageCapture(track);
});
}
const captureFrame = (callback) => {
if (!imageContext) return;
imageContext.takePhoto().then(blob => {
blob.arrayBuffer().then(buffer => {
/* work on buffer */
}).catch(err => { callback(err) });
}).catch(err => { callback(err) });
}
Jimp est une librarie pure JS (front et back) qui permet la manipulation d’image. De nombreuses fonctions similaire à OpenCV comme la rotation ou le diff d’images sont disponibles. J’ai juste du coder erode/dilate.
Jimp.read(buffer).then(image => {
image.rotate(270)
callback(undefined, image)
})
Pour debugguer il suffit de réinjecter le buffer (Back) de l’image dans le src
d’une <img>
en front.
let colorImageURL = undefined;
const jimpToSnapshot = (image) => {
if (colorImageURL) {
URL.revokeObjectURL(colorImageURL);
}
image.getBufferAsync(Jimp.MIME_PNG).then((buffer) => {
const imageBlob = new Blob([buffer], { type: 'image/png' });
colorImageURL = URL.createObjectURL(imageBlob);
document.getElementById('snapshot').src = colorImageURL
})
}
Computer Vision
L’idée est de faire un diff entre 2 photos prises avant et après une action de l’utilisateur pour identifier ce qui a changé.
if (!image1 || !image2) return;
let image = Jimp.diff(image1, image2, 0.15).image;
black_white(image)
dilate(image, 1)
erode(image, 2)
dilate(image, 1)
/* fine tune again */
diff.mask(image)
Le diff est très sensible et dépendant du setup. Il faut faire une série erode/dilate pour nettoyer l’image et créer un mask avec l’originale.

La partie OCR manuscrite en Français est faite via une requête REST à une API Azure contenant l’image.
let hwr = 'https://northeurope.api.cognitive.microsoft.com/vision/v2.0/recognizeText?mode=Handwritten'
request({
method: "POST",
url: hwr,
headers: {
'Content-Type': 'application/octet-stream',
'Ocp-Apim-Subscription-Key': '00000000000'
},
body: buffer
}, (err, response, body) => {
if (err) { return callback(err); }
let op = response.headers['operation-location']
setTimeout(() => { recognizeTextOperation(op, callback); }, 1000)
});
L’API fonctionne en 2 appels car le traitement des opérations peut prendre plusieurs secondes. Dans mon cas c’est pratiquement instantané et le retour JSON indique la position des différents morceaux de texte reconnus.
Google QuickDraw
QuickDraw est un DataSet de doodle de Google utilisé dans différentes expériences pour reconnaître un dessin, suggéré un dessin, etc …
J’aurais bien voulu reconnaître un dessin dans la bulle mais cela implique de vectoriser le dessin crayon car QuickDraw tiens compte du coup de crayon.
const drawingToSVG = (drawing) => {
let svg = ''
for (let stroke of drawing){
svg += '<path d="'
for (let i = 0 ; i < stroke[0].length ; i++){
x = stroke[0][i]
y = stroke[1][i]
svg += (i == 0 ? 'M':'L') + ' ' + x + ' ' + y + ' '
}
svg += '" style="display:inline;fill:none;..." />\n'
}
return svg
}
Donc comme pour le prénom, l’idée est
- de reconnaître le mot,
- puis d’aller chercher dans la base un drawing,
- puis de le vectoriser dans une bulle
- et l’imprimer.

Plein d’idées
Quand on a un marteau le monde est un clou !
- Reconnaissance de dessin (déjà que sur écran ce n’est pas ouff)
- Jouer à TicTacToe (la difficulté est de repérer les cases depuis la webcam)
- Une interface tactile (OUI | NON) avec un stylo à encre magnétique (je l’ai déjà !)
- Une écriture invisible (comme à la fin de la vidéo)
- Une écriture dont l’encre s’efface (oui ça existe sur Aliexpress)
- Des dessins génératifs (GAN) ou Cartoon-isation de photos
- Un jeux dont vous êtes les héro !
- Du collaboratif VR (une personne écrit à distance ou en VR puis c’est dupliqué en physique)
- De l’écriture par la pensée (avec de nombreux casques)
Voilà plein plein de choses à faire, ce n’est qu’une question d’imagination et de temps !
Tres Impressionnant.
Une Bonne et Heureuse Annee a Toi.
Christian et Evelyne.
Ping : Pour une IHM universelle | IFTTD - If This Then Dev