最小外接矩形的裁剪与坐标的还原
做视觉类算法时候,有时候需要对最小外接矩形进行裁剪,往往出现的是旋转斜矩形,这里给出程序与效果。如图所示,我们现在要裁剪橘色的区域

1.程序与效果
#裁剪图片 from typing import List, Tuple import cv2 import numpy as np def get_minarea_rect_crop(img, points): bounding_box = cv2.minAreaRect(np.array(points).astype(np.int32)) points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0]) index_a, index_b, index_c, index_d = 0, 1, 2, 3 if points[1][1] > points[0][1]: index_a = 0 index_d = 1 else: index_a = 1 index_d = 0 if points[3][1] > points[2][1]: index_b = 2 index_c = 3 else: index_b = 3 index_c = 2 box = [points[index_a], points[index_b], points[index_c], points[index_d]] crop_img,M = get_rotate_crop_image(img, np.array(box)) return crop_img,M def get_rotate_crop_image(img, points):##points:[[x1,y1],[x2,y2],......] ''' img_height, img_width = img.shape[0:2] left = int(np.min(points[:, 0])) right = int(np.max(points[:, 0])) top = int(np.min(points[:, 1])) bottom = int(np.max(points[:, 1])) img_crop = img[top:bottom, left:right, :].copy() points[:, 0] = points[:, 0] - left points[:, 1] = points[:, 1] - top ''' assert len(points) == 4, "shape of points must be 4*2" img_crop_width = int( max( np.linalg.norm(points[0] - points[1]), np.linalg.norm(points[2] - points[3]))) img_crop_height = int( max( np.linalg.norm(points[0] - points[3]), np.linalg.norm(points[1] - points[2]))) pts_std = np.float32([[0, 0], [img_crop_width, 0], [img_crop_width, img_crop_height], [0, img_crop_height]]) M = cv2.getPerspectiveTransform(points, pts_std) dst_img = cv2.warpPerspective( img, M, (img_crop_width, img_crop_height), borderMode=cv2.BORDER_REPLICATE, flags=cv2.INTER_CUBIC) dst_img_height, dst_img_width = dst_img.shape[0:2] if dst_img_height * 1.0 / dst_img_width >= 1.5: dst_img = np.rot90(dst_img) return dst_img,M def contours_to_original(contours_dst: List[np.ndarray], M: np.ndarray, dst_shape: Tuple[int, int], rotated: bool = False) -> List[np.ndarray]: M_inv = np.linalg.inv(M) center = np.array([dst_shape[1] / 2, dst_shape[0] / 2], dtype=np.float32) rot_mat = np.array([[0, 1], [-1, 0]], dtype=np.float32) if rotated else None contours_original = [] for cnt in contours_dst: # 关键修正:将 (N,1,2) 转为 (N,2) cnt = cnt.reshape(-1, 2).astype(np.float32) # 旋转处理(如果需要) if rotated: cnt = (cnt - center) @ rot_mat.T + center # 齐次坐标变换 ones = np.ones((cnt.shape[0], 1), dtype=np.float32) cnt_hom = np.hstack([cnt, ones]) cnt_original_hom = cnt_hom @ M_inv.T cnt_original = cnt_original_hom[:, :2] / cnt_original_hom[:, 2:] contours_original.append(cnt_original.astype(np.float32)) # 转换为整数 contours_original_int = [np.round(c).astype(np.int32) for c in contours_original] return contours_original_int def yuv_iou(img=None, img_path=None,lower_range = np.array([0, 0, 0]),upper_range = np.array([255, 123, 255])): if img is not None: image = img else: if img_path == None: raise Exception("img与img_path参数不能都为None,请传入参数") # 读取图像 image = cv2.imread(img_path) # 将图像转换为YUV颜色空间 yuv_img = cv2.cvtColor(image, cv2.COLOR_BGR2YUV) # 创建掩码 mask = cv2.inRange(yuv_img, lower_range, upper_range) return mask def draw_contours(image, contours, output_folder=None): drawn_image = np.copy(image) # 绘制轮廓 # cv2.drawContours(drawn_image, contours, -1, (128, 0, 128), 1) # -1 表示绘制所有轮廓 cv2.drawContours(drawn_image, contours, -1, (255, 255, 255), 2) # -1 表示绘制所有轮廓,绘制白色 return drawn_image if __name__ == '__main__': image_path=r'crop_yellow.png' image=cv2.imread(image_path,1) mask=yuv_iou(img=image, img_path=None,lower_range = np.array([0, 0, 0]),upper_range = np.array([255, 123, 255])) contours,_=cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) points=np.squeeze(contours[0]) #<class 'numpy.ndarray'> [ [x1,y1] [x2,y2] ... ] crop_img,M=get_minarea_rect_crop(image, points) #传入点坐标,得到最小外接矩形图(橙色区域图) cv2.imwrite("crop_rect.png",crop_img) #保存橙色区域图,如果只是裁剪图,到这里就结束了 #下面补充在橙色矩形图crop_img中找绿色区域的轮廓坐标,再将坐标还原到最初始的crop_yellow.png大图中去,并绘制出来 #获取crop_img绿色部分的二值图 crop_rect_mask=yuv_iou(img=crop_img, img_path=None,lower_range = np.array([0, 0, 0]),upper_range = np.array([255, 255, 126])) cv2.imwrite("./green.png",crop_rect_mask) contours,_ = cv2.findContours(crop_rect_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) draw_crop_rect=draw_contours(crop_img, contours, output_folder=None) cv2.imwrite("./draw_crop_rect.png",draw_crop_rect) #坐标变换,还原到初始图中 dst_shape=image.shape[:2] contours_original_int=contours_to_original(contours,M,dst_shape,rotated= False) #过滤掉一个点的轮廓 # contours_original_int = [c.astype(np.int32) for c in contours_original_int if len(c) >=3] draw_image = draw_contours(image, contours_original_int, output_folder=None) cv2.imwrite("./draw_image.png", draw_image)
裁剪下的图如下:

程序里使用yuv提取了斜矩形区域,得到轮廓点后获取最小外接矩形的四个点,通过透视变换矩阵旋转正。

进一步在裁剪下来的橘色矩形中找到绿色区域后,然后将坐标还原的原始的蓝色背景的大图中,这里绘制出来是正确的。


小结:简单记录了旋转矩形的裁剪与坐标的还原的程序,调用即可,不多解释。

浙公网安备 33010602011771号