最小外接矩形的裁剪与坐标的还原

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

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提取了斜矩形区域,得到轮廓点后获取最小外接矩形的四个点,通过透视变换矩阵旋转正。

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

 

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

posted @ 2025-07-21 13:28  wancy  阅读(39)  评论(0)    收藏  举报