Saltar al contenido

Cómo encontrar esquinas en una imagen usando OpenCv

Por fin después de tanto luchar hemos dado con el resultado de este atolladero que tantos usuarios de nuestro sitio han presentado. Si tienes algo que aportar puedes aportar tu información.

Solución:

Primero, consulte /samples/c/squares.c en su distribución de OpenCV. Este ejemplo proporciona un detector cuadrado, y debería ser un buen comienzo sobre cómo detectar características similares a las esquinas. Luego, eche un vistazo a las funciones orientadas a características de OpenCV como cvCornerHarris () y cvGoodFeaturesToTrack ().

Los métodos anteriores pueden volver muchos características similares a las esquinas: la mayoría no serán las “true esquinas “que estás buscando. En mi aplicación, tenía que detectar cuadrados que habían sido rotados o sesgados (debido a la perspectiva). Mi canal de detección consistía en:

  1. Convertir de RGB a escala de grises (cvCvtColor)
  2. Suave (cvSmooth)
  3. Umbral (cvThreshold)
  4. Detectar bordes (cvCanny)
  5. Buscar contornos (cvFindContours)
  6. Contornos aproximados con características lineales (cvApproxPoly)
  7. Busque “rectángulos”, que eran estructuras que: tenían contornos poligonales que poseían 4 puntos, tenían un área suficiente, tenían bordes adyacentes de ~ 90 grados, tenían una distancia entre vértices “opuestos” de tamaño suficiente, etc.

El paso 7 era necesario porque una imagen ligeramente ruidosa puede producir muchas estructuras que parecen rectangulares después de la poligonalización. En mi aplicación, también tuve que lidiar con estructuras cuadradas que aparecían dentro o se superponían al cuadrado deseado. Encontré que la propiedad del área del contorno y el centro de gravedad son útiles para discernir el rectángulo adecuado.

A primera vista, para el ojo humano hay 4 esquinas. Pero en la visión por computadora, una esquina se considera un punto que tiene un gran cambio de gradiente en la intensidad en su vecindario. El vecindario puede ser un vecindario de 4 píxeles o un vecindario de 8 píxeles.

ingrese la descripción de la imagen aquí

En la ecuación proporcionada para encontrar el gradiente de intensidad, se ha considerado para vecindad de 4 píxeles VER DOCUMENTACIÓN.

Aquí está mi enfoque para la imagen en cuestión. También tengo el código en Python:

path = r'C:Usersselwyn77DesktopStackcorner'
filename = 'env.jpg'

img = cv2.imread(os.path.join(path, filename))
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)    #--- convert to grayscale 

Es una buena opción desenfocar siempre la imagen para eliminar los menos posibles cambios de degradado y conservar los más intensos. Opté por elegir el filtro bilateral que, a diferencia del filtro gaussiano, no difumina todos los píxeles del vecindario. Más bien difumina los píxeles que tienen una intensidad de píxel similar a la del píxel central. En resumen, conserva los bordes / esquinas de un cambio de gradiente alto pero difumina las regiones que tienen cambios de gradiente mínimos.

bi = cv2.bilateralFilter(gray, 5, 75, 75)
cv2.imshow('bi',bi)

ingrese la descripción de la imagen aquí

Para un humano, no es una gran diferencia en comparación con la imagen original. Pero importa. Ahora encontrando posibles rincones:

dst = cv2.cornerHarris(bi, 2, 3, 0.04)

dst devuelve un array (la misma forma 2D de la imagen) con valores propios obtenidos de la ecuación final mencionada AQUÍ.

Ahora se debe aplicar un umbral para seleccionar esas esquinas más allá de un cierto valor. Usaré el de la documentación:

#--- create a black image to see where those corners occur ---
mask = np.zeros_like(gray)

#--- applying a threshold and turning those pixels above the threshold to white ---           
mask[dst>0.01*dst.max()] = 255
cv2.imshow('mask', mask)

ingrese la descripción de la imagen aquí

Los píxeles blancos son regiones de posibles esquinas. Puedes encontrar muchos rincones vecinos entre sí.

Para dibujar las esquinas seleccionadas en la imagen:

img[dst > 0.01 * dst.max()] = [0, 0, 255]   #--- [0, 0, 255] --> Red ---
cv2.imshow('dst', img)

ingrese la descripción de la imagen aquí

(Los píxeles de color rojo son las esquinas, no tan visibles)

Para obtener un array de todos los píxeles con esquinas:

coordinates = np.argwhere(mask)

ACTUALIZAR

Variable coor es un array de matrices. Convirtiéndolo en una lista de listas

coor_list = [l.tolist() for l in list(coor)]

Conversión de lo anterior a una lista de tuplas

coor_tuples = [tuple(l) for l in coor_list]

Tengo una forma fácil y bastante ingenua de encontrar las 4 esquinas. Simplemente calculé la distancia de cada esquina a cada otra esquina. Conservé aquellos rincones cuya distancia excedía cierto umbral.

Aquí está el código:

thresh = 50

def distance(pt1, pt2):
    (x1, y1), (x2, y2) = pt1, pt2
    dist = math.sqrt( (x2 - x1)**2 + (y2 - y1)**2 )
    return dist

coor_tuples_copy = coor_tuples

i = 1    
for pt1 in coor_tuples:

    print(' I :', i)
    for pt2 in coor_tuples[i::1]:
        print(pt1, pt2)
        print('Distance :', distance(pt1, pt2))
        if(distance(pt1, pt2) < thresh):
            coor_tuples_copy.remove(pt2)      
    i+=1

Antes de ejecutar el fragmento anterior coor_tuples tenía todos los puntos de esquina:
[(4, 42),
(4, 43),
(5, 43),
(5, 44),
(6, 44),
(7, 219),
(133, 36),
(133, 37),
(133, 38),
(134, 37),
(135, 224),
(135, 225),
(136, 225),
(136, 226),
(137, 225),
(137, 226),
(137, 227),
(138, 226)]

Después de ejecutar el fragmento, me quedé con 4 esquinas:

[(4, 42), (7, 219), (133, 36), (135, 224)]

ACTUALIZACIÓN 2

Ahora todo lo que tienes que hacer es marcar estos 4 puntos en una copia de la imagen original.

img2 = img.copy()
for pt in coor_tuples:
    cv2.circle(img2, tuple(reversed(pt)), 3, (0, 0, 255), -1)
cv2.imshow('Image with 4 corners', img2) 

ingrese la descripción de la imagen aquí

Aquí hay una implementación usando cv2.goodFeaturesToTrack() para detectar esquinas. El enfoque es

  • Convertir imagen a escala de grises
  • Realice una detección de bordes astuta
  • Detectar esquinas
  • Opcionalmente, realice una transformación de perspectiva de 4 puntos para obtener una vista de arriba hacia abajo de la imagen

Usando esta imagen inicial,

ingrese la descripción de la imagen aquí

Después de convertir a escala de grises, realizamos una astuta detección de bordes

ingrese la descripción de la imagen aquí

Ahora que tenemos una imagen binaria decente, podemos usar cv2.goodFeaturesToTrack()

corners = cv2.goodFeaturesToTrack(canny, 4, 0.5, 50)

Para los parámetros, le damos la imagen astuta, establecemos el número máximo de esquinas en 4 (maxCorners), utilice una calidad mínima aceptada de 0,5 (qualityLevel), y establezca la distancia euclidiana mínima posible entre las esquinas devueltas en 50 (minDistance). Aqui esta el resultado

ingrese la descripción de la imagen aquí

Ahora que hemos identificado las esquinas, podemos realizar una transformación de perspectiva de 4 puntos para obtener una vista de arriba hacia abajo del objeto. Primero ordenamos los puntos en el sentido de las agujas del reloj y luego dibujamos el resultado en una máscara.

Nota: Podríamos haber encontrado contornos en la imagen de Canny en lugar de hacer este paso para crear la máscara, pero finge que solo teníamos los 4 puntos de esquina para trabajar.

ingrese la descripción de la imagen aquí

A continuación, encontramos contornos en esta máscara y filtramos usando cv2.arcLength() y cv2.approxPolyDP(). La idea es que si el contorno tiene 4 puntos, entonces debe ser nuestro objeto. Una vez que tenemos este contorno, realizamos una transformación de perspectiva

ingrese la descripción de la imagen aquí

Finalmente giramos la imagen en función de la orientación deseada. Aqui esta el resultado

ingrese la descripción de la imagen aquí

Código para solo detectar esquinas

import cv2

image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(gray, 120, 255, 1)

corners = cv2.goodFeaturesToTrack(canny,4,0.5,50)

for corner in corners:
    x,y = corner.ravel()
    cv2.circle(image,(x,y),5,(36,255,12),-1)

cv2.imshow('canny', canny)
cv2.imshow('image', image)
cv2.waitKey()

Código para detectar esquinas y realizar transformación de perspectiva

import cv2
import numpy as np

def rotate_image(image, angle):
    # Grab the dimensions of the image and then determine the center
    (h, w) = image.shape[:2]
    (cX, cY) = (w / 2, h / 2)

    # grab the rotation matrix (applying the negative of the
    # angle to rotate clockwise), then grab the sine and cosine
    # (i.e., the rotation components of the matrix)
    M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
    cos = np.abs(M[0, 0])
    sin = np.abs(M[0, 1])

    # Compute the new bounding dimensions of the image
    nW = int((h * sin) + (w * cos))
    nH = int((h * cos) + (w * sin))

    # Adjust the rotation matrix to take into account translation
    M[0, 2] += (nW / 2) - cX
    M[1, 2] += (nH / 2) - cY

    # Perform the actual rotation and return the image
    return cv2.warpAffine(image, M, (nW, nH))

def order_points_clockwise(pts):
    # sort the points based on their x-coordinates
    xSorted = pts[np.argsort(pts[:, 0]), :]

    # grab the left-most and right-most points from the sorted
    # x-roodinate points
    leftMost = xSorted[:2, :]
    rightMost = xSorted[2:, :]

    # now, sort the left-most coordinates according to their
    # y-coordinates so we can grab the top-left and bottom-left
    # points, respectively
    leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
    (tl, bl) = leftMost

    # now, sort the right-most coordinates according to their
    # y-coordinates so we can grab the top-right and bottom-right
    # points, respectively
    rightMost = rightMost[np.argsort(rightMost[:, 1]), :]
    (tr, br) = rightMost

    # return the coordinates in top-left, top-right,
    # bottom-right, and bottom-left order
    return np.array([tl, tr, br, bl], dtype="int32")

def perspective_transform(image, corners):
    def order_corner_points(corners):
        # Separate corners into individual points
        # Index 0 - top-right
        #       1 - top-left
        #       2 - bottom-left
        #       3 - bottom-right
        corners = [(corner[0][0], corner[0][1]) for corner in corners]
        top_r, top_l, bottom_l, bottom_r = corners[0], corners[1], corners[2], corners[3]
        return (top_l, top_r, bottom_r, bottom_l)

    # Order points in clockwise order
    ordered_corners = order_corner_points(corners)
    top_l, top_r, bottom_r, bottom_l = ordered_corners

    # Determine width of new image which is the max distance between 
    # (bottom right and bottom left) or (top right and top left) x-coordinates
    width_A = np.sqrt(((bottom_r[0] - bottom_l[0]) ** 2) + ((bottom_r[1] - bottom_l[1]) ** 2))
    width_B = np.sqrt(((top_r[0] - top_l[0]) ** 2) + ((top_r[1] - top_l[1]) ** 2))
    width = max(int(width_A), int(width_B))

    # Determine height of new image which is the max distance between 
    # (top right and bottom right) or (top left and bottom left) y-coordinates
    height_A = np.sqrt(((top_r[0] - bottom_r[0]) ** 2) + ((top_r[1] - bottom_r[1]) ** 2))
    height_B = np.sqrt(((top_l[0] - bottom_l[0]) ** 2) + ((top_l[1] - bottom_l[1]) ** 2))
    height = max(int(height_A), int(height_B))

    # Construct new points to obtain top-down view of image in 
    # top_r, top_l, bottom_l, bottom_r order
    dimensions = np.array([[0, 0], [width - 1, 0], [width - 1, height - 1], 
                    [0, height - 1]], dtype = "float32")

    # Convert to Numpy format
    ordered_corners = np.array(ordered_corners, dtype="float32")

    # Find perspective transform matrix
    matrix = cv2.getPerspectiveTransform(ordered_corners, dimensions)

    # Return the transformed image
    return cv2.warpPerspective(image, matrix, (width, height))

image = cv2.imread('1.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(gray, 120, 255, 1)

corners = cv2.goodFeaturesToTrack(canny,4,0.5,50)

c_list = []
for corner in corners:
    x,y = corner.ravel()
    c_list.append([int(x), int(y)])
    cv2.circle(image,(x,y),5,(36,255,12),-1)

corner_points = np.array([c_list[0], c_list[1], c_list[2], c_list[3]])
ordered_corner_points = order_points_clockwise(corner_points)
mask = np.zeros(image.shape, dtype=np.uint8)
cv2.fillPoly(mask, [ordered_corner_points], (255,255,255))

mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.015 * peri, True)
    if len(approx) == 4:
        transformed = perspective_transform(original, approx)

result = rotate_image(transformed, -90)

cv2.imshow('canny', canny)
cv2.imshow('image', image)
cv2.imshow('mask', mask)
cv2.imshow('transformed', transformed)
cv2.imshow('result', result)
cv2.waitKey()

Si aceptas, puedes dejar un artículo acerca de qué le añadirías a este enunciado.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

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