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()
相关推荐
喵了几个咪5 分钟前
GoWind Flow 拆解:一个 Go 工作流引擎与它的双模式设计器
开发语言·人工智能·后端·golang
2601_9679801125 分钟前
win10如何把python加入环境变量
python·win10·安装教程·命令行·环境变量
꧁细听勿语情꧂31 分钟前
二叉树余题,排序、直接插入排序、希尔排序
c语言·开发语言·数据结构·算法
是main不是漫1 小时前
【C++——类和对象(上)】筑基篇:以封装奠定面向对象之基
c语言·开发语言·c++
apcipot_rain5 小时前
C++转Java日记(06)
java·开发语言
锋行天下8 小时前
langgraph 后端发自定义数据,前端接收方法概括
人工智能·python
benchmark_cc10 小时前
用 QuantDash 读取单只股票五档盘口:抽样、缺失值检查与复现记录
python·数据分析·pandas·量化交易·股票数据·quantdash
曲径海盗10 小时前
【Python 学习第 55 天】列表基础
python·学习笔记·编程入门
专注仿真10 小时前
知识问答模型用到的算法集详解及代码实例
python·算法·代码·大模型训练·控制算法
jyOverQ11 小时前
我写了一个 CSDN Markdown 图片助手:告别本地图片逐张上传
python·工具·csdn