En el procesamiento de imágenes , un núcleo , una matriz de convolución o una máscara es una pequeña matriz que se utiliza para desenfocar, enfocar, realzar, detectar bordes y más. Esto se logra haciendo una convolución entre el núcleo y una imagen . O más simplemente, cuando cada píxel de la imagen de salida es una función de los píxeles cercanos (incluido él mismo) en la imagen de entrada, el núcleo es esa función.
La expresión general de una convolución es
donde está la imagen filtrada, es la imagen original, es el núcleo del filtro. Cada elemento del núcleo del filtro es considerado por y .
Dependiendo de los valores de los elementos, un kernel puede causar una amplia gama de efectos:
Los anteriores son sólo algunos ejemplos de efectos que se pueden lograr mediante la convolución de núcleos e imágenes.
El origen es la posición del núcleo que está encima (conceptualmente) del píxel de salida actual. Esto podría estar fuera del núcleo real, aunque normalmente corresponde a uno de los elementos del núcleo. Para un núcleo simétrico, el origen suele ser el elemento central.
La convolución es el proceso de agregar cada elemento de la imagen a sus vecinos locales, ponderados por el núcleo. Esto está relacionado con una forma de convolución matemática . La operación matricial que se realiza (convolución) no es una multiplicación de matrices tradicional, a pesar de que se denota de manera similar con *.
Por ejemplo, si tenemos dos matrices de tres por tres, la primera un núcleo y la segunda una pieza de imagen, la convolución es el proceso de invertir tanto las filas como las columnas del núcleo y multiplicar entradas localmente similares y sumar. El elemento en las coordenadas [2, 2] (es decir, el elemento central) de la imagen resultante sería una combinación ponderada de todas las entradas de la matriz de la imagen, con pesos dados por el núcleo:
Las otras entradas tendrían una ponderación similar, donde ubicamos el centro del núcleo en cada uno de los puntos límite de la imagen y calculamos una suma ponderada.
Los valores de un píxel determinado en la imagen de salida se calculan multiplicando cada valor del núcleo por los valores de píxel de la imagen de entrada correspondientes. Esto se puede describir algorítmicamente con el siguiente pseudocódigo:
para cada fila de imagen en la imagen de entrada : para cada píxel en la fila de imagen : poner el acumulador a cero para cada fila del núcleo en el núcleo : para cada elemento en la fila del núcleo : si la posición del elemento corresponde* a la posición del píxel , multiplique el valor del elemento correspondiente* al valor del píxel y agregue el resultado al final del acumulador establecer el píxel de la imagen de salida en el acumulador
Si el núcleo es simétrico, coloque el centro (origen) del núcleo en el píxel actual. El núcleo superpondrá los píxeles vecinos alrededor del origen. Cada elemento del núcleo debe multiplicarse por el valor de píxel con el que se superpone y todos los valores obtenidos deben sumarse. Esta suma resultante será el nuevo valor para el píxel actual actualmente superpuesto con el centro del núcleo.
Si el núcleo no es simétrico, se debe invertir alrededor de su eje horizontal y vertical antes de calcular la convolución como se indicó anteriormente. [1]
La forma general de convolución matricial es
La convolución del kernel generalmente requiere valores de píxeles fuera de los límites de la imagen. Existe una variedad de métodos para manejar los bordes de la imagen.
La normalización se define como la división de cada elemento del núcleo por la suma de todos los elementos del núcleo, de modo que la suma de los elementos de un núcleo normalizado sea la unidad. Esto garantizará que el píxel promedio de la imagen modificada sea tan brillante como el píxel promedio de la imagen original.
Los algoritmos de convolución rápida incluyen:
La convolución 2D con un núcleo M × N requiere multiplicaciones M × N para cada muestra (píxel). Si el núcleo es separable, entonces el cálculo se puede reducir a M + N multiplicaciones. El uso de convoluciones separables puede disminuir significativamente el cálculo al realizar una convolución 1D dos veces en lugar de una convolución 2D. [2]
Aquí una implementación de convolución concreta realizada con el lenguaje de sombreado GLSL :
// autor: csblo // Trabajo realizado con solo consultar: // https://en.wikipedia.org/wiki/Kernel_(image_processing)/Kernel_(image_processing)// Definir núcleos #definir identidad mat3(0, 0, 0, 0, 1, 0, 0, 0, 0) #definir borde0 mat3(1, 0, -1, 0, 0, 0, -1, 0, 1) #definir borde1 mat3(0, 1, 0, 1, -4, 1, 0, 1, 0) #definir borde2 mat3(-1, -1, -1, -1, 8, -1, -1 , -1, -1) #definir afilar mat3(0, -1, 0, -1, 5, -1, 0, -1, 0) #definir box_blur mat3(1, 1, 1, 1, 1, 1 , 1, 1, 1) * 0.1111 #definir gaussian_blur mat3(1, 2, 1, 2, 4, 2, 1, 2, 1) * 0.0625 #definir relieve mat3(-2, -1, 0, -1, 1, 1, 0, 1, 2)// Encuentra las coordenadas del elemento de la matriz a partir del índice vec2 kpos ( int index ) { return vec2 [ 9 ] ( vec2 ( - 1 , - 1 ), vec2 ( 0 , - 1 ), vec2 ( 1 , - 1 ), vec2 ( - 1 , 0 ), vec2 ( 0 , 0 ), vec2 ( 1 , 0 ), vec2 ( - 1 , 1 ), vec2 ( 0 , 1 ), vec2 ( 1 , 1 ) ) [ índice ] / iResolution . xy ; } // Extrae la región de la dimensión 3x3 del sampler centrado en uv // sampler: sampler de textura // uv: coordenadas actuales en el sampler // return: una matriz de mat3, cada índice correspondiente a un canal de color mat3 [ 3 ] region3x3 ( sampler2D , vec2 uv ) { // Crea cada píxel para la región vec4 [ 9 ] región ; para ( int i = 0 ; i < 9 ; i ++ ) región [ i ] = textura ( muestra , uv + kpos ( i )); // Crea una región de 3x3 con 3 canales de color (rojo, verde, azul) mat3 [ 3 ] mRegión ; para ( int i = 0 ; i < 3 ; i ++ ) mRegión [ i ] = mat3 ( región [ 0 ][ i ], región [ 1 ][ i ], región [ 2 ][ i ], región [ 3 ] [ i ], región [ 4 ][ i ], región [ 5 ][ i ], región [ 6 ][ i ], región [ 7 ][ i ], región [ 8 ][ i ] ); devolver mRegión ; } // Convoluciona una textura con el kernel // kernel: kernel usado para la convolución // sampler : sampler de textura // uv: coordenadas actuales en la convolución del sampler vec3 ( kernel mat3 , sampler2D , vec2 uv ) { fragmento vec3 ; // Extrae una región de 3x3 centrada en uv mat3 [ 3 ] region = region3x3 ( sampler , uv ); // para cada canal de color de la región for ( int i = 0 ; i < 3 ; i ++ ) { // obtiene el canal de región mat3 rc = región [ i ]; // multiplicación por componentes del kernel por canal de región mat3 c = MatrixCompMult ( kernel , rc ); // suma cada componente de la matriz float r = c [ 0 ][ 0 ] + c [ 1 ][ 0 ] + c [ 2 ] [ 0 ] + c [ 0 ][ 1 ] + c [ 1 ][ 1 ] + c [ 2 ][ 1 ] + c [ 0 ][ 2 ] + c [ 1 ][ 2 ] + c [ 2 ][ 2 ]; // para el fragmento en el canal i, establece el resultado fragmento [ i ] = r ; } devolver fragmento ; } void mainImage ( out vec4 fragColor , in vec2 fragCoord ) { // Coordenadas de píxeles normalizadas (de 0 a 1) vec2 uv = fragCoord / iResolution . xy ; // Convolución del kernel con textura vec3 col = convolución ( emboss , iChannel0 , uv ); // Salida a la pantalla fragColor = vec4 ( col , 1.0 ); }