3. Image Processing

Image processing #

1. Introducción #

En este parte del taller se busca aplicar distintos kernels de imagén junto con varias herramientas de luminosidad o brillo de imagén, partiendo de varios conceptos que serán detallados en el siguiente punto.

2. Revisión bibliográfica #

Convolución #

La convolución dentro del procesamiento de imágenes, es el proceso de transformar una imagén aplicando unos valores de un kernel sobre cada píxel y sus vecinos locales.

Para realizar la convolución de una imagen, primero se multiplican los valores tanto del píxel seleccionado como los de sus vecinos locales por su respectivo valor de la matriz del kernel, y luego se realiza una sumatoria de cada uno de los valores obtenidos para así obtener el valor del píxel seleccionado.

De esta forma cada píxel toma un nuevo valor mediante la medida ponderada de los pixeles contiguos.

Ejemplo gŕafico - Operación de convolución de una imagen con el kernel de Sharpen: Convolution

Kernel #

El kernel es una matriz \(M{\times}M\) cuyos valores determinan la transformación que sufrirá la imagen original, ya sea para difuminar, detectar ejes o aumentar su nitidez.

Algunos kernels usados en este taller son:

KernelDescripción
\[\begin{bmatrix} 0 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & 0 \end{bmatrix}\]Kernel identidad
\[\frac{1}{9} \begin{bmatrix} 1 & 1 & 1\\ 1 & 1 & 1\\ 1 & 1 & 1 \end{bmatrix}\]Desenfoque de caja
\[\frac{1}{16} \begin{bmatrix} 1 & 2 & 1\\ 2 & 4 & 2\\ 1 & 2 & 1 \end{bmatrix}\]Desenfoque Gaussiano
\[\begin{bmatrix} 0 & -1 & 0\\ -1 & 5 & -1\\ 0 & -1 & 0 \end{bmatrix}\]Enfoque o afilado (Sharpen)

Luminosidad #

Luma

La luminosidad aplicada en este caso está asociada a una conversión del espacio de color RGB a un espacio de escala de grises aplicando métodos ponderados como también mediante la intensidad (método promedio), valor HSV y luminosidad HSL. Estos dos últimos transforman el espacio cartesiano RGB.

Se obtiene esta escala de grises debido a la luminancia, la cual representa el brillo de las componentes de blanco y negro, esta se crea a partir de las componentes del espacio RGB las cuales son multiplicadas independientemente por un valor especifico.

Dado que el ojo humano tiene un mayor sensibilidad a la luz verde, adquiere un mayor valor en la ponderación del luma (Y’), seguido del rojo y a su vez del azul.

\[Y_{601}^{'} = 0.2989 \cdot R + 0.5870 \cdot G + 0.1140 \cdot B\]

Por otra parte, los siguientes métodos no tienen en cuenta esta sensibilidad.

\[Intensity = \frac{1}{3}(R + G + B) = \frac{R}{3} + \frac{G}{3} + \frac{B}{3}\] \[HSV-Value = max(R, G, B)\] \[HSL-Lightness = \frac{1}{2}(max(R, G, B) + min(R, G, B))\]

3. Métodos #

Para la realización de este tema hicimos uso de los conceptos anteriormente expuestos, de tal forma que se presenta una implementación del procesamiento de imágenes mediante la convolución con distintos valores del kernel para lograr diferentes efectos, la aplicación de distintos métodos de luminosidad monocromática.

4. Resultados #

Atajos de teclado

El manejo del kernel se hace mediante teclado, mientras que para las demás herramientas (brillo, lupa y región) es mediante un select y unos radio buttons.

TeclaDescripción
1Imagen original (Identidad)
2Desenfoque de caja
3Enfoque o afilado (Sharpen)
4Desenfoque Gaussiano
5Detección de crestas (Ridges)
6Operador Sobel

Fragment shader
precision mediump float;

uniform sampler2D texture;
uniform vec2 texOffset;

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

// we need our interpolated tex coord
varying vec2 texcoords2;

// Mouse Coordenates
uniform vec2 mouseCoords;

// Resolution
uniform vec2 resolution;

// Brightness Tools
uniform int brightnessTool;

// Magnifier Tool
uniform bool magnifierTool;
uniform float magnifierRadius;
uniform float magnifierScale;

// Region Tool
uniform bool regionTool;

vec4 kernel() {
  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];
  }

  // 4. Set color from convolution
  return vec4(convolution.rgb, 1.0);
}

/* ------------------------------------ */
/*         Brightness functions         */
/* ------------------------------------ */

float luma(vec3 color) {
  return dot(color, vec3(0.299, 0.587, 0.114));
}

float mean(vec3 color) {
  return (color.r + color.g + color.b) / 3.0;
}

float hsv(vec3 color){
  return max(color.r, max(color.g, color.b));
}

float luminance(vec3 color) {
  return (max(color.r, max(color.g, color.b)) + min(color.r, min(color.g, color.b))) / 2.0;
}

vec4 changeBrightness(vec4 color) {

  if(brightnessTool == 0){
    return color;
  }
  else if(brightnessTool == 1){
    return vec4(vec3(luma(color.rgb)), 1.0);
  }
  else if(brightnessTool == 2){
    return vec4(vec3(mean(color.rgb)), 1.0);
  }
  else if(brightnessTool == 3){
    return vec4(vec3(hsv(color.rgb)), 1.0);
  }

  return vec4(vec3(luminance(color.rgb)), 1.0);
}


/* ------------------------------------ */
/*                 MAIN                 */
/* ------------------------------------ */

void main(){
  vec4 texel = texture2D(texture, texcoords2);

  if(!regionTool){
    texel = kernel();
    texel = changeBrightness(texel);
  }

  float dist = distance(gl_FragCoord.xy, mouseCoords);

  if (dist < magnifierRadius) {
    if(magnifierTool){

      vec2 magnifierCoords = mouseCoords + (gl_FragCoord.xy - mouseCoords) * magnifierScale;
      magnifierCoords = magnifierCoords / resolution;
      magnifierCoords = vec2(magnifierCoords.x, 1.0 - magnifierCoords.y);

      vec4 magnifierTexel = texture2D(texture, magnifierCoords);
      magnifierTexel = changeBrightness(magnifierTexel);

      gl_FragColor = magnifierTexel;

    }
    else if(regionTool){

      vec2 regionCoords = gl_FragCoord.xy;
      regionCoords = regionCoords / resolution;
      
      vec4 regionTexel = texture2D(texture, regionCoords);
      
      regionTexel = kernel();
      regionTexel = changeBrightness(regionTexel);
      gl_FragColor = regionTexel;

    }
    else{
      gl_FragColor = texel;
    }
  }
  else{
    gl_FragColor = texel;
  }

}

Referencias #