如何用Python PIL通过内部四边形扭曲图像且不裁剪四边形外区域
使用Python PIL实现指定四点的透视扭曲(保留完整图像)
要实现将图像内部任意四边形映射为矩形,同时保留原图像的全部内容(不裁剪),核心是通过透视变换(Homography)完成坐标映射,并提前计算变换后的图像边界以确定输出尺寸。以下是完整的实现方案:
实现步骤与代码
1. 依赖库导入
需要用到Pillow和numpy(用于矩阵计算),先安装依赖:
pip install pillow numpy
2. 核心函数实现
from PIL import Image import numpy as np def compute_perspective_matrix(src_points, dst_points): """计算透视变换矩阵(3x3)""" A = [] for (x, y), (u, v) in zip(src_points, dst_points): A.append([x, y, 1, 0, 0, 0, -u*x, -u*y]) A.append([0, 0, 0, x, y, 1, -v*x, -v*y]) A = np.array(A, dtype=np.float32) B = np.array(dst_points).reshape(8, 1).astype(np.float32) # 求解线性方程组得到变换矩阵 matrix, _, _, _ = np.linalg.lstsq(A, B, rcond=None) return np.append(matrix, 1).reshape(3, 3) def get_transformed_bounds(image, matrix): """计算变换后图像的边界与尺寸,确保容纳全部内容""" width, height = image.size # 原图像四个角坐标 corners = [(0, 0), (width, 0), (width, height), (0, height)] transformed_corners = [] for x, y in corners: nx, ny, w = matrix @ np.array([x, y, 1]) transformed_corners.append((nx/w, ny/w)) xs = [p[0] for p in transformed_corners] ys = [p[1] for p in transformed_corners] min_x, max_x = min(xs), max(xs) min_y, max_y = min(ys), max(ys) # 输出尺寸取整,确保覆盖所有像素 new_width = int(np.ceil(max_x - min_x)) new_height = int(np.ceil(max_y - min_y)) # 偏移量:将变换后的图像移至正坐标区域 return (-min_x, -min_y), (new_width, new_height) def warp_to_rectangle(image_path, src_points, dst_rect): """ 将图像中指定四点映射为目标矩形,保留完整图像 :param image_path: 输入图像路径 :param src_points: 原始四点(顺时针/逆时针顺序,如左上、右上、右下、左下) :param dst_rect: 目标矩形,格式为(x, y, width, height) :return: 变换后的图像对象 """ img = Image.open(image_path).convert("RGB") # 生成目标矩形的四个顶点(与原始点顺序对应) dst_points = [ (dst_rect[0], dst_rect[1]), (dst_rect[0] + dst_rect[2], dst_rect[1]), (dst_rect[0] + dst_rect[2], dst_rect[1] + dst_rect[3]), (dst_rect[0], dst_rect[1] + dst_rect[3]) ] # 计算透视变换矩阵 perspective_matrix = compute_perspective_matrix(src_points, dst_points) # 获取变换后的边界与偏移 offset, new_size = get_transformed_bounds(img, perspective_matrix) # 构造平移矩阵,将变换后的图像移至正坐标区域 translation_matrix = np.array([ [1, 0, offset[0]], [0, 1, offset[1]], [0, 0, 1] ]) final_matrix = translation_matrix @ perspective_matrix # 转换为PIL要求的8元素矩阵(3x3矩阵的前8个元素) pil_matrix = final_matrix.flatten()[:8] # 执行透视变换,fillcolor为背景色(可改为透明(0,0,0,0)如果用RGBA模式) warped_img = img.transform( new_size, Image.PERSPECTIVE, pil_matrix, Image.BILINEAR, fillcolor=(255, 255, 255) ) return warped_img
3. 使用示例
# 原始图像内部的四个目标点(按顺时针顺序:左上、右上、右下、左下) src_points = [(120, 110), (310, 130), (300, 320), (110, 300)] # 目标矩形:(起始x, 起始y, 宽度, 高度) dst_rect = (60, 60, 220, 220) # 处理图像 result_img = warp_to_rectangle("input.jpg", src_points, dst_rect) # 保存结果 result_img.save("output.jpg") # 显示图像 result_img.show()
关键说明
- 点顺序一致性:原始四点和目标矩形的四点必须保持相同的顺序(如顺时针或逆时针),否则变换会出现错位。
- 背景色设置:如果需要透明背景,可将图像转为RGBA模式(
img.convert("RGBA")),并设置fillcolor=(0,0,0,0)。 - 插值方式:
Image.BILINEAR是平衡速度与质量的插值方式,也可改用Image.LANCZOS获得更高质量(但速度稍慢)。
内容的提问来源于stack exchange,提问作者Eric H.
相关产品推荐
相关产品推荐

