Saltar al contenido

¿Cómo enderezar un área rectangular rotada de una imagen usando OpenCV en Python?

Solución:

Puedes usar el warpAffine función para rotar la imagen alrededor de un punto central definido. La matriz de rotación adecuada se puede generar usando getRotationMatrix2D (dónde theta es en grados).

Imagen de inicio

Después de encontrar el rectángulo deseado

A continuación, puede utilizar el corte Numpy para cortar la imagen.

Imagen rotada Resultado

import cv2
import numpy as np

def subimage(image, center, theta, width, height):

   ''' 
   Rotates OpenCV image around center with angle theta (in deg)
   then crops the image according to width and height.
   '''

   # Uncomment for theta in radians
   #theta *= 180/np.pi

   shape = ( image.shape[1], image.shape[0] ) # cv2.warpAffine expects shape in (length, height)

   matrix = cv2.getRotationMatrix2D( center=center, angle=theta, scale=1 )
   image = cv2.warpAffine( src=image, M=matrix, dsize=shape )

   x = int( center[0] - width/2  )
   y = int( center[1] - height/2 )

   image = image[ y:y+height, x:x+width ]

   return image

Manten eso en mente dsize es la forma del producción imagen. Si el parche / ángulo es lo suficientemente grande, los bordes se cortan (compare la imagen de arriba) si usa la forma original como, por razones de simplicidad, se hizo arriba. En este caso, podría introducir un factor de escala para shape (para ampliar la imagen de salida) y el punto de referencia para cortar (aquí center).

La función anterior se puede utilizar de la siguiente manera:

image = cv2.imread('owl.jpg')
image = subimage(image, center=(110, 125), theta=30, width=100, height=200)
cv2.imwrite('patch.jpg', image)

Tuve problemas con compensaciones incorrectas al usar las soluciones aquí y en preguntas similares.

Así que hice los cálculos y se me ocurrió la siguiente solución que funciona:

def subimage(self,image, center, theta, width, height):
    theta *= 3.14159 / 180 # convert to rad

    v_x = (cos(theta), sin(theta))
    v_y = (-sin(theta), cos(theta))
    s_x = center[0] - v_x[0] * ((width-1) / 2) - v_y[0] * ((height-1) / 2)
    s_y = center[1] - v_x[1] * ((width-1) / 2) - v_y[1] * ((height-1) / 2)

    mapping = np.array([[v_x[0],v_y[0], s_x],
                        [v_x[1],v_y[1], s_y]])

    return cv2.warpAffine(image,mapping,(width, height),flags=cv2.WARP_INVERSE_MAP,borderMode=cv2.BORDER_REPLICATE)

Como referencia, aquí hay una imagen que explica las matemáticas detrás de ella:

Tenga en cuenta que

w_dst = width-1
h_dst = height-1

Esto se debe a que la última coordenada tiene el valor width-1 y no width, o height.

Los otros métodos funcionarán solo si el contenido del rectángulo está en la imagen rotada después de la rotación y fallará gravemente en otras situaciones.. ¿Qué pasa si se pierde parte de la pieza? Vea un ejemplo a continuación:

ingrese la descripción de la imagen aquí

Si va a recortar el área de texto del rectángulo girado con el método anterior,

import cv2
import numpy as np


def main():
    img = cv2.imread("big_vertical_text.jpg")
    cnt = np.array([
            [[64, 49]],
            [[122, 11]],
            [[391, 326]],
            [[308, 373]]
        ])
    print("shape of cnt: {}".format(cnt.shape))
    rect = cv2.minAreaRect(cnt)
    print("rect: {}".format(rect))

    box = cv2.boxPoints(rect)
    box = np.int0(box)

    print("bounding box: {}".format(box))
    cv2.drawContours(img, [box], 0, (0, 0, 255), 2)

    img_crop, img_rot = crop_rect(img, rect)

    print("size of original img: {}".format(img.shape))
    print("size of rotated img: {}".format(img_rot.shape))
    print("size of cropped img: {}".format(img_crop.shape))

    new_size = (int(img_rot.shape[1]/2), int(img_rot.shape[0]/2))
    img_rot_resized = cv2.resize(img_rot, new_size)
    new_size = (int(img.shape[1]/2)), int(img.shape[0]/2)
    img_resized = cv2.resize(img, new_size)

    cv2.imshow("original contour", img_resized)
    cv2.imshow("rotated image", img_rot_resized)
    cv2.imshow("cropped_box", img_crop)

    # cv2.imwrite("crop_img1.jpg", img_crop)
    cv2.waitKey(0)


def crop_rect(img, rect):
    # get the parameter of the small rectangle
    center = rect[0]
    size = rect[1]
    angle = rect[2]
    center, size = tuple(map(int, center)), tuple(map(int, size))

    # get row and col num in img
    height, width = img.shape[0], img.shape[1]
    print("width: {}, height: {}".format(width, height))

    M = cv2.getRotationMatrix2D(center, angle, 1)
    img_rot = cv2.warpAffine(img, M, (width, height))

    img_crop = cv2.getRectSubPix(img_rot, size, center)

    return img_crop, img_rot


if __name__ == "__main__":
    main()

Esto es lo que obtendrás:

ingrese la descripción de la imagen aquí

¡Aparentemente, algunas de las partes están cortadas! ¿Por qué no deformar directamente el rectángulo girado ya que podemos obtener sus cuatro puntos de esquina con cv.boxPoints() ¿método?

import cv2
import numpy as np


def main():
    img = cv2.imread("big_vertical_text.jpg")
    cnt = np.array([
            [[64, 49]],
            [[122, 11]],
            [[391, 326]],
            [[308, 373]]
        ])
    print("shape of cnt: {}".format(cnt.shape))
    rect = cv2.minAreaRect(cnt)
    print("rect: {}".format(rect))

    box = cv2.boxPoints(rect)
    box = np.int0(box)
    width = int(rect[1][0])
    height = int(rect[1][1])

    src_pts = box.astype("float32")
    dst_pts = np.array([[0, height-1],
                        [0, 0],
                        [width-1, 0],
                        [width-1, height-1]], dtype="float32")
    M = cv2.getPerspectiveTransform(src_pts, dst_pts)
    warped = cv2.warpPerspective(img, M, (width, height))

Ahora la imagen recortada se convierte en

ingrese la descripción de la imagen aquí

Mucho mejor, ¿no? Si lo comprueba con atención, notará que hay un área negra en la imagen recortada. Esto se debe a que una pequeña parte del rectángulo detectado está fuera del límite de la imagen. Para remediar esto, puede rellenar un poco la imagen y hacer el recorte después de eso. Hay un ejemplo ilustrado en esta respuesta.

Ahora, comparamos los dos métodos para recortar el rectángulo girado de la imagen. Este método no requiere rotar la imagen y puede lidiar con este problema de manera más elegante con menos código.

¡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 *