Implementar Canny Edge Detector en Python usando OpenCV

En este artículo, aprenderemos el funcionamiento del popular algoritmo de detección de bordes Canny desarrollado por John F. Canny en 1986. Por lo general, en Matlab y OpenCV usamos la detección de bordes canny para muchas tareas populares en la detección de bordes, como la detección de carriles, la creación de bocetos. , eliminación de bordes, ahora aprenderemos el funcionamiento interno y la implementación de este algoritmo desde cero.
 

Comprensión teórica

Los pasos básicos involucrados en este algoritmo son: 
 

  • Reducción de ruido mediante filtro gaussiano 
     
  • Cálculo de gradiente a lo largo del eje horizontal y vertical 
     
  • Supresión no máxima de bordes falsos 
     
  • Umbral doble para segregar bordes fuertes y débiles 
     
  • Seguimiento de bordes por histéresis

Ahora vamos a entender estos conceptos en detalle: 
 

1. Reducción de ruido mediante filtro gaussiano

Este paso es de suma importancia en la detección de bordes Canny. Utiliza un filtro gaussiano para la eliminación de ruido de la imagen, ya que este ruido puede asumirse como bordes debido al cambio repentino de intensidad por parte del detector de bordes. La suma de los elementos en el núcleo gaussiano es 1, por lo que el núcleo debe normalizarse antes de aplicar la convolución a la imagen. En este tutorial, usaremos un kernel de tamaño 5 X 5 y sigma = 1.4, que desenfocará la imagen y eliminará el ruido. La ecuación para el núcleo del filtro gaussiano es
 

2. Cálculo de gradiente

Cuando se suaviza la imagen, las derivadas Ix e Iy se calculan respecto al eje xey. Se puede implementar utilizando la convolución de kernels de Sobel-Feldman con la imagen como se indica:
 

Núcleos Sobel

después de aplicar estos kernel, podemos usar las magnitudes de gradiente y el ángulo para seguir procesando este paso. La magnitud y el ángulo se pueden calcular como
 

Magnitud y ángulo del gradiente

3. Supresión no máxima

Este paso tiene como objetivo reducir los píxeles fusionados duplicados a lo largo de los bordes para hacerlos desiguales. Para cada píxel, encuentre dos vecinos en las direcciones de gradiente positivo y negativo, suponiendo que cada vecino ocupa el ángulo de pi /4, y 0 es la dirección directamente a la derecha. Si la magnitud del píxel actual es mayor que la magnitud de los vecinos, nada cambia; de lo contrario, la magnitud del píxel actual se establece en cero.
 

4. Doble Umbral

Las magnitudes de gradiente se comparan con dos valores de umbral especificados, el primero es más bajo que el segundo. Los gradientes que son más pequeños que el valor de umbral bajo se suprimen, los gradientes más altos que el valor de umbral alto se marcan como fuertes y los píxeles correspondientes se incluyen en el mapa de borde final. Todos los demás gradientes se marcan como débiles y los píxeles correspondientes a estos gradientes se consideran en el siguiente paso.
 

5. Seguimiento de bordes usando histéresis

Dado que un píxel de borde débil causado por bordes verdaderos se conectará a un píxel de borde fuerte, el píxel W con gradiente débil se marca como borde y se incluye en el mapa de borde final si y solo si está involucrado en el mismo componente conectado que algún píxel S con fuerte pendiente. En otras palabras, debe haber una string de píxeles vecinos débiles que conecten W y S (los vecinos están a 8 píxeles alrededor del considerado). Inventaremos e implementaremos un algoritmo que encuentre todos los componentes conectados del mapa de degradado considerando cada píxel solo una vez. Después de eso, puede decidir qué píxeles se incluirán en el mapa de borde final.
A continuación se muestra la implementación. 
 

Python3

import numpy as np
import os
import cv2
import matplotlib.pyplot as plt
  
   
# defining the canny detector function
   
# here weak_th and strong_th are thresholds for
# double thresholding step
def Canny_detector(img, weak_th = None, strong_th = None):
      
    # conversion of image to grayscale
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
       
    # Noise reduction step
    img = cv2.GaussianBlur(img, (5, 5), 1.4)
       
    # Calculating the gradients
    gx = cv2.Sobel(np.float32(img), cv2.CV_64F, 1, 0, 3)
    gy = cv2.Sobel(np.float32(img), cv2.CV_64F, 0, 1, 3)
      
    # Conversion of Cartesian coordinates to polar 
    mag, ang = cv2.cartToPolar(gx, gy, angleInDegrees = True)
       
    # setting the minimum and maximum thresholds 
    # for double thresholding
    mag_max = np.max(mag)
    if not weak_th:weak_th = mag_max * 0.1
    if not strong_th:strong_th = mag_max * 0.5
      
    # getting the dimensions of the input image  
    height, width = img.shape
       
    # Looping through every pixel of the grayscale 
    # image
    for i_x in range(width):
        for i_y in range(height):
               
            grad_ang = ang[i_y, i_x]
            grad_ang = abs(grad_ang-180) if abs(grad_ang)>180 else abs(grad_ang)
               
            # selecting the neighbours of the target pixel
            # according to the gradient direction
            # In the x axis direction
            if grad_ang<= 22.5:
                neighb_1_x, neighb_1_y = i_x-1, i_y
                neighb_2_x, neighb_2_y = i_x + 1, i_y
              
            # top right (diagonal-1) direction
            elif grad_ang>22.5 and grad_ang<=(22.5 + 45):
                neighb_1_x, neighb_1_y = i_x-1, i_y-1
                neighb_2_x, neighb_2_y = i_x + 1, i_y + 1
              
            # In y-axis direction
            elif grad_ang>(22.5 + 45) and grad_ang<=(22.5 + 90):
                neighb_1_x, neighb_1_y = i_x, i_y-1
                neighb_2_x, neighb_2_y = i_x, i_y + 1
              
            # top left (diagonal-2) direction
            elif grad_ang>(22.5 + 90) and grad_ang<=(22.5 + 135):
                neighb_1_x, neighb_1_y = i_x-1, i_y + 1
                neighb_2_x, neighb_2_y = i_x + 1, i_y-1
              
            # Now it restarts the cycle
            elif grad_ang>(22.5 + 135) and grad_ang<=(22.5 + 180):
                neighb_1_x, neighb_1_y = i_x-1, i_y
                neighb_2_x, neighb_2_y = i_x + 1, i_y
               
            # Non-maximum suppression step
            if width>neighb_1_x>= 0 and height>neighb_1_y>= 0:
                if mag[i_y, i_x]<mag[neighb_1_y, neighb_1_x]:
                    mag[i_y, i_x]= 0
                    continue
   
            if width>neighb_2_x>= 0 and height>neighb_2_y>= 0:
                if mag[i_y, i_x]<mag[neighb_2_y, neighb_2_x]:
                    mag[i_y, i_x]= 0
   
    weak_ids = np.zeros_like(img)
    strong_ids = np.zeros_like(img)              
    ids = np.zeros_like(img)
       
    # double thresholding step
    for i_x in range(width):
        for i_y in range(height):
              
            grad_mag = mag[i_y, i_x]
              
            if grad_mag<weak_th:
                mag[i_y, i_x]= 0
            elif strong_th>grad_mag>= weak_th:
                ids[i_y, i_x]= 1
            else:
                ids[i_y, i_x]= 2
       
       
    # finally returning the magnitude of
    # gradients of edges
    return mag
   
frame = cv2.imread('food.jpeg')
  
# calling the designed function for
# finding edges
canny_img = Canny_detector(frame)
   
# Displaying the input and output image  
plt.figure()
f, plots = plt.subplots(2, 1) 
plots[0].imshow(frame)
plots[1].imshow(canny_img)

Imagen de entrada

Imagen de salida

Publicación traducida automáticamente

Artículo escrito por infoaryan y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *