Detector de bordes de Canny: teoría e implementación


La detección de bordes es una técnica ampliamente usada en procesamiento de imágenes para identificar los contornos de objetos dentro de una escena visual. En imágenes digitales, los bordes de intensidad aparecen en aquellas regiones donde la función de niveles de gris cambia de forma abrupta. Estos puntos de variación repentina, conocidos también como edgels (elementos de borde), contienen una gran cantidad de información visual porque su valor no se puede estimar fácilmente a partir de los píxeles que los rodean.
Esta dificultad para anticipar su intensidad los hace destacar frente al resto de la imagen y, por tanto, son cruciales para describir su contenido. De hecho, incluso representaciones reducidas en forma de simples dibujos de líneas permiten a los humanos reconocer objetos y escenarios con notable facilidad. Este fenómeno muestra que los bordes de intensidad no solo son importantes en la percepción visual humana, sino también en los sistemas de visión por computadora, donde capturar correctamente estas transiciones es esencial para interpretar lo que hay en una imagen.
Entre los distintos tipos de bordes de intensidad —como los bordes escalón (step edges), de línea, de techo o rampa— los bordes escalón son los más comunes y representativos. En una dimensión, este tipo de borde se manifiesta como un valor elevado en la primera derivada de la señal. En dos dimensiones, esta idea se generaliza al concepto de gradiente: una medida vectorial que indica tanto la dirección como la intensidad del cambio en la imagen.
Si bien existen múltiples formas de estimar el gradiente mediante filtros como Sobel, Prewitt, Scharr o derivados de Gauss, obtener bordes claros y bien definidos requiere más que simplemente calcular derivadas. El algoritmo de Canny aborda esta necesidad con un enfoque robusto y refinado, y se ha convertido en una de las técnicas más utilizadas para la detección de bordes. Diseñado con criterios precisos —buena detección, localización exacta y mínima respuesta múltiple—, sigue siendo una referencia clave en segmentación, reconocimiento de patrones y análisis de escenas.
Etapas del algoritmo de detección de bordes tipo Canny
Cálculo del gradiente de intensidad
La imagen se suaviza mediante un filtro Gaussiano para reducir el ruido. Luego se calculan las derivadas parciales en las direcciones horizontal y vertical Gₓ y Gy, a partir de las cuales se obtiene:
Magnitud del gradiente:
$$G_{\text{mag}} = \sqrt{Gx^2 + Gy^2}$$
Supresión de no-máximos con interpolación subpíxel
Para conservar únicamente los bordes más significativos, se suprimen los valores que no son máximos locales en la dirección del gradiente. En lugar de discretizar esta dirección en pocos ángulos (como 0°, 45°, 90°, 135°), se realiza una interpolación lineal entre los píxeles vecinos a lo largo de la dirección exacta del gradiente. Esta aproximación subpíxel mejora la precisión de la detección, eliminando bordes falsos y afinando las líneas detectadas.
Normalización de la respuesta
Tras la supresión, la imagen se normaliza a una escala de 0 a 255. Esta operación facilita la aplicación de umbrales en la etapa siguiente, garantizando una separación clara entre bordes fuertes y débiles.
Umbralización por histéresis
Se aplican dos umbrales:
Umbral alto (
T_high
): identifica los píxeles que forman parte de bordes fuertes.Umbral bajo (
T_low
): marca como candidatos a borde aquellos píxeles con respuesta intermedia, que podrían formar parte de un borde si están conectados a píxeles fuertes.La conectividad se propaga mediante un recorrido tipo BFS (cola FIFO), agrupando píxeles débiles que estén directa o indirectamente conectados con los fuertes. Esto permite cerrar contornos y preservar estructuras continuas.
Generación del mapa de bordes
El resultado es una imagen binaria en la que los bordes detectados se representan con intensidad máxima (255) y el fondo con 0. Solo sobreviven los píxeles que cumplieron con todos los criterios: magnitud alta, máximo local y conexión estructural.
def CannyLikeDetector(image: np.ndarray, sigma=1.0, tlow=0.1, thigh=0.3) -> np.ndarray:
Gx, Gy, Gmag, Gphase = ComputeImageGradient(image, sigma_s=sigma, sigma_d=sigma)
suppressed = NonMaximumSuppressionSubpixel(Gx, Gy, Gmag)
# Normalización post-supresión
norm_suppressed = np.clip((suppressed / suppressed.max()) * 255.0, 0, 255).astype(np.float32)
# Umbrales escalados
T_high = thigh * 255
T_low = tlow * 255
edges = HysteresisThresholdFIFO(norm_suppressed, T_high, T_low)
return edges
Supresión de no-máximos con interpolación subpíxel
Esta técnica busca conservar únicamente aquellos puntos cuya magnitud del gradiente es un verdadero máximo local en la dirección de mayor cambio de intensidad. A diferencia de métodos clásicos que discretizan la orientación en pocos ángulos, esta variante mejora la precisión al trabajar directamente con la dirección continua del gradiente, empleando interpolación bilineal.
Para cada píxel, se calcula el vector gradiente y se normaliza para obtener la dirección dx, dy. Luego, se estima la magnitud del gradiente en posiciones desplazadas hacia adelante y hacia atrás siguiendo esa dirección, utilizando interpolación bilineal sobre la imagen de magnitudes. A continuación, se comparan tres valores: el del píxel actual y los dos interpolados. Si el valor central es mayor o igual que ambos vecinos, se conserva; en caso contrario, se suprime.
def NonMaximumSuppressionSubpixel(Gx: np.ndarray, Gy: np.ndarray, Gmag: np.ndarray) -> np.ndarray:
rows, cols = Gmag.shape
output = np.zeros_like(Gmag, dtype=np.float32)
for i in range(1, rows - 1):
for j in range(1, cols - 1):
gx, gy = Gx[i, j], Gy[i, j]
mag = Gmag[i, j]
if gx == 0 and gy == 0:
continue
norm = np.hypot(gx, gy)
dx, dy = gx / norm, gy / norm
def interp(y, x):
x0, y0 = int(x), int(y)
x1 = min(x0 + 1, cols - 1)
y1 = min(y0 + 1, rows - 1)
a, b = x - x0, y - y0
return (
Gmag[y0, x0] * (1 - a) * (1 - b) +
Gmag[y0, x1] * a * (1 - b) +
Gmag[y1, x0] * (1 - a) * b +
Gmag[y1, x1] * a * b
)
mag1 = interp(i + dy, j + dx)
mag2 = interp(i - dy, j - dx)
if mag >= mag1 and mag >= mag2:
output[i, j] = mag
return output
La imagen resultante contiene únicamente los puntos que se destacan como máximos locales en la dirección del gradiente, representando con mayor fidelidad los bordes reales en la escena y favoreciendo una delineación más precisa y continua de los contornos.
Derecha: gradiente de la imagen, izquierda: imagen resultante de la supresión.
Umbralizado por histéresis
Después de la supresión de no-máximos, nos queda una imagen donde los bordes están bien localizados, pero aún pueden incluir ruido o detalles poco relevantes. Para decidir cuáles bordes conservar, Canny propuso usar dos umbrales:
Tₕ (umbral alto): cualquier píxel con una magnitud mayor o igual a este se considera un borde fuerte (válido).
Tₗ (umbral bajo): cualquier píxel con una magnitud entre Tₗ y Tₕ es un borde débil, que solo será aceptado si está conectado a un borde fuerte.
Todo lo que está por debajo de Tₗ se descarta por completo.
Este proceso evita tanto la pérdida de bordes importantes (si se usara un único umbral alto), como la aceptación de ruido (si se usara un único umbral bajo).
Umbralizado por histéresis con propagación BFS
El umbralizado por histéresis se encarga de refinar los bordes detectados eliminando aquellos que no estén suficientemente respaldados por la estructura global de la imagen. Para ello, clasifica los píxeles según dos umbrales: alto y bajo. Aquellos cuya magnitud supera el umbral alto se consideran bordes fuertes; los que están entre ambos umbrales, bordes débiles; y el resto se descarta.
La técnica aplica un proceso de propagación que preserva únicamente los bordes débiles conectados a bordes fuertes, lo que garantiza una mayor continuidad y reduce los falsos positivos. Esta propagación se realiza mediante un recorrido tipo búsqueda en anchura (BFS) utilizando una cola FIFO.
Primero, se identifican y almacenan todos los píxeles fuertes. Luego, para cada uno de ellos, se exploran sus vecinos en una conectividad de 8. Si alguno de estos vecinos corresponde a un píxel débil aún no marcado, se lo promueve a borde definitivo y se agrega a la cola para seguir expandiendo la conexión.
def HysteresisThresholdFIFO(image, T_high, T_low):
h, w = image.shape
strong = (image >= T_high)
weak = (image >= T_low) & ~strong
result = np.zeros_like(image, dtype=np.uint8)
queue = deque()
for y in range(h):
for x in range(w):
if strong[y, x]:
result[y, x] = 255
queue.append((x, y))
directions = [(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1)]
while queue:
x, y = queue.popleft()
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < w and 0 <= ny < h:
if weak[ny, nx] and result[ny, nx] == 0:
result[ny, nx] = 255
queue.append((nx, ny))
return result
Este mecanismo asegura que los contornos detectados sean coherentes y estén respaldados por una estructura significativa, permitiendo preservar bordes reales mientras se descartan aquellos aislados o espurios.
Resultados.
Antes de aplicar el detector de bordes de Canny, es recomendable realizar un preprocesamiento mediante técnicas de filtrado que suavicen la imagen y reduzcan el ruido. Esta etapa no solo evita la detección de bordes espurios causados por pequeñas variaciones locales, sino que también ayuda a que los contornos verdaderamente relevantes se presenten de forma más continua y estable. En particular, filtros como el gaussiano o variantes más avanzadas como el bilateral pueden mejorar significativamente la coherencia espacial de los bordes detectados en la etapa posterior.
El algoritmo de Canny ha perdurado como uno de los métodos más eficaces y usados en el campo del procesamiento de imágenes debido a su formulación precisa y sus decisiones fundamentadas en principios matemáticos y perceptuales. A través de un enfoque estructurado que incluye suavizado gaussiano, cálculo de gradientes, supresión de no-máximos e histéresis con umbrales duales, logra detectar bordes bien localizados, continuos y con bajo nivel de falsos positivos.
La versión moderna del detector de Canny emplea técnicas avanzadas como la supresión de no-máximos con interpolación subpíxel y la propagación por histéresis basada en una cola FIFO. Estas estrategias refinan la localización de los bordes y mejoran su continuidad, incluso en escenas con transiciones suaves o estructuras poco definidas.
En la imagen de la derecha se aplico el algoritmo original, en la imagen de la izquierda se aplico el algoritmo optimizado.
Más allá de su efectividad práctica, el estudio del algoritmo de Canny revela cómo decisiones algorítmicas cuidadosamente diseñadas —como el uso conjunto de magnitud, orientación y conectividad— pueden reproducir con notable precisión la percepción humana de los contornos. Por ello, Canny no solo representa un hito histórico en visión por computadora, sino que sigue siendo una herramienta vigente y valiosa en aplicaciones modernas como análisis de imágenes, visión artificial y preprocesamiento para modelos de aprendizaje automático.
Subscribe to my newsletter
Read articles from Francisco Zavala directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
