Star

Created With

Máscaras de Convolución

linkAntecedentes

Las máscaras de Convolución son una manera de aplicar efecto a las representaciones gráficas y obtener nuevos efectos o asignaciones de color a partir de operaciones matemáticas. Usualmente se hace uso de la llamada matriz de convolución que incluye los valores de color de la imagen/elemento original y el kernel el cual es una matriz cuadrada que contiene pesos definidos que alteran los valores de los pixeles según su magnitud. La operación de convolución se expresa matemáticamente de la siguiente manera:

Sea I la matriz de los valores de color de una imagen, y K el kernel de un filtro determinado, entonces la matriz de la imagen filtrada G se calculará como:

G(x,y)=K(dx,dy)I(x,y)=dx=aady=bbK(dx,dy)I(x+dx,y+dy),G(x,y) = K(dx, dy) * I(x, y) = \sum_{dx=-a}^{a} \sum_{dy=-b}^{b} K(dx,dy) * I(x+dx,y+dy),1

donde los elementos de K cumplen: adxa-a \leq dx \leq a y bdyb-b \leq dy \leq b. Por tanto el proceso de convolución es la accion de sumar cada elemento de la imagen a sus vecinos próximos ponderados por el kernel. Sin embargo la convolución no consiste en la multiplicacián de matrices habitual. Es usual usar kernels de tamaño 3x3 (a pesar de que existen de mayor tamaño), la operación de convolución corresponderá a situar el elemento del centro del kernel en el pixel a analizar. El kernel se solapará con los valores vecinos alrededor del centro, por lo que cada elemento del kernel debe multiplicarse con el valor que está solapando y finalmente todos los valores deben sumarse para dar como resultado el nuevo valor del pixel analizado (que se solapa por el centro del kernel). La imagen a continuación muestra gráficamente este proceso:

Convolution Process Image

En caso de que el kernel tenga mayores dimensiones, la operación de convolución requiere que las filas y columnas del kernel se intercambien horizontal y verticalmente antes de realizar calculos. Adicionalmente se debe manejar una estrategia para los bordes de una imagen, ya que en las operaciones, una parte del kernel se situará por fuera de la imagen; para esto se puede adoptar una de las siguientes convenciones:

Dependiendo de los valores, un kernel puede generar diferentes efectos, entre los más comunes se encuentran la detección de bordes, difuminación, emboss (identificación de sombras o brillos). En el presente taller se detallará en la implementación de convoluciones, tanto de las que hacen uso de un kernel como algunas especiales que no (ver pestaña Máscaras video). Para esto a continuación en la pestaña Máscaras Imagen se podrá visualizar la implementación de algunas convoluciones mencionadas anteriormente aplicadas a una imagen por medio de shadders en WEBGL, lo cual permite que se ejecute el código directamente en la unidad de procesamiento gráfica GPU. Se crea una textura con la imagen y a esa textura se le aplica la matriz de convolución correspondiente según el usuario especifique con las siguientes teclas:

TeclaMáscara
0Identidad (Original)
1Emboss
2Outline (Edge Detection)
3Sharpen
4Top Sobel
5Gaussian Blur
6Left Sobel

La representación de la imagen convolucionada se visualizará aplicada a una elipse y a un cubo que rota, con el ánimo de explorar una de las capacidades de WEBGL que es el manejo de elementos en 3D. De igual manera veremos como la luz puede afectar principalmente a el mencionado objeto 3D al pasar el cursor sobre el mismo y ver como la luz de distorsiona sobre el objeto (se observa mejor al dejar el cursor estático en un lugar cerca al cubo).

En la pestaña Máscaras Video se visualizan otras máscaras pero esta vez aplicadas a la captura de video por cámara en tiempo real. El video responde a los comandos por teclas mostrados en la siguiente tabla, igualmente aplicando las texturas a un shader de WEBGL.

TeclaMáscara
0Identidad (Original)
1RGB Negative ?
2Inverse
3Single Pass Blur
4Outline (Edge Detection)
5Emboss

Para este caso, las máscaras denominadas como RGB Negative e Inverse no hacen uso de un kernel como tal sino que efectuan operaciones determinadas sobre cada color de la imagen, están incluidas a causa de su aplicación en la transformación de imagenes.

Por su parte, la pestaña Instrucciones describe un paso a paso del proceso de convolución de los elementos mencionados (imagen y video) ya que son pasos en su mayoría similares; Iniciando con la creación de los shaders respectivos a partir de los Fragment Shader y Vertex Shader correspondientes, una vez creado el canvas tipo WEBGL se pasan los datos de imagen o video al fragment shader para que identifique según la tecla presionada por el usuario que tipo de máscara de convolución desea. Por último el Fragment Shader realiza las operaciones de la máscara deseada y devuelve el renderizado de la imagen/video para ser mostrada.

Finalmente, las pestañas Código Imagen y Código Video muestran el código de implementación para los fragment shaders usados en la imagen y el video respectivamente, ambos tienen comentarios de su funcionamiento para mayor comprensión.

linkSolución y Resultados

No.Descripción
1Precargar Shader para imagen/ video con el vertex y fragment shader.
2Dividir el área a lo largo de una línea horizontal o vertical.
3Seleccionar una de las dos nuevas celdas de partición.
4Realizar nuevamente el paso numero 2.
5A partir de la tecla presionada por el usuario se definen los valores de la matriz kernel de convolución (si aplica).
6En el caso de Imagen (y algunas máscaras de Video) se obtienen los pixeles vecinos por cada pixel.
7Se realiza la operación de convolución con el kernel o valores correspondientes a cada pixel.
8Con el vector del nuevo color renderiza este valor producto de la convolución y se muestra en pantalla.
9En el caso de Imagen, aplicar la textura renderizada en 2D(elipse) y 3D (cubo).
convMask.frag
1linkvarying vec2 vTexCoord;

2link// Valores que se pasan desde p5

3linkuniform sampler2D u_img;

4linkuniform int u_key;

5linkuniform vec2 stepSize; //Tamaño del texel a usar para cada paso ( 1.0 / width)

6link

7link//Arreglo de 9 valores, cada uno representa un valor alrederor de un pixel (vecinos)

8link// y el pixel mismo

9linkvec2 offset[9];

10link

11link// Arreglo con los valores de la matriz de convolucion a usar

12linkfloat kernel[9];

13link

14link// valor de convolucion que sera renderizado en la pantalla

15linkvec4 conv = vec4(0.0);

16link

17linkvoid main() {

18link vec2 uv = vTexCoord;

19link

20link //Invierte la posicion de la cordenada para que la imagen no quede alrreves

21link uv.y = 1.0 - uv.y;

22link

23link // identity kernel

24link kernel[0] = 0.0; kernel[1] = 0.0; kernel[2] = 0.0;

25link kernel[3] = 0.0; kernel[4] = 1.0; kernel[5] = 0.0;

26link kernel[6] = 0.0; kernel[7] = 0.0; kernel[8] = 0.0;

27link

28link //Segun la tecla presionada por el usuario se define el kernel de la mascara respectiva

29link

30link if(u_key==1){

31link

32link // emboss kernel

33link kernel[0] = -2.0; kernel[1] = -1.0; kernel[2] = 0.0;

34link kernel[3] = -1.0; kernel[4] = 1.0; kernel[5] = 1.0;

35link kernel[6] = 0.0; kernel[7] = 1.0; kernel[8] = 2.0;

36link

37link }else if (u_key==2){

38link

39link // edge detection

40link kernel[0] = -1.0; kernel[1] = -1.0; kernel[2] = -1.0;

41link kernel[3] = -1.0; kernel[4] = 8.0; kernel[5] = -1.0;

42link kernel[6] = -1.0; kernel[7] = -1.0; kernel[8] = -1.0;

43link

44link }else if (u_key==3){

45link

46link // sharpen kernel

47link kernel[0] = 0.0; kernel[1] = -1.0; kernel[2] = 0.0;

48link kernel[3] = -1.0; kernel[4] = 5.0; kernel[5] = -1.0;

49link kernel[6] = 0.0; kernel[7] = -1.0; kernel[8] = 0.0;

50link

51link }else if (u_key==4){

52link

53link // top sobel

54link kernel[0] = 1.0; kernel[1] = 2.0; kernel[2] = 1.0;

55link kernel[3] = 0.0; kernel[4] = 0.0; kernel[5] = 0.0;

56link kernel[6] = -1.0; kernel[7] = -2.0; kernel[8] = -1.0;

57link

58link }else if (u_key==5){

59link

60link // blur kernel

61link kernel[0] = 0.0625; kernel[1] = 0.125; kernel[2] = 0.0625;

62link kernel[3] = 0.125; kernel[4] = 0.25; kernel[5] = 0.125;

63link kernel[6] = 0.0625; kernel[7] = 0.125; kernel[8] = 0.0625;

64link

65link }else if (u_key==6){

66link

67link // left sobel kernel

68link kernel[0] = 1.0; kernel[1] = 0.0; kernel[2] = -1.0;

69link kernel[3] = 2.0; kernel[4] = 0.0; kernel[5] = -2.0;

70link kernel[6] = 1.0; kernel[7] = 0.0; kernel[8] = -1.0;

71link

72link } else if (u_key==0){

73link

74link // identity kernel values

75link kernel[0] = 0.0; kernel[1] = 0.0; kernel[2] = 0.0;

76link kernel[3] = 0.0; kernel[4] = 1.0; kernel[5] = 0.0;

77link kernel[6] = 0.0; kernel[7] = 0.0; kernel[8] = 0.0;

78link

79link }

80link

81link //Guardar la ubicaci�n de los pixeles vecinos

82link offset[0] = vec2(-stepSize.x, -stepSize.y); // top left

83link offset[1] = vec2(0.0, -stepSize.y); // top middle

84link offset[2] = vec2(stepSize.x, -stepSize.y); // top right

85link offset[3] = vec2(-stepSize.x, 0.0); // middle left

86link offset[4] = vec2(0.0, 0.0); //middle

87link offset[5] = vec2(stepSize.x, 0.0); //middle right

88link offset[6] = vec2(-stepSize.x, stepSize.y); //bottom left

89link offset[7] = vec2(0.0, stepSize.y); //bottom middle

90link offset[8] = vec2(stepSize.x, stepSize.y); //bottom right

91link

92link //Por cada pixel vecino

93link for(int i = 0; i<9; i++){

94link //sample a 3x3 grid of pixels

95link vec4 color = texture2D(u_img, uv + offset[i]);

96link

97link //multiplicar el color del pixel por el valor correspondiente del kernel y

98link // a&ntilde;adirlo al valor total de la convolucion

99link conv += color * kernel[i];

100link

101link }//for end

102link

103link // Se renderiza la salida con el valor de la convolucion

104link gl_FragColor = vec4(conv.rgb, 1.0);

105link}

106link}

107link

webcamMask.frag
1link// textura de p5

2linkuniform sampler2D tex0;

3link//valor de la tecla presionada por el usuario

4linkuniform int u_key;

5link//Tama&ntilde;o de pixel en la pantalla

6linkuniform vec2 texelSize;

7link

8link//Arreglo de 9 valores, cada uno representa un valor alrederor de un pixel (vecinos)

9link// y el pixel mismo

10linkvec2 offset[9];

11link

12link// Arreglo con los valores de la matriz de convolucion a usar

13linkfloat kernel[9];

14link

15link// valor de convolucion que sera renderizado en la pantalla

16linkvec4 conv = vec4(0.0);

17link

18linkvoid main() {

19link

20link vec2 uv = vTexCoord;

21link // voltear la textura para mostrarse al derecho

22link uv = 1.0 - uv;

23link

24link vec4 tex = texture2D(tex0, uv);

25link

26link //Dejar valores de color originales desde el inicio

27link

28link float threshR = tex.r ;

29link float threshG = tex.g ;

30link float threshB = tex.b ;

31link

32link vec4 thresh = vec4(threshR, threshG, threshB, 1.0);

33link

34link //Si la tecla es 0 se muestra el video original

35link if (u_key==0){

36link

37link float threshR = tex.r ;

38link float threshG = tex.g ;

39link float threshB = tex.b ;

40link

41link vec4 thresh = vec4(threshR, threshG, threshB, 1.0);

42link

43link }else if (u_key==1){

44link

45link //Si la tecla es 1 se muestra la mascara correspondiente

46link

47link float gray = (tex.r + tex.g + tex.b) / 3.0;

48link

49link float res = 20.0;

50link float scl = res / (10.0);

51link

52link float threshR = (fract(floor(tex.r*res)/scl)*scl) * gray ;

53link float threshG = (fract(floor(tex.g*res)/scl)*scl) * gray ;

54link float threshB = (fract(floor(tex.b*res)/scl)*scl) * gray ;

55link

56link thresh = vec4(threshR, threshG, threshB, 1.0);

57link }else if (u_key==2){

58link

59link//Si la tecla es 2 se muestra la mascara inversa

60link

61link tex.rgb = 1.0 - tex.rgb;

62link thresh = tex;

63link

64link }else if (u_key==3){

65link

66link //Si la tecla es 3 se muestra la mascara single pass blur

67link //consiste en promediar todos los pixeles vecinos del mismo, incluyendo el propio pixel

68link

69link // creacion de valor del paso a dar para calcular vecinos de un pixel

70link vec2 step = texelSize * 4.0;

71link

72link // obtener los pixeles vecinos y sumarlos en el vector tex

73link

74link vec4 tex = texture2D(tex0, uv); // middle middle -- el pixel actual

75link

76link tex += texture2D(tex0, uv + vec2(-step.x, -step.y)); // top left

77link tex += texture2D(tex0, uv + vec2(0.0, -step.y)); // top middle

78link tex += texture2D(tex0, uv + vec2(step.x, -step.y)); // top right

79link

80link tex += texture2D(tex0, uv + vec2(-step.x, 0.0)); //middle left

81link tex += texture2D(tex0, uv + vec2(step.x, 0.0)); //middle right

82link

83link tex += texture2D(tex0, uv + vec2(-step.x, step.y)); // bottom left

84link tex += texture2D(tex0, uv + vec2(0.0, step.y)); // bottom middle

85link tex += texture2D(tex0, uv + vec2(step.x, step.y)); // bottom right

86link

87link // se toma el promedio de los valores sumados

88link tex /= 9.0;

89link

90link thresh = tex;

91link

92link }else if (u_key==4 || u_key==5){

93link

94link //Si la tecla es 4 o 5 se aplica la mascara edge detection (outline) o emboss

95link vec4 conv = vec4(0.0);

96link if(u_key==4){

97link // edge detection kernel

98link kernel[0] = -1.0; kernel[1] = -1.0; kernel[2] = -1.0;

99link kernel[3] = -1.0; kernel[4] = 8.0; kernel[5] = -1.0;

100link kernel[6] = -1.0; kernel[7] = -1.0; kernel[8] = -1.0;

101link

102link } if (u_key==5){

103link

104link // emboss kernel values

105link kernel[0] = -2.0; kernel[1] = -1.0; kernel[2] = 0.0;

106link kernel[3] = -1.0; kernel[4] = 1.0; kernel[5] = 1.0;

107link kernel[6] = 0.0; kernel[7] = 1.0; kernel[8] = 2.0;

108link }

109link

110link //Guardar la ubicaci�n de los pixeles vecinos

111link offset[0] = vec2(-texelSize.x, -texelSize.y); // top left

112link offset[1] = vec2(0.0, -texelSize.y); // top middle

113link offset[2] = vec2(texelSize.x, -texelSize.y); // top right

114link offset[3] = vec2(-texelSize.x, 0.0); // middle left

115link offset[4] = vec2(0.0, 0.0); //middle

116link offset[5] = vec2(texelSize.x, 0.0); //middle right

117link offset[6] = vec2(-texelSize.x, texelSize.y); //bottom left

118link offset[7] = vec2(0.0, texelSize.y); //bottom middle

119link offset[8] = vec2(texelSize.x, texelSize.y); //bottom right

120link

121link //Por cada pixel vecino

122link for(int i = 0; i<9; i++){

123link //sample a 3x3 grid of pixels

124link vec4 color = texture2D(tex0, uv + offset[i]);

125link

126link //multiplicar el color del pixel por el valor correspondiente del kernel y

127link // a&ntilde;adirlo al valor total de la convolucion

128link conv += color * kernel[i];

129link

130link }//for end

131link thresh = vec4(conv.rgb, 1.0);

132link }else{

133link

134link //Dejar valores de color originales

135link

136link float threshR = tex.r ;

137link float threshG = tex.g ;

138link float threshB = tex.b ;

139link

140link vec4 thresh = vec4(threshR, threshG, threshB, 1.0);

141link }

142link

143link // Se renderiza la salida con el valor de la convolucion

144link gl_FragColor = thresh;

145link}

Con base en la implementación mostrada se puede evidenciar como la convolución afecta en la representación de una imagen y permite a su vez analizar propiedades que no son evidentes a la vista humana a priori. La principal aplicación de las máscaras de convolución es evidentemente en el campo de la fotografía y las aplicaciones relacionadas, en donde la lógica de los diferentes estilos y filtros que se le pueden aplicar a las imagenes está fundamentado en una operación de convolución.

Se resalta que, en el presente taller se observaron operaciones relativamente sencillas y sin mayor grado de complejidad, sin embargo, para filtros más específicos es necesario efectuar operaciones de mayor grado como por ejemplo los filtros sepia para imagenes.

Por otra parte se concluye que las máscaras de convolución tienen un alto potencial en aplicarse en el diagnóstico de trastornos visuales e incluso en la terapia del manejo de estos mismos al diseñar e implementar filtros definidos que posean las propiedades médicas necesarias para la función deseada. De igual manera en la industria del entretenimiento como los videojuegos y la producción audiovisual se abre la posibilidad de representar con mas certeza algunas características del sentido de la vista como condiciones de enfoque y perspectiva de manera más realista.

linkReferencias y Bibliografía

Convolution Fundamentals

Convolution Process and Kernel Fundamentals

Kernel values

Image Convolution Fragment Shader Base

Video Convolution Mask Assets

Image and Video Convolution Masks

Convolution Process Image

AntecedentesSolución y ResultadosReferencias y Bibliografía

Home

Workshopschevron_right
Imaging & Videochevron_right
Softwarechevron_right
Hardwarechevron_right

Introducción RGB y Luma Mascaras de Convolución Ascii Art Foto-Mosaico

Desempeño Computacionalchevron_right

Conclusiones y Trabajo Futuro

Renderingchevron_right
Teamchevron_right