3. Image Processing

Image Procesing #

En webgl (es decir, GLSL ES) la texturización se utiliza para implementar el procesamiento de imágenes.

Para dibujar imágenes en WebGL necesitamos utilizar texturas. De manera similar a la forma en que WebGL espera coordenadas de espacio de recorte cuando renderiza en lugar de píxeles, WebGL espera coordenadas de textura cuando lee una textura. Las coordenadas de la textura van de 0.0 a 1.0 sin importar las dimensiones de la textura.

Como sólo estamos dibujando un único rectángulo (bueno, 2 triángulos) necesitamos decirle a WebGL a qué lugar de la textura corresponde cada punto del rectángulo. Pasaremos esta información desde el sombreador de vértices al sombreador de fragmentos usando un tipo especial de variable llamada ‘varying’. Se llama variable porque varía. WebGL interpolará los valores que proporcionemos en el sombreador de vértices mientras dibuja cada píxel utilizando el fragment shader.

Por ejemplo, el siguiente shader implementa una matriz de convolución 3x3 bastante genérica, a la cual se le aplica la operacion de Ridge detection la cual esta denotada de la siguiente forma

Shaders_ImageProcessing.js
let lumaShader;
let img;
let video;

function preload() {
  lumaShader = readShader('/Template/sketches/shaders/mask.frag', {matrices: Tree.NONE});
  // image source: https://en.wikipedia.org/wiki/HSL_and_HSV#/media/File:Fire_breathing_2_Luc_Viatour.jpg
  img = loadImage('/Template/sketches/shaders/fire_breathing.jpg');
}

function setup() {
  createCanvas(700, 500, WEBGL);
  noStroke();
  textureMode(NORMAL);
  shader(lumaShader);
  lumaShader.setUniform('texture', img);
  lumaShader.setUniform('texOffset', [0.001,0.001]);
  lumaShader.setUniform('mask', [-1,-1,-1,-1,8.0,-1,-1,-1,-1]);

}

function draw() {
  
  background(0);
  lumaShader.setUniform('mouse_position', [mouseX,mouseY]);
  quad(1, 1, -1, 1, -1, -1, 1, -1);
  /*beginShape();
    vertex(1, -1);
    vertex(-1, -1);
    vertex(-1, 1);
    vertex(1, 1);
  endShape(CLOSE);*/
}

function keyPressed(){
  if(key == '1'){
    lumaShader.setUniform('filter_selected', 1.0);
  }else if(key == '2'){
    lumaShader.setUniform('filter_selected', 2.0);
  }
  background(0);
  lumaShader.setUniform('mouse_position', [mouseX,mouseY]);
  quad(1, 1, -1, 1, -1, -1, 1, -1);
}
mask.frag
precision mediump float;

// uniforms are defined and sent by the sketch
uniform bool grey_scale;
uniform sampler2D texture;
uniform vec2 mouse_position;
uniform float filter_selected;
uniform vec2 u_resolution;


uniform vec2 texOffset;
// holds the 3x3 kernel
uniform float mask[9];

// interpolated texcoord (same name and type as in vertex shader)
varying vec2 texcoords2;



float luma(vec3 texel) {
  return 0.299 * texel.r + 0.587 * texel.g + 0.114 * texel.b;
}


// returns luma of given texel
vec3 luminance() {

  vec4 color = texture2D(texture, texcoords2);
  
  
  vec2 st = gl_FragCoord.xy; 
  vec2 mouse = vec2(mouse_position.x, 500.0 - mouse_position.y);

  /*if(st.x <= mouse.x + 100.0 && 
    st.x > mouse.x - 100.0 && 
    500.0 - st.y <=  mouse.y + 100.0 &&
    500.0 - st.y > mouse.y - 100.0){
      r = 0.0;
      g = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
      b = 0.0;
  }*/


  if(distance(st,mouse)< 90.0){
     // 1. Use offset to move along texture space.
  // In this case to find the texcoords of the texel neighbours.
  vec2 tc0 = texcoords2 + vec2(-texOffset.s, -texOffset.t);
  vec2 tc1 = texcoords2 + vec2(         0.0, -texOffset.t);
  vec2 tc2 = texcoords2 + vec2(+texOffset.s, -texOffset.t);
  vec2 tc3 = texcoords2 + vec2(-texOffset.s,          0.0);
  // origin (current fragment texcoords)
  vec2 tc4 = texcoords2 + vec2(         0.0,          0.0);
  vec2 tc5 = texcoords2 + vec2(+texOffset.s,          0.0);
  vec2 tc6 = texcoords2 + vec2(-texOffset.s, +texOffset.t);
  vec2 tc7 = texcoords2 + vec2(         0.0, +texOffset.t);
  vec2 tc8 = texcoords2 + vec2(+texOffset.s, +texOffset.t);

  // 2. Sample texel neighbours within the rgba array
  vec4 rgba[9];
  rgba[0] = texture2D(texture, tc0);
  rgba[1] = texture2D(texture, tc1);
  rgba[2] = texture2D(texture, tc2);
  rgba[3] = texture2D(texture, tc3);
  rgba[4] = texture2D(texture, tc4);
  rgba[5] = texture2D(texture, tc5);
  rgba[6] = texture2D(texture, tc6);
  rgba[7] = texture2D(texture, tc7);
  rgba[8] = texture2D(texture, tc8);

  // 3. Apply convolution kernel
  vec4 convolution;
  for (int i = 0; i < 9; i++) {
    convolution += rgba[i]*mask[i];
  }

  if(filter_selected == 1.0){
     return convolution.rgb;
  }else if(filter_selected == 2.0){
   return (vec3(luma(color.rgb)));
  }
    
  

   

   
 
    

  }else{

    vec4 color = texture2D(texture, texcoords2);

    float r = color.r;
    float g = color.g;
    float b = color.b;

    return (vec3(r,g,b));
  }

}

void main() {
  // texture2D(texture, texcoords2) samples texture at texcoords2 
  // and returns the normalized texel color
  /*vec4 texel = texture2D(texture, texcoords2);
  gl_FragColor = vec4(luminance(texel.rgb), 1.0);
  gl_FragColor = vec4(luminance(), 1.0);
  */
  

   
    gl_FragColor = vec4(luminance(), 1.0);

}

Visión nocturna #

Como ejercicio adicional, implementamos un shader que simula la vision nocturna a través de una Lupa, adicionalmente usa las herramientas de brillo de HSV, HSL y Luma.

La implementación se realizó tanto para una imagen, como para un video

  • Al oprimir el número “1” se aplica la herramienta de color luma
  • Al oprimir el número “2” se aplica la herramienta de color HSV
  • Al oprimir el número “3” se aplica la herramienta de color HSL
  • El efecto de lupa, cambio de color y herramienta de brillo se aplica a la región circular que tiene como centro la posición del mouse
  • El selector en la parte superior cambiará el recurso de entrada, ya sea la imagen o el video
Shaders_Night_Vision.js
let lumaShader;
let img;
let selector;
let vid;

function preload() {
  lumaShader = readShader('/Template/sketches/shaders/playground.frag', {matrices: Tree.NONE});
  // image source: https://en.wikipedia.org/wiki/HSL_and_HSV#/media/File:Fire_breathing_2_Luc_Viatour.jpg
  img = loadImage('/Template/sketches/shaders/night.jpg');
  
}

function setup() {
  vid = createVideo("/Template/sketches/shaders/videotest.mp4",vidLoad);
  createCanvas(700, 500, WEBGL);
  noStroke();
  textureMode(NORMAL);
  shader(lumaShader);
  lumaShader.setUniform('texture', img);

  selector = createCheckbox('video',false);
  selector.position(10,10);
  selector.style('color', 'white');  
  selector.changed(SelectorEvent);
}

function SelectorEvent(){
  if (selector.checked()) {
    lumaShader.setUniform('texture', vid);
    vidLoad();
  } else {
    lumaShader.setUniform('texture', img);
  }
}

function vidLoad() {
  vid.loop();
  vid.speed(3);
  vid.volume(0);
  vid.hide();
}

function draw() {
  
  background(0);
  lumaShader.setUniform('mouse_position', [mouseX,mouseY]);
  quad(1, 1, -1, 1, -1, -1, 1, -1);
  /*beginShape();
    vertex(1, -1);
    vertex(-1, -1);
    vertex(-1, 1);
    vertex(1, 1);
  endShape(CLOSE);*/
}

function keyPressed(){
  if(key == '1'){
    lumaShader.setUniform('filter_selected', 1.0);
  }else if(key == '2'){
    lumaShader.setUniform('filter_selected', 2.0);
  }else if(key == '3'){
    lumaShader.setUniform('filter_selected', 3.0);
  }
  background(0);
  lumaShader.setUniform('mouse_position', [mouseX,mouseY]);
  quad(1, 1, -1, 1, -1, -1, 1, -1);
}
night_vision.frag
precision mediump float;

// uniforms are defined and sent by the sketch
uniform bool grey_scale;
uniform sampler2D texture;
uniform vec2 mouse_position;
uniform float filter_selected;
uniform vec2 u_resolution;

// interpolated texcoord (same name and type as in vertex shader)
varying vec2 texcoords2;

// returns luma of given texel
vec3 luminance() {

  vec4 color = texture2D(texture, texcoords2);
  
  vec2 st = gl_FragCoord.xy; 
  vec2 mouse = vec2(mouse_position.x, 500.0 - mouse_position.y);

  /*if(st.x <= mouse.x + 100.0 && 
    st.x > mouse.x - 100.0 && 
    500.0 - st.y <=  mouse.y + 100.0 &&
    500.0 - st.y > mouse.y - 100.0){
      r = 0.0;
      g = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
      b = 0.0;
  }*/


  if(distance(st,mouse)< 150.0){
    vec4 color = texture2D(texture, vec2((1.0- mouse.x/700.0) + (texcoords2.x - mouse.x/700.0)*0.4,(1.0- mouse.y/500.0) + (texcoords2.y - mouse.y/500.0)*0.4));
    float r = color.r;
    float g = color.g;
    float b = color.b;

    float Cmax = max(r,max(g,b)); 
    float Cmin = min(r,min(g,b));

    float V;
     
    // HSV 
    if (filter_selected == 2.0){
      V = Cmax;
    }
    //HSL
    else if (filter_selected == 3.0){
      V = (Cmax + Cmin) / 2.0;
    }
    // luma by default
    else{
      V = dot(color.rgb, vec3(0.299, 0.587, 0.0114));
    }

    vec3 result = vec3(V);
    result.r = 0.0;
    result.b = 0.0;
    
    return result;

  }else{

    vec4 color = texture2D(texture, texcoords2);

    float r = color.r;
    float g = color.g;
    float b = color.b;

    return (vec3(r,g,b));
  }

}

void main() {
  // texture2D(texture, texcoords2) samples texture at texcoords2 
  // and returns the normalized texel color
  /*vec4 texel = texture2D(texture, texcoords2);
  gl_FragColor = vec4(luminance(texel.rgb), 1.0);*/
  gl_FragColor = vec4(luminance(), 1.0);
}