python 截取矩形 缩放,旋转

python

python 复制代码
import cv2
import numpy as np
import time

img = cv2.imread(r"C:\Users\ChanJing-01\Pictures\3_35_l.jpg")
if img is None:
    raise ValueError("图片路径错误")
clone = img.copy()

cx, cy = img.shape[1] // 2, img.shape[0] // 2
w, h = 300, 200
angle = 0

dragging = None
start_angle = 0
start_mouse_angle = 0


# ======================
# 工具函数
# ======================
def get_box_points(cx, cy, w, h, angle):
    rect = ((cx, cy), (w, h), angle)
    return cv2.boxPoints(rect).astype(np.float32)


def order_points(pts):
    """
    固定顺序:左上、右上、右下、左下
    """
    pts = np.array(pts, dtype=np.float32)
    rect = np.zeros((4, 2), dtype=np.float32)

    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]  # 左上
    rect[2] = pts[np.argmax(s)]  # 右下

    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]  # 右上
    rect[3] = pts[np.argmax(diff)]  # 左下

    return rect


def get_rotate_handle(cx, cy, w, h, angle):
    theta = np.radians(angle)
    rx = cx + (-np.sin(theta)) * (h / 2 + 40)
    ry = cy + ( np.cos(theta)) * (h / 2 + 40)
    return int(rx), int(ry)


def warp_crop():
    global cx, cy, w, h, angle

    rect = ((cx, cy), (w, h), angle)
    box = get_box_points(cx, cy, w, h, angle)
    box = order_points(box)

    # 自动真实宽高(避免翻转)
    widthA = np.linalg.norm(box[2] - box[3])
    widthB = np.linalg.norm(box[1] - box[0])
    maxW = int(max(widthA, widthB))

    heightA = np.linalg.norm(box[1] - box[2])
    heightB = np.linalg.norm(box[0] - box[3])
    maxH = int(max(heightA, heightB))

    dst = np.array([
        [0, 0],
        [maxW, 0],
        [maxW, maxH],
        [0, maxH]
    ], dtype=np.float32)

    M = cv2.getPerspectiveTransform(box, dst)
    warped = cv2.warpPerspective(img, M, (maxW, maxH))

    return warped


def draw():
    display = clone.copy()
    pts = get_box_points(cx, cy, w, h, angle)

    # 矩形
    cv2.polylines(display, [pts.astype(np.int32)], True, (0,255,0), 2)

    # 角点
    for p in pts:
        cv2.circle(display, tuple(p.astype(int)), 6, (0,0,255), -1)

    # 中心
    cv2.circle(display, (int(cx), int(cy)), 5, (255,0,0), -1)

    # 旋转手柄
    rx, ry = get_rotate_handle(cx, cy, w, h, angle)
    cv2.circle(display, (rx, ry), 6, (0,255,255), -1)
    cv2.line(display, (int(cx), int(cy)), (rx, ry), (0,255,255), 1)

    return display


# ======================
# 鼠标事件
# ======================
def mouse(event, x, y, flags, param):
    global cx, cy, w, h, angle, dragging
    global start_angle, start_mouse_angle

    pts = get_box_points(cx, cy, w, h, angle)
    rx, ry = get_rotate_handle(cx, cy, w, h, angle)

    if event == cv2.EVENT_LBUTTONDOWN:

        # 角点
        for i, p in enumerate(pts):
            if np.linalg.norm(np.array([x,y]) - p) < 10:
                dragging = i
                return

        # 中心
        if np.linalg.norm(np.array([x,y]) - np.array([cx,cy])) < 10:
            dragging = 'center'
            return

        # 旋转
        if np.linalg.norm(np.array([x,y]) - np.array([rx,ry])) < 12:
            dragging = 'rotate'
            start_angle = angle
            start_mouse_angle = np.degrees(np.arctan2(y - cy, x - cx))
            return

    elif event == cv2.EVENT_MOUSEMOVE:

        if dragging == 'center':
            cx, cy = x, y

        elif dragging == 'rotate':
            current = np.degrees(np.arctan2(y - cy, x - cx))
            angle = start_angle + (current - start_mouse_angle)

        elif isinstance(dragging, int):
            theta = np.radians(angle)

            dx = x - cx
            dy = y - cy

            local_x = dx * np.cos(theta) + dy * np.sin(theta)
            local_y = -dx * np.sin(theta) + dy * np.cos(theta)

            w = max(20, abs(local_x) * 2)
            h = max(20, abs(local_y) * 2)

    elif event == cv2.EVENT_LBUTTONUP:
        dragging = None


# ======================
# 初始化
# ======================
cv2.namedWindow("ROI")
cv2.setMouseCallback("ROI", mouse)


# ======================
# 主循环
# ======================
while True:
    cv2.imshow("ROI", draw())
    key = cv2.waitKey(1) & 0xFF

    # Enter预览
    if key == 13:
        cv2.imshow("crop_preview", warp_crop())

    # S保存
    elif key == ord('s'):
        warped = warp_crop()
        filename = f"crop_{int(time.time())}.png"
        cv2.imwrite(filename, warped)
        print("已保存:", filename)

    # 重置
    elif key == ord('r'):
        cx, cy = img.shape[1] // 2, img.shape[0] // 2
        w, h = 300, 200
        angle = 0

    # 退出
    elif key == 27:
        break

cv2.destroyAllWindows()
相关推荐
2301_814809861 小时前
PHP源码开发用二手硬件划算吗_性价比与稳定性权衡【操作】
jvm·数据库·python
Yyyyy123jsjs1 小时前
轻松通过Python调用外汇api获取汇率数据
开发语言·python
啦啦啦_99991 小时前
4. 网络编程
python
墨^O^1 小时前
C++ Memory Order 完全指南:从 relaxed 到 seq_cst,深入理解无锁编程与 happens-before
linux·开发语言·c++·笔记·学习·算法·缓存
阿荻在肝了2 小时前
Agent学习五:LangGraph学习-节点与可控性
人工智能·python·学习·agent
2301_782659182 小时前
C#怎么操作PostgreSQL数据库 C#如何用Npgsql连接和操作PostgreSQL进行数据读写【数据库】
jvm·数据库·python
2401_897190552 小时前
CSS如何处理层级混乱问题_利用z-index与Stacking Context原理
jvm·数据库·python
m0_748839492 小时前
Golang怎么实现配置校验_Golang如何在启动时检查必填配置项是否缺失【技巧】
jvm·数据库·python
西西弗Sisyphus2 小时前
在 Python 中使用 Pydantic 的 BaseModel 进行数据验证
python·pydantic·basemodel