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).
A continuación, puede utilizar el corte Numpy para cortar la imagen.
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:
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:
¡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
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.