5.13. Filtros lineales y de vecindad¶
Las operaciones de aritmética de píxeles vistas antes en el capítulo combinaban dos imágenes punto por punto. Los filtros realizan un trabajo relacionado de una forma distinta: calculan el valor de cada píxel de salida a partir de una pequeña vecindad de píxeles de entrada que rodea la posición correspondiente. La salida en (x, y) es algún estadístico – el promedio, la mediana, el valor más frecuente – de los píxeles de entrada contenidos en una pequeña caja centrada en (x, y).
Ese pequeño cambio de planteamiento – pasar de un píxel cada vez a una ventana de píxeles cada vez – es lo que hace funcionar a toda una familia de operaciones útiles. Un simple promedio sobre una ventana pequeña suaviza el ruido del sensor. La mediana sobre la misma ventana elimina el moteado de un solo píxel sin difuminar tanto los bordes. Un promedio bilateral se niega a suavizar a través de los límites de fuerte contraste, preservando los bordes de los objetos mientras limpia las texturas de su interior. La vecindad es la unidad de trabajo; la elección del estadístico decide lo que hace el filtro.
5.13.1. El tamaño del kernel¶
Cada filtro de vecindad recibe un parámetro size que establece el radio de la ventana en píxeles. La ventana en sí es cuadrada y abarca (2 * size + 1) píxeles por lado – de modo que size=1 significa una vecindad de 3 por 3, size=2 significa 5 por 5, size=3 significa 7 por 7, y así sucesivamente.
La vecindad se desplaza por la imagen un píxel cada vez, de arriba a la izquierda hacia abajo a la derecha. Cada píxel de salida es el resultado de aplicar el estadístico del filtro a la vecindad de entrada centrada en él.¶
Tamaños mayores significan vecindades mayores, lo que significa un filtrado más suave (o más agresivo). El coste crece con el área de la ventana, así que un filtro con size=3 realiza aproximadamente nueve veces más trabajo por píxel que un filtro con size=1. El valor predeterminado práctico para la mayoría de las tareas de limpieza es size=1 o size=2; recurre a tamaños mayores solo cuando las vecindades pequeñas no sean suficientes para suprimir la característica que la aplicación intenta suprimir.
5.13.2. El filtro de media¶
mean() reemplaza cada píxel por el promedio aritmético de su vecindad. El resultado suaviza la variación de píxel a píxel a lo largo del tamaño de la ventana, lo que lo convierte en la forma más económica de suprimir el moteado del ruido del sensor: la variación de alta frecuencia se promedia, el contenido de baja frecuencia sobrevive.
El inconveniente es que los bordes y otras características nítidas también se promedian. Un borde brillante que medía un píxel de ancho antes del filtro mide dos o tres píxeles de ancho después de un filtro de media con size=1, con el brillo atenuado en los hombros. Para la pura reducción de ruido en una imagen pobre en texturas (una pared limpia, el interior de un marcador de color) el compromiso es aceptable. Para una escena con mucho detalle donde importan los bordes, uno de los siguientes filtros suele encajar mejor.
img.mean(1) # 3x3 box average -- fast, gentle smoothing
img.mean(2) # 5x5 box average -- stronger, slower
5.13.3. Mediana, moda, punto medio¶
Los otros tres filtros estadísticos de vecindad cambian el simple promedio aritmético por algo más robusto frente a los valores atípicos.
median() devuelve la mediana de la vecindad – el valor que queda en el medio de la lista ordenada de los píxeles de la ventana. Un único píxel muy brillante o muy oscuro en la ventana no arrastra la mediana; simplemente se convierte en uno de los extremos descartados. El efecto práctico es que el filtrado de mediana elimina el moteado de un solo píxel y el ruido de sal y pimienta sin difuminar los bordes como hace mean. El coste es más cómputo por píxel – ordenar una ventana es más lento que promediarla – y el resultado no es estrictamente un promedio, lo que a veces importa para los cálculos posteriores.
Un parámetro percentile (por defecto 0.5) desplaza el valor elegido fuera de la mediana estricta. percentile=0.0 devuelve el mínimo de la vecindad, percentile=1.0 el máximo; los valores intermedios eligen proporcionalmente entre ellos dentro de la ventana ordenada. Eso le da a median la capacidad de enfatizar las partes oscuras o brillantes de la vecindad sin perder la robustez frente a valores atípicos del estadístico de orden.
mode() devuelve el valor más frecuente de la vecindad. Es útil cuando el modelo de ruido es «la mayoría de los píxeles son correctos, unos pocos se han corrompido en diversos grados», donde la respuesta correcta es el valor que aparece con más frecuencia – algo que la mediana puede pasar por alto cuando los valores corrompidos se acumulan en un lado de la ventana ordenada.
midpoint() devuelve una combinación ponderada del mínimo y el máximo de la vecindad – bias=0.5 da el punto medio entre ellos, bias=0.0 da el mínimo, bias=1.0 da el máximo. Se usa menos que los demás, pero vale la pena conocerlo cuando el objetivo es específicamente extraer características oscuras o brillantes.
5.13.4. Bilateral, la versión que preserva los bordes¶
bilateral() es el filtro de vecindad que más vale la pena comprender bien. Produce el efecto de suavizado de mean(), pero con una restricción adicional: cuanto más difiere un píxel de la vecindad del píxel central, menos cuenta en el promedio. El resultado suaviza el interior de cada región uniforme sin sangrar a través de los bordes que las separan, que es exactamente lo que la mayoría de las aplicaciones realmente quieren.
Dos parámetros controlan con cuánta agresividad descuenta píxeles el filtro:
color_sigmadecide cómo afecta la diferencia de color a la ponderación. Valores más pequeños hacen que el filtro sea más estricto a la hora de descontar los píxeles que difieren del centro.space_sigmadecide cómo afecta la distancia espacial a la ponderación. Valores más pequeños dan más peso a los píxeles cercanos al centro.
Los valores predeterminados (color_sigma=0.1, space_sigma=1.0) son puntos de partida razonables; ajustarlos suele ser cuestión de ejecutar el filtro sobre un fotograma de muestra y modificarlos hasta que los bordes queden nítidos y los interiores limpios.
Bilateral es más costoso que median() y significativamente más costoso que mean(), así que vale la pena recurrir a él solo cuando el comportamiento que preserva los bordes es lo que la aplicación necesita.
5.13.5. Umbralización adaptativa¶
Los filtros de media, mediana, moda y punto medio llevan todos el mismo par de argumentos con nombre que convierten su salida en un umbral binario:
threshold=Truecambia el filtro al modo de umbralización.offset=Ndesplaza el corte local enNunidades antes de la comparación.
El mecanismo se basa directamente en el comportamiento ordinario del filtro. Sin threshold=True, el filtro calcula su estadístico sobre la vecindad y escribe ese estadístico en el píxel de salida. Con threshold=True, el filtro calcula el mismo estadístico, luego compara el píxel de origen en la misma posición con el estadístico más el desplazamiento, y escribe el valor máximo del formato si el origen es mayor, y cero en caso contrario.
El resultado es una imagen binaria cuyo corte se mueve con el brillo local a lo largo del fotograma. Las regiones brillantes obtienen un corte alto, las regiones tenues obtienen un corte bajo, y un píxel de primer plano que es localmente más brillante que sus vecinos coincide tanto si está en una región brillante como en una tenue – que es exactamente el comportamiento que un único umbral global no podría producir en una imagen iluminada de forma desigual.
img.mean(3, threshold=True, offset=5)
El parámetro offset es donde la aplicación controla cuán estricta es la prueba. Un pequeño desplazamiento positivo exige que el píxel de origen sea apreciablemente más brillante que sus vecinos antes de contar como coincidencia, lo que suprime los falsos positivos del ruido del sensor a costa de descartar primer plano débil. Un pequeño desplazamiento negativo captura primer plano débil a costa de dejar pasar algo de ruido. La elección depende de lo que el resto de la canalización vaya a hacer con la salida binaria.
Bajo iluminación desigual, un único umbral global no puede describir el primer plano en cada posición. Un filtro de vecindad ejecutado con threshold=True produce un corte que se mueve con el brillo local y clasifica el primer plano correctamente en todo el fotograma.¶
La familia de filtros ejecuta el umbral adaptativo, así que elegir el filtro correcto importa: mean() para el umbral adaptativo más económico, median() cuando la entrada tiene ruido de sal y pimienta que el filtro debe rechazar antes de calcular el corte local.