使用 Pillow 将矩形映射到四边形
Mapping a rectangle to a quad with Pillow
我正在尝试编写一个 Python 程序来获取输入图像(例如 JPEG)并生成“地球仪组件”输出图像,类似于 le Paper Globe。本质上,如果输出图像经过打印、剪切、折叠和粘合,应该得到投影到粗糙球体上的原始图像。
该程序会将输入图像分成 32 个(水平 8 个,垂直 4 个)矩形,然后将每个矩形映射到一些精心挑选的梯形或更一般的四边形上。我找到了 Pillow/PIL 方法 that maps a quad onto a square,但找不到将矩形映射到四边形的方法。
有谁知道如何在 Python 中将输入图像的矩形映射到输出图像的四边形上? 我更喜欢 Pillow/PIL, 但任何可以打开和保存 JPEG 的库都可以。
基本上,您需要进行一些透视变换才能完成此操作。 Pillow Image.transform
for that. You'd need to calculate all necessary parameters beforehand, i.e. the homographic transform, cf. this Q&A. I personally would use OpenCV's warpPerspective
, and get the transformation matrix by using getPerspectiveTransform
, such that you only need to provide four points in the source image, and four points in the destination image. 在这方面有一个很好的快速入门。
在我们进入细节之前,我只是想确定以下是您想要实现的目标:
所以,完整的算法是:
- 使用 Pillow 加载源图像和具有一些四边形的专用输出图像。我假设是白色背景上的黑色四边形。
- 将图像转换为 NumPy 数组以便能够与 OpenCV 一起使用。
- 设置源点。这些只是您感兴趣区域 (ROI) 的角落。
- 找到——或知道——目的地。这些是四边形的角。自动查找这些可能会变得非常困难,因为顺序必须与为 ROI 点设置的顺序相同。
- 获取变换矩阵,并应用实际的透视变换。
- 将变形图像的所需部分复制到初始输出图像的四边形。
- 转换回一些枕头图像并保存。
而且,这是完整的代码,包括一些可视化:
import cv2
import numpy as np
from PIL import Image, ImageDraw
# Input image to get rectangle (region of interest, roi) from
image = Image.open('path/to/your/image.png')
roi = ((100, 30), (300, 200))
# Dummy output image with some quad to paste to
output = Image.new('RGB', (600, 800), (255, 255, 255))
draw = ImageDraw.Draw(output)
draw.polygon(((100, 20), (40, 740), (540, 350), (430, 70)), outline=(0, 0, 0))
# Convert images to NumPy arrays for processing in OpenCV
image_cv2 = np.array(image)
output_cv2 = np.array(output)
# Source points, i.e. roi in input image
tl = (roi[0][0], roi[0][1])
tr = (roi[1][0], roi[0][1])
br = (roi[1][0], roi[1][1])
bl = (roi[0][0], roi[1][1])
pts = np.array([bl, br, tr, tl])
# Find (or know) target points in output image w.r.t. the quad
# Attention: The order must be the same as defined by the roi points!
tl_dst = (100, 20)
tr_dst = (430, 70)
br_dst = (540, 350)
bl_dst = (40, 740)
dst_pts = np.array([bl_dst, br_dst, tr_dst, tl_dst])
# Get transformation matrix, and warp image
pts = np.float32(pts.tolist())
dst_pts = np.float32(dst_pts.tolist())
M = cv2.getPerspectiveTransform(pts, dst_pts)
image_size = (output_cv2.shape[1], output_cv2.shape[0])
warped = cv2.warpPerspective(image_cv2, M, dsize=image_size)
# Get mask from quad in output image, and copy content from warped image
gray = cv2.cvtColor(output_cv2, cv2.COLOR_BGR2GRAY)
gray = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV)[1]
cnts = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
mask = np.zeros_like(output_cv2)
mask = cv2.drawContours(mask, cnts, 0, (255, 255, 255), cv2.FILLED)
mask = mask.all(axis=2)
output_cv2[mask, :] = warped[mask, :]
# Transform back to PIL images
output_new = Image.fromarray(output_cv2)
output_new.save('final_output.jpg')
# Just for visualization
import matplotlib.pyplot as plt
draw = ImageDraw.Draw(image)
draw.rectangle(roi, outline=(255, 0, 0), width=3)
plt.figure(0, figsize=(18, 9))
plt.subplot(1, 3, 1), plt.imshow(image), plt.title('Input with ROI')
plt.subplot(1, 3, 2), plt.imshow(output), plt.title('Output with quad')
plt.subplot(1, 3, 3), plt.imshow(output_new), plt.title('Final output')
plt.tight_layout(), plt.show()
在第 4 步,自动寻找目标点,你可以这样做:
# Find target points in output image w.r.t. the quad
gray = cv2.cvtColor(output_cv2, cv2.COLOR_BGR2GRAY)
gray = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV)[1]
cnts = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
approx = cv2.approxPolyDP(cnts[0], 0.03 * cv2.arcLength(cnts[0], True), True)
这基本上是在图像中找到轮廓,并逼近角点。您仍然需要找到结果点的正确顺序...
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.8.5
Matplotlib: 3.3.3
NumPy: 1.19.5
OpenCV: 4.5.1
Pillow: 8.1.0
----------------------------------------
我正在尝试编写一个 Python 程序来获取输入图像(例如 JPEG)并生成“地球仪组件”输出图像,类似于 le Paper Globe。本质上,如果输出图像经过打印、剪切、折叠和粘合,应该得到投影到粗糙球体上的原始图像。
该程序会将输入图像分成 32 个(水平 8 个,垂直 4 个)矩形,然后将每个矩形映射到一些精心挑选的梯形或更一般的四边形上。我找到了 Pillow/PIL 方法 that maps a quad onto a square,但找不到将矩形映射到四边形的方法。
有谁知道如何在 Python 中将输入图像的矩形映射到输出图像的四边形上? 我更喜欢 Pillow/PIL, 但任何可以打开和保存 JPEG 的库都可以。
基本上,您需要进行一些透视变换才能完成此操作。 Pillow Image.transform
for that. You'd need to calculate all necessary parameters beforehand, i.e. the homographic transform, cf. this Q&A. I personally would use OpenCV's warpPerspective
, and get the transformation matrix by using getPerspectiveTransform
, such that you only need to provide four points in the source image, and four points in the destination image.
在我们进入细节之前,我只是想确定以下是您想要实现的目标:
所以,完整的算法是:
- 使用 Pillow 加载源图像和具有一些四边形的专用输出图像。我假设是白色背景上的黑色四边形。
- 将图像转换为 NumPy 数组以便能够与 OpenCV 一起使用。
- 设置源点。这些只是您感兴趣区域 (ROI) 的角落。
- 找到——或知道——目的地。这些是四边形的角。自动查找这些可能会变得非常困难,因为顺序必须与为 ROI 点设置的顺序相同。
- 获取变换矩阵,并应用实际的透视变换。
- 将变形图像的所需部分复制到初始输出图像的四边形。
- 转换回一些枕头图像并保存。
而且,这是完整的代码,包括一些可视化:
import cv2
import numpy as np
from PIL import Image, ImageDraw
# Input image to get rectangle (region of interest, roi) from
image = Image.open('path/to/your/image.png')
roi = ((100, 30), (300, 200))
# Dummy output image with some quad to paste to
output = Image.new('RGB', (600, 800), (255, 255, 255))
draw = ImageDraw.Draw(output)
draw.polygon(((100, 20), (40, 740), (540, 350), (430, 70)), outline=(0, 0, 0))
# Convert images to NumPy arrays for processing in OpenCV
image_cv2 = np.array(image)
output_cv2 = np.array(output)
# Source points, i.e. roi in input image
tl = (roi[0][0], roi[0][1])
tr = (roi[1][0], roi[0][1])
br = (roi[1][0], roi[1][1])
bl = (roi[0][0], roi[1][1])
pts = np.array([bl, br, tr, tl])
# Find (or know) target points in output image w.r.t. the quad
# Attention: The order must be the same as defined by the roi points!
tl_dst = (100, 20)
tr_dst = (430, 70)
br_dst = (540, 350)
bl_dst = (40, 740)
dst_pts = np.array([bl_dst, br_dst, tr_dst, tl_dst])
# Get transformation matrix, and warp image
pts = np.float32(pts.tolist())
dst_pts = np.float32(dst_pts.tolist())
M = cv2.getPerspectiveTransform(pts, dst_pts)
image_size = (output_cv2.shape[1], output_cv2.shape[0])
warped = cv2.warpPerspective(image_cv2, M, dsize=image_size)
# Get mask from quad in output image, and copy content from warped image
gray = cv2.cvtColor(output_cv2, cv2.COLOR_BGR2GRAY)
gray = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV)[1]
cnts = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
mask = np.zeros_like(output_cv2)
mask = cv2.drawContours(mask, cnts, 0, (255, 255, 255), cv2.FILLED)
mask = mask.all(axis=2)
output_cv2[mask, :] = warped[mask, :]
# Transform back to PIL images
output_new = Image.fromarray(output_cv2)
output_new.save('final_output.jpg')
# Just for visualization
import matplotlib.pyplot as plt
draw = ImageDraw.Draw(image)
draw.rectangle(roi, outline=(255, 0, 0), width=3)
plt.figure(0, figsize=(18, 9))
plt.subplot(1, 3, 1), plt.imshow(image), plt.title('Input with ROI')
plt.subplot(1, 3, 2), plt.imshow(output), plt.title('Output with quad')
plt.subplot(1, 3, 3), plt.imshow(output_new), plt.title('Final output')
plt.tight_layout(), plt.show()
在第 4 步,自动寻找目标点,你可以这样做:
# Find target points in output image w.r.t. the quad
gray = cv2.cvtColor(output_cv2, cv2.COLOR_BGR2GRAY)
gray = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV)[1]
cnts = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
approx = cv2.approxPolyDP(cnts[0], 0.03 * cv2.arcLength(cnts[0], True), True)
这基本上是在图像中找到轮廓,并逼近角点。您仍然需要找到结果点的正确顺序...
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.8.5
Matplotlib: 3.3.3
NumPy: 1.19.5
OpenCV: 4.5.1
Pillow: 8.1.0
----------------------------------------