You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于无序矩形顶点实现物体正确横屏旋转?

解决矩形物体横屏校正中的180度翻转问题

问题背景

训练的模型可检测矩形物体及其角点,目标是将物体调整为横屏方向,但现有逻辑存在错误,偶尔会导致物体180度翻转。核心问题是主物体的vertices顶点顺序不固定(非始终为顺时针),导致竖屏物体的旋转逻辑失效。

原实现代码

def save_poly_crop(vertices, img, vertex_top_left, vertex_top_right):
    img = np.array(img) 
    # RGB to BGR 
    # img = img[:, :, ::-1].copy()

    tag_area = list(cv2.minAreaRect(vertices))
    tag_area[0] = list(tag_area[0])
    tag_area[1] = list(tag_area[1])

    # get width and height of the detected rectangle
    # THIS IS FLIPPED:
    width = int(tag_area[1][0])
    height = int(tag_area[1][1])
    rotate_90_ccw = False
    rotate_90 = False
    if width < height:
        if vertex_top_left is not None and vertex_top_right is not None:
            if vertex_top_left[1] > vertex_top_right[1]:
                rotate_90 = True
            else:
                rotate_90_ccw = True
        else:
            rotate_90 = True
    
    box = cv2.boxPoints(tag_area)
    box = np.int0(box)

    src_pts = box.astype("float32")
    
    # coordinate of the points in box points after the rectangle has been
    # straightened
    dst_pts = np.array([[0, height-1],
                        [0, 0],
                        [width-1, 0],
                        [width-1, height-1]], dtype="float32")
    
    # the perspective transformation matrix
    M = cv2.getPerspectiveTransform(src_pts, dst_pts)

    # directly warp the rotated rectangle to get the straightened rectangle
    warped = cv2.warpPerspective(img, M, (width, height))
    
    if rotate_90:
        warped = cv2.rotate(warped, cv2.ROTATE_90_CLOCKWISE)
    if rotate_90_ccw:
        warped = cv2.rotate(warped, cv2.ROTATE_90_COUNTERCLOCKWISE)

    return warped

失效案例

正常顶点顺序(逻辑生效)

vertices = array([[  55, 1006],
       [  67,  357],
       [  484,  364],
       [  473, 1013]])
rectangle_top_left = array([[ -38, 1005],
       [ -38,  893],
       [  73,  893],
       [  73, 1005]])
vertex_top_left = cv2.minAreaRect(rectangle_top_left)[0]

rectangle_top_right = array([[-20, 464],
       [-18, 352],
       [ 93, 354],
       [ 91, 466]])
vertex_top_right = cv2.minAreaRect(rectangle_top_right)[0]

旋转失效的顶点顺序

vertices = array([[ 706,  751],
       [ 866,  726],
       [ 907,  986],
       [ 747, 1011]])
rectangle_top_left = array([[837, 707],
       [889, 699],
       [897, 752],
       [845, 759]])
vertex_top_left = cv2.minAreaRect(rectangle_top_left)[0]

rectangle_top_right = array([[ 870,  967],
       [ 922,  960],
       [ 930, 1012],
       [ 877, 1020]])

vertex_top_right = cv2.minAreaRect(rectangle_top_right)[0]

注:vertex_top_right和vertex_top_left只需接近对应角点,无需与主物体顶点完全重合。

修复后的代码

def save_poly_crop(vertices, img, vertex_top_left, vertex_top_right):
    img = np.array(img) 

    # 获取最小外接矩形,统一处理顶点顺序
    tag_area = cv2.minAreaRect(vertices)
    box = cv2.boxPoints(tag_area)
    box = np.int0(box)

    # 对box顶点按顺时针排序,消除原始顶点顺序影响
    def sort_clockwise(points):
        # 计算重心
        center = np.mean(points, axis=0)
        # 计算每个点相对于重心的角度
        angles = np.arctan2(points[:, 1] - center[1], points[:, 0] - center[0])
        # 按角度排序(顺时针)
        sorted_indices = np.argsort(angles)
        return points[sorted_indices]
    
    sorted_box = sort_clockwise(box)
    src_pts = sorted_box.astype("float32")

    # 获取矩形的真实宽高,确保width为较长边
    width = int(tag_area[1][0])
    height = int(tag_area[1][1])
    if width < height:
        width, height = height, width

    # 目标点:横屏状态下的顺时针顶点(左上、右上、右下、左下)
    dst_pts = np.array([[0, 0],
                        [width-1, 0],
                        [width-1, height-1],
                        [0, height-1]], dtype="float32")
    
    # 计算透视变换矩阵并执行变换
    M = cv2.getPerspectiveTransform(src_pts, dst_pts)
    warped = cv2.warpPerspective(img, M, (width, height))

    # 利用检测到的两个上角点校正180度翻转问题
    if vertex_top_left is not None and vertex_top_right is not None:
        # 将角点映射到变换后的坐标系
        corner_pts = np.array([vertex_top_left, vertex_top_right], dtype="float32")
        transformed_corners = cv2.perspectiveTransform(corner_pts.reshape(-1, 1, 2), M).reshape(-1, 2)
        
        # 横屏状态下左上角x应小于右上角x,否则执行180度翻转
        if transformed_corners[0][0] > transformed_corners[1][0]:
            warped = cv2.rotate(warped, cv2.ROTATE_180)
    
    return warped

关键修改说明

  • 统一顶点顺序:新增sort_clockwise函数,通过重心和角度计算对矩形顶点进行顺时针排序,彻底消除原始顶点顺序混乱的影响。
  • 固定宽高定义:强制width为矩形的较长边,确保变换后基础图像是横屏方向的候选。
  • 基于角点的方向校正:将检测到的两个上角点通过透视变换映射到新坐标系,判断其相对位置,若顺序颠倒则执行180度翻转,保证最终图像方向正确。

内容的提问来源于stack exchange,提问作者Niko Gamulin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 07:10:11