Filtro Gaussiano y convolución en el procesamiento de imágenes: fundamentos y aplicaciones

Francisco ZavalaFrancisco Zavala
11 min read

La convolución es una de las operaciones esenciales en el procesamiento de imágenes. Permite aplicar filtros espaciales para lograr diversos efectos, como el suavizado, la detección de bordes o el realce de detalles. En términos generales, consiste en combinar dos señales: una imagen, entendida como una señal bidimensional, y un kernel (también llamado máscara o plantilla), para generar una tercera señal: el resultado del filtrado.

¿Qué es la convolución?

La convolución puede entenderse como un procedimiento en el que un pequeño conjunto de valores (el kernel), se desliza a lo largo de una señal (una secuencia de números) y, en cada posición, realiza una operación de multiplicación y suma. Por ejemplo, si deseamos promediar cada valor con sus dos vecinos, podemos usar un kernel sencillo como [1, 1, 1]. Al aplicarlo a una señal como [ 1, 5, 6, 7], se multiplica el kernel con cada ventana de tres valores superpuestos de la señal, se suman los productos y se divide entre la suma de los pesos (en este caso, 3), para obtener un nuevo valor. El proceso se repite desplazando el kernel por toda la señal.

convolución.

$$[2 ,4 ,6 ,4.333]$$

La convolución discreta de una señal f(x) con un kernel g(x) se define como:

$$(f * g)(x) = \sum_{i=-w}^{w} f(x - i) \cdot g(i)$$

Donde.

w es la mitad del ancho del kernel (asumiendo que tiene un número impar de elementos), y g(i) es el valor del kernel centrado en el índice 0. Este centrado es fundamental para que el resultado de la convolución esté alineado correctamente respecto a la entrada original. sin embargo comportamiento de la convolución depende en gran medida del tipo de kernel utilizado. En procesamiento de imágenes, los kernels más comunes pueden clasificarse en dos grupos: suavizantes y diferenciadores.

Kernels de suavizado (low-pass filters)

Los kernels de suavizado se encargan de promediar los valores dentro de una vecindad local, reduciendo así las variaciones abruptas debidas al ruido. Estos kernels actúan como filtros pasa-bajas, eliminando los detalles de alta frecuencia en la imagen. Son especialmente útiles en la etapa de preprocesamiento**,** cuando se busca restaurar una imagen que ha sido degradada por ruido. Un ejemplo clásico es el filtro de caja (o promedio simple), donde cada píxel es reemplazado por el promedio de sus vecinos.

$$\sum_i g_i = 1$$

Esto garantiza que el nivel de gris promedio de la imagen no se vea alterado por el filtro.

Kernels de diferenciación (high-pass filters)

Por otro lado, los kernels de diferenciación están diseñados para resaltar los cambios abruptos en la señal, como los bordes o contornos de los objetos. Estos cambios suelen indicar transiciones entre regiones, lo que resulta valioso para tareas como la detección de bordes o la segmentación de objetos. Los kernels diferenciadores funcionan como filtros pasa-altas, ya que realzan las componentes de alta frecuencia. A diferencia de los suavizantes, estos kernels tienen la propiedad de que la suma de sus elementos es cero.

$$\sum_i g_i = 0$$

Esto refleja el hecho de que la derivada de una señal constante (sin cambios) debe ser cero.

Suavizado mediante Convolución con un kernel Gaussiano

Un kernel gaussiano es una función matemática que se utiliza ampliamente en procesamiento de imágenes, particularmente para suavizar o desenfocar una imagen. Este kernel se basa en la distribución normal, también conocida como la campana de Gauss, que describe cómo los valores de la imagen se suavizan al promediar los píxeles vecinos. Sin embargo, al trabajar con imágenes digitales, la versión continua de la función gaussiana debe ser discretizada para ser utilizada de manera efectiva.

Paso 1: Discretización de la Función Gaussiana

La fórmula para una función gaussiana continua es:

$$\text{gauss}(x) = \frac{1}{\sqrt{2\pi s^2}} \exp \left( -\frac{i^2}{2s^2} \right)$$

Donde s es la desviación estándar. Esta es una función continua con la propiedad importante de que el área bajo la curva (es decir, la integral) es igual a 1. Cuando se necesita trabajar con una versión discreta de esta función para utilizarla en un kernel de convolución, se muestrea la función en puntos discretos, generalmente centrados alrededor de cero. La función gaussiana discreta para un índice i se calcula como:

$$\text{gauss}(i) = \exp\left( -\frac{i^2}{2s^2} \right)$$

Aquí, i es el índice discreto que recorre una vecindad de puntos. Sin embargo, al discretizar la función, no se incluye el término de normalización, lo que significa que los valores calculados para cada i no sumarán 1. Este paso puede ser adecuado si solo se busca una representación aproximada de la forma de la distribución gaussiana, pero para aplicaciones como la convolución, donde se requiere que el kernel sea normalizado, es necesario un paso adicional.

Paso 2: Normalización

Es crucial normalizar los valores del kernel gaussiano discreto para asegurarse de que la suma de todos los valores del kernel sea 1. Esto garantiza que no se altere la intensidad de la imagen durante el proceso de convolución. La normalización se hace sumando todos los valores del kernel y dividiendo cada valor por esta suma. En notación:

$$\text{norm_gauss}(i) = \frac{\text{gauss}(i)}{\sum_{i} \text{gauss}(i)}$$

Esto asegura que el kernel tenga un efecto de suavizado que no cambie el brillo global de la imagen.

Paso 3: Elección del Tamaño del Kernel (Ancho del Kernel)

El tamaño del kernel gaussiano depende de la desviación estándar s. En general, se recomienda que el ancho del kernel, denotado por w, sea aproximadamente 2.5 veces la desviación estándar. Es decir:

$$w \approx 2.5s$$

Esto asegura que el kernel cubra la mayor parte de la distribución gaussiana. Según la propiedad de la distribución gaussiana, el 68.27% de la distribución está contenido en el rango [−s,s], el 95.45% está contenido en el rango [−2s,2s] y el 99.7% está contenido en el rango [−3s,3s]. De ahí que se use w ≈ 2.5s para capturar el 98.76% de la distribución bajo la curva gaussiana.

Paso 4: Construcción del Kernel Discreto

Una vez que se ha determinado el valor adecuado de w (el ancho del kernel), se construye el kernel en el dominio discreto. Este kernel es un vector o matriz cuyos valores se calculan como los valores de la función gaussiana para cada índice ii en el rango [-w, w]. Es común que el kernel sea simétrico, lo que significa que el valor en i es igual al valor en -i, reflejando la simetría de la distribución gaussiana.

El siguiente código describe cómo construir un kernel gaussiano discreto normalizado:

def CreateGaussianKernel(sigma):
"""
Generates a 1D Gaussian kernel based on a given standard deviation (sigma).

Parameters:
- sigma (float): The standard deviation of the Gaussian function.

Returns:
- gauss (numpy.ndarray): The normalized 1D Gaussian kernel.

"""

# Step 1: Determine the half-width of the kernel
    half_width = GetKernelHalfWidth(sigma)

# Step 2: Compute the full width (must be an odd number)
    w = 2 * half_width + 1

# Step 3: Initialize the kernel and normalization factor
    gauss = np.zeros(w, dtype=np.float32)
    norm = 0.0

# Step 4: Compute Gaussian values and accumulate normalization factor
    for i in range(w):
        x = i - half_width # Shift the index to center around 0
        gauss[i] = np.exp(- (x**2) / (2 * sigma**2))
        norm += gauss[i]


# Step 5: Normalize the kernel so that its sum equals 1
    gauss /= norm

    return gauss

Convolución 2D

Antes de abordar la convolución bidimensional, es útil entender cómo funciona su versión unidimensional. La convolución 1D se aplica comúnmente a señales en una sola dimensión, pero también se utiliza como parte de técnicas más avanzadas, como la convolución separable, en la que una operación 2D se descompone en dos pasos 1D más eficientes: uno horizontal y otro vertical. Esta estrategia no solo reduce el costo computacional, sino que también permite un análisis más claro del comportamiento del filtro en cada dirección.

Alt text: Diagram illustrating 1D convolution of a 7-element image array and a 3-element kernel. It shows step-by-step calculations that produce a 5-element output array: 12, 26, 38, 32, and 20.

Extensión de la convolución 1D a 2D

Hasta ahora, gran parte de la discusión sobre convolución se ha presentado en el contexto de señales unidimensionales. Aunque este caso es más simple para el análisis matemático, en procesamiento de imágenes nos interesa principalmente trabajar con señales bidimensionales.

Afortunadamente, la extensión de la convolución al caso 2D es directa. Dada una imagen I(x, y) y un kernel bidimensional G(i, j), la convolución se define como:

$$I_r(x, y) = (I * G)(x, y) = \sum_{i=0}^{w-1} \sum_{j=0}^{h-1} I(x + w_0 - i,, y + h_0 - j) \cdot G(i, j)$$

Donde:

  • I(x, y): imagen de entrada .

  • G(i,j): kernel (o filtro) bidimensional.

  • Iᵣ(x, y): valor del píxel en la imagen resultante (filtrada) en la posición $(x,y)$.

  • w y h: ancho y alto del kernel.

  • centro del kernel (la posición alrededor de la cual se aplican los valores del filtro).

    $$w_0 = [\frac{w}{2}] \quad h_0 = [\frac{h}{2}]$$

  • La operación se realiza desplazando el kernel sobre la imagen y, en cada posición, multiplicando elemento a elemento y sumando los resultados.

def ConvolveSeparable(I, gh, gv):
    """
    Applies separable convolution to the input image I using 1D kernels gh (horizontal) and gv (vertical).

    Parameters:
    - I: 2D numpy array representing the input grayscale image.
    - gh: 1D numpy array representing the horizontal convolution kernel.
    - gv: 1D numpy array representing the vertical convolution kernel.

    Returns:
    - Itmp: 2D numpy array representing the image after applying both convolutions.
            Note: The returned image is the intermediate result after vertical convolution,
            with padding removed.
    """

    height, width = I.shape
    w = len(gh)
    pad_size = w // 2

    # Step 1: Apply padding to the input image
    I_padded = np.pad(I, ((pad_size, pad_size), (pad_size, pad_size)), mode='reflect')

    # Step 2: Horizontal convolution
    Itmp = np.zeros_like(I_padded, dtype=np.float32)
    for y in range(height):
        for x in range(width):
            val = 0
            for i in range(w):
                val += gh[i] * I_padded[y + pad_size, x + i]
            Itmp[y + pad_size, x + pad_size] = val

    # Step 3: Vertical convolution
    Ir = np.zeros_like(I_padded, dtype=np.float32)
    for y in range(height):
        for x in range(width):
            val = 0
            for i in range(w):
                val += gv[i] * Itmp[y + i, x + pad_size]
            Ir[y + pad_size, x + pad_size] = val

    # Remove padding (currently only returning the intermediate result)
    Ir = Ir[pad_size:-pad_size, pad_size:-pad_size]
    return Ir

El padding es esencial para conservar las dimensiones originales de la imagen y asegurar que el kernel se aplique de manera uniforme en toda la imagen, incluyendo los bordes. Sin padding, la salida sería una versión reducida de la imagen original.

Animation showing a 3x3 teal square grid overlapping different sections of a larger 6x6 blue grid, illustrating a sliding window technique, with dashed lines indicating the larger grid area.convlo

Este enfoque de convolución 2D constituye la base de muchos filtros en procesamiento de imágenes, como el suavizado, la detección de bordes y el realce.

Implementaciones

Las señales, ya sean de audio, imagen o vídeo, suelen estar contaminadas por ruido, que se manifiesta como fluctuaciones o alteraciones no deseadas que no forman parte de la información relevante. Este ruido puede originarse de diversas fuentes, como interferencias externas, limitaciones del equipo de adquisición o incluso la transmisión de datos. La presencia de ruido puede hacer que la interpretación de las señales se vuelva más compleja, dificultando tareas como el análisis, la identificación de patrones y la toma de decisiones automáticas. Por lo tanto, la capacidad de eliminar o atenuar el ruido es esencial para mejorar la calidad y precisión de las señales procesadas, permitiendo obtener resultados más claros y confiables.

Se aplica un filtro Gaussiano a la imagen en escala de grises para realizar un suavizado básico, cuyo propósito es reducir el ruido sin distorsionar significativamente la imagen.

Two black-and-white images of a cat are shown, with "Original" and "Smoothed" labels. The "Original" image is more pixelated. Each image is accompanied by a histogram representing pixel intensity distribution. The "Original" histogram shows two high peaks at both ends, while the "Smoothed" histogram has a more distributed range with multiple peaks.

El filtrado es ampliamente utilizado en etapas de preprocesamiento, ya que atenúa el contenido de alta frecuencia y facilita posteriores tareas como la detección de bordes. En un la siguiente imagen se muestra cómo un preprocesamiento con suavizado previo permite obtener bordes mucho más limpios y precisos al aplicar un detector como el de Canny; sin esta etapa, el ruido podría generar bordes falsos y resultados desordenados.

Outline drawing of a woman in a hat facing forward, shown in two side-by-side sections. The image appears to be a contour line representation highlighting facial features and hat details.

El filtrado Gaussiano también encuentra aplicaciones más creativas, como en el desenfoque artístico de imágenes. En este caso, mediante el uso de segmentación, se logra mantener nítido el objeto principal mientras se desenfoca el fondo, dirigiendo la atención del espectador y otorgando a la imagen un acabado profesional y cinematográfico.

A woman with long dark hair is wearing a wide-brimmed hat adorned with a feather. She is looking over her shoulder with a neutral expression. The background is softly blurred.

La implementación de filtros es una parte esencial en el procesamiento de señales e imágenes, ya que permite mejorar la calidad de los datos al reducir el ruido y resaltar la información relevante. Sin embargo, no existe un único filtro que sea adecuado para todos los casos en este articulo se reviso el uso del filtro Gaussiano, pero cada tipo de ruido posee características distintivas, y dependiendo de la situación, puede ser necesario utilizar diferentes estrategias de filtrado. Por ejemplo, mientras que un filtro Gaussiano es excelente para suavizar variaciones suaves y eliminar ruido aleatorio de alta frecuencia, otros tipos de filtros, como los filtros de mediana o los filtros adaptativos, pueden ser más efectivos frente a ruidos impulsivos o patrones de distorsión más complejos. Por ello, seleccionar el filtro adecuado requiere entender tanto la naturaleza del ruido como los objetivos específicos del procesamiento que se desea realizar.

Puedes encontrar el código fuente en el siguiente repositorio.

0
Subscribe to my newsletter

Read articles from Francisco Zavala directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Francisco Zavala
Francisco Zavala