moments findcontours extent cvapproxpoly contours boxpoints python image-processing opencv

python - findcontours - opencv contour approximation methods



¿Cómo enderezar un área de rectángulo girado de una imagen usando opencv en python? (5)

La siguiente imagen te dirá lo que quiero.

Tengo la información de los rectángulos en la imagen, ancho, altura, punto central y grado de rotación. Ahora, quiero escribir un guión para recortarlos y guardarlos como imagen, pero enderezarlos. Al igual que en, quiero pasar del rectángulo que se muestra dentro de la imagen al rectángulo que se muestra afuera.

Estoy usando OpenCV python, por favor dígame una manera de lograr esto.

Muestra amablemente algún código, ya que los ejemplos de OpenCV Python son difíciles de encontrar.


Esta es mi versión de C ++ que realiza la misma tarea. Me he dado cuenta de que es un poco lento. Si alguien ve algo que pueda mejorar el rendimiento de esta función, hágamelo saber. :)

bool extractPatchFromOpenCVImage( cv::Mat& src, cv::Mat& dest, int x, int y, double angle, int width, int height) { // obtain the bounding box of the desired patch cv::RotatedRect patchROI(cv::Point2f(x,y), cv::Size2i(width,height), angle); cv::Rect boundingRect = patchROI.boundingRect(); // check if the bounding box fits inside the image if ( boundingRect.x >= 0 && boundingRect.y >= 0 && (boundingRect.x+boundingRect.width) < src.cols && (boundingRect.y+boundingRect.height) < src.rows ) { // crop out the bounding rectangle from the source image cv::Mat preCropImg = src(boundingRect); // the rotational center relative tot he pre-cropped image int cropMidX, cropMidY; cropMidX = boundingRect.width/2; cropMidY = boundingRect.height/2; // obtain the affine transform that maps the patch ROI in the image to the // dest patch image. The dest image will be an upright version. cv::Mat map_mat = cv::getRotationMatrix2D(cv::Point2f(cropMidX, cropMidY), angle, 1.0f); map_mat.at<double>(0,2) += static_cast<double>(width/2 - cropMidX); map_mat.at<double>(1,2) += static_cast<double>(height/2 - cropMidY); // rotate the pre-cropped image. The destination image will be // allocated by warpAffine() cv::warpAffine(preCropImg, dest, map_mat, cv::Size2i(width,height)); return true; } // if else { return false; } // else } // extractPatch


Receta similar para la versión 3.4.0 de openCV.

from cv2 import cv import numpy as np def getSubImage(rect, src): # Get center, size, and angle from rect center, size, theta = rect # Convert to int center, size = tuple(map(int, center)), tuple(map(int, size)) # Get rotation matrix for rectangle M = cv2.getRotationMatrix2D( center, theta, 1) # Perform rotation on src image dst = cv2.warpAffine(src, M, src.shape[:2]) out = cv2.getRectSubPix(dst, size, center) return out img = cv2.imread(''img.jpg'') # Find some contours thresh2, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # Get rotated bounding box rect = cv2.minAreaRect(contours[0]) # Extract subregion out = getSubImage(rect, img) # Save image cv2.imwrite(''out.jpg'', out)


Tuve problemas de compensaciones incorrectas con las soluciones publicadas 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)

Para 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 la height .

Si hay preguntas sobre las matemáticas, pregúnteles como comentarios e intentaré responderlas.


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

Si va a recortar el área de texto del rectángulo girado utilizando 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:

Al parecer, 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 el método cv.boxPoints() ?

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 es así? Si verifica cuidadosamente, 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 la imagen un poco 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 resolver este problema de manera más elegante con menos código.


Puede usar la función warpAffine para rotar la imagen alrededor de un punto central definido. La matriz de rotación adecuada se puede generar utilizando getRotationMatrix2D (donde theta está en grados ).

A continuación, puede utilizar el corte de 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

Tenga en cuenta que dsize es la forma de la imagen de salida . Si el parche / ángulo es lo suficientemente grande, los bordes se recortan (compare la imagen de arriba) si se usa la forma original como, para simplificar, se hace arriba. En este caso, podría introducir un factor de escala a la shape (para ampliar la imagen de salida) y el punto de referencia para el corte (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)