Filtre vidéo coloré

Xavier Bourry

Filtre vidéo coloré

  • Capturer le flux webcam avec GetUserMedia API,
  • Le traiter en temps réel avec webgl : lui reproduire une liste d'éffets colorimétriques réalisés avec un logiciel de retouche d'image,
  • Le sauvegarder au format webM avec Whammy.js

GetUserMedia API

  • Capture l'image de la webcam et le son du micro dans un élément VIDEO d'HTML5
  • Validé par le W3C : spécifications
  • Pilier de WebRTC (avec RTCPeerConnection et RTCDataChannel)
  • Compatibilité : FF 17, Chrome 21, Opera 12. Source : caniuse.com

Cross compatibilité


navigator.getMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);

window.URL = window.URL || window.webkitURL;
                                    
var get_mediaStream=function(video, func, funcError) {
    navigator.getMedia (
        { video: true, audio: false },

        // successCallback
        function(locMediaStream) {
          video.src = window.URL.createObjectURL(locMediaStream);
          var timerUserMedia=setInterval(function(){
              if(video.readyState==4){
                clearInterval(timerUserMedia);
                video.width=video.videoWidth,
                video.height=video.videoHeight;
                video.autoplay=true;
                func(video);
              }
          }, 100);
        },

        // errorCallback
        function(err) {
          funcError();
        }
    );
}

Le Filtre coloré

  • Fonction F:(R, V, B) → (R', V', B')
  • Doit marcher quelle que soit F
  • Stockée sous la forme d'une image contenant toutes les couleurs = palette
  • Idée : F1:(R, V, B) → (X,Y) et F2: (X,Y) → (R',V',B')
  • La palette représente F2

La palette

  • Image de 4096 x 4096 pixels
  • Générée avec un script canvas2D
  • X = R + 256 * ⌊ B/16 ⌋
    Y = V + 256 * (B-16*⌊B/16 ⌋);
  • S'inverse en :
    R = X - 256 * ⌊ X/256 ⌋
    V = Y - 256 * ⌊ Y/256 ⌋
    B = 16*⌊ X/256 ⌋ + ⌊ Y/256 ⌋
Application à la palette les effets colorimétriques :

Reproduction des effets

  • Soit un pixel de couleur C=(R, V, B) sur la vidéo
  • Coordonnées de C sur la palette :
    X = R + 256 * ⌊ B/16 ⌋
    Y = V + 256 * (B-16*⌊B/16 ⌋)
  • Récupération de la couleur modifiée sur la palette modifiée : C'=(R', V', B')=palette(X,Y)
  • On affiche le pixel avec la couleur C'

Implémentation

  • Avec Canvas2D, getImageData et putImageData
    20 images/seconde * résolution de 800*600 → 107 pixels/seconde à traiter. Trop lent.
  • WebGL : traitement en parallèle sur carte graphique → good

WebGL

  • Utilisé ici pour de la 2D
  • La vidéo est importée sur une texture
  • La texture est affichée sur un quad sur tout le viewPort
  • La conversion colorimétrique est effectuée dans le shader de fragments (par pixels)

Création du contexte GL

Dans le code HTML :
<canvas id="mon_canvas" width="800" height="600"></canvas>
Après le chargement de la page :

var canvas=document.getElementById("mon_canvas");
var GL =canvas.getContext("experimental-webgl",{antialias: true});

Vidéo to texture

Création :
var texture=GL.createTexture();
GL.bindTexture(GL.TEXTURE_2D, texture);
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR);
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR);
Rafraîchissement :
GL.bindTexture(GL.TEXTURE_2D, texture);
GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGB,GL.RGB, GL.UNSIGNED_BYTE, video );
GL.texParameteri( GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE );
GL.texParameteri( GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE );

Création de l'écran

var coins= GL.createBuffer ();
GL.bindBuffer(GL.ARRAY_BUFFER, coins);
GL.bufferData(GL.ARRAY_BUFFER, new Float32Array([-1, -1,
                                                  1, -1,
                                                  1,  1,
                                                 -1,  1]),
                                                 GL.STATIC_DRAW);
var indices= GL.createBuffer ();
GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, indices);
GL.bufferData(GL.ELEMENT_ARRAY_BUFFER, new Uint16Array([1,0,2,3]),GL.STATIC_DRAW);

Rendu

GL.bindBuffer(GL.ARRAY_BUFFER, coins) ;
GL.vertexAttribPointer(position, 2, GL.FLOAT, false,8,0) ;
GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, indices);
GL.drawElements(GL.TRIANGLE_STRIP, 4, GL.UNSIGNED_SHORT, 0);

La palette

Stockée en tant que texture.
Envoyée aux shaders en plus de la vidéo (multisampling)

GL.activeTexture(0);
GL.bindTexture(GL.TEXTURE_2D, texturePalette);
GL.activeTexture(1);
GL.bindTexture(GL.TEXTURE_2D, textureVideo);

Le shader de vertex

  • Exécuté sur le GPU
  • Nécessite d'être compilé (non abordé)
  • Appelé une fois pour chaque coin
  • Code :
    attribute vec2 position;
    void main(void) {
        gl_Position = vec4(position, 0.,1.);
    }
    

Le shader de fragments

Appelé pour chaque pixel. La position du pixel est interpolée en fonction de la position retournée par le shader de vertex, gl_Position.
uniform sampler2D sampler, samplerPalette;
uniform vec2 LH; //dimensions de la video en pixels

void main(void) {
    vec2 uv=vec2(gl_FragCoord)/LH;
    vec4 vidCol = texture2D(sampler, uv);

    float blueBlock=floor(videoCol.b*256.);
    float yBlue=floor(blueBlock/16.)/16.;
    float xBlue=floor((blueBlock-yBlue*256.)/16.)/16.;

    //coordonnées de texture sur la palette :
    vec2 XY=vec2(videoCol.r/16.+yBlue, 1.-vidCol.g/16.-xBlue);
    gl_FragColor=texture2D(samplerPalette, XY);
}

Enregistrement vidéo

  • Whammy.js : encodeur de vidéo WebM en javascript
  • Enregistre une vidéo à partir d'un canvas (2D ou webgl)
  • Nécessite un rendu à fréquence constante

Whammy.js

Initialisation :
var CAPTURER = new Whammy.Video(16); 
Dessin de la scène :
GL.viewport(0.0, 0.0, CV.width, CV.height);
[...] Envoi des textures et rendu de l'écran
GL.flush();
CAPTURER.add(CV);
Fin de l'enregistrement :

var output = CAPTURER.compile();
var href=(window.webkitURL || window.URL).createObjectURL(output);
window.open(href);

Questions ?