Tech things and such
Article / Note
2017/03/10

Traitement d'image vidéo "live"

Accès à la webcam et process des images à la volée. Le filtre vu ici simule un appareil photo avec réglage de sensibilité et de temps de pose (éventuellement long). Le système Human Brush met en oeuvre un procédé similaire quoique plus performant en terme de résolution et nombre d'images par seconde. (Ouvrir en https).





Fonctionne avec Chrome et Firefox. Ne fonctionne pas avec Safari et d'autres navigateurs. Ouvrez la page en https (outrepassez l'avertissement de sécurité), acceptez la demande d'accès à la webcam.

Canvas et profondeur de couleur

Les canvas HTML sont prévus pour stocker au plus des pixels 32 bits (8 bits par couche R, G, B et A) soit des valeurs entières entre 0 et 255. Toute valeur supérieure ou inférieure à ces limites est "clampée".

Si dans les usages courants ce n'est pas un problème car une telle précision sur les couleurs suffit pour l'oeil humain (16,7 millions de couleurs tout de même), pour l'effet présenté dans cet exemple, cela ne peut pas convenir car le phénomène d'accumulation peut amener certaines zones à dépasser le blanc.

Voyons l'algorithme de traitement des pixels :
// Array de stockage des pixels instancié lorsque l'accès à la caméra est obtenu.
// L'array est initialisé avec toutes les valeurs à  0.0. 
// Contrairement à l'array issu de getImageData().data, ces valeurs ne sont pas limitées à 8 bits mais sont des nombres flottants :
var PIXELS = [];
for (var i = 0; i < (w*h*4); i++) PIXELS[i] = 0.0;

// Voici ce qui est réalisé à chaque fois qu'une image est obtenue de la caméra :

//On dessine l'image de la caméra dans le canvas puis on récupère les pixels.       
con.drawImage(v, 0, 0, w, h);
var imgData = con.getImageData(0,0,w,h);
var d = imgData.data;

var alpha = document.getElementById("alpha").value;
var speed = document.getElementById("speed").value;
var seuil = document.getElementById("seuil").value;

for(var o = 0 ; o < d.length ; o += 4) 
{
    //calcul approximatif de la luminosité
    var lum = 0.3*d[o]/255 + 0.6*d[o+1]/255 + 0.1*d[o+2]/255;
    //addition aux valeurs précédentes, gestion du seuil, de la sensibilité et du taux d'accumulation.
    PIXELS[o] =  alpha*(PIXELS[o] + speed*(lum > seuil ? d[o] : 0));
    PIXELS[o+1] = alpha*(PIXELS[o+1] + speed*(lum > seuil ? d[o+1] : 0));
    PIXELS[o+2] = alpha*(PIXELS[o+2] + speed*(lum > seuil ? d[o+2] : 0));
    PIXELS[o+3] = alpha*(PIXELS[o+3] + speed*(lum > seuil ? d[o+3] : 0));

// Les valeurs dans PIXELS sont donc des flottants, possiblement supérieurs à 255. Cela signifie que les zones "cramées", c'est à dire qui sont devenues "plus blanches que blanches" sont possibles à conserver. Si on diminue l'accumulation, l'image va repasser par toutes les étapes antérieures, le détail dans les hautes lumières n'est donc jamais définitivement perdu, ce qui aurait été le cas si on n'avait pas fait appel à cet array, mais seulement travaillé sur les valeurs 8 bits. Ceci se paye fatalement en terme de performances...

    // clamp des valeurs flottantes vers des entiers 8 bits :    
    d[o] =   Math.floor(Math.min(Math.max(0,PIXELS[o]), 255));
    d[o+1] = Math.floor(Math.min(Math.max(0,PIXELS[o+1]), 255));
    d[o+2] = Math.floor(Math.min(Math.max(0,PIXELS[o+2]), 255));
    d[o+3] = Math.floor(Math.min(Math.max(0,PIXELS[o+3]), 255));
}
// Dessiner le résultat dans le canvas :
con.putImageData(imgData,0,0);

>> Réagir à cet article