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()