使用python进行图像处理—图像变换(6)
图像变换是指改变图像的几何形状或空间位置的操作。常见的几何变换包括平移、旋转、缩放、剪切(shear)以及更复杂的仿射变换和透视变换。这些变换在图像配准、图像校正、创建特效等场景中非常有用。
6.1仿射变换(Affine Transformation)
仿射变换是一种线性变换,它可以表示为矩阵乘法和平移的组合。在二维图像中,一个仿射变换可以由一个2x3的矩阵表示:
其中 ( (x, y) ) 是原始图像中的点坐标,( (x’, y’) ) 是变换后图像中的点坐标。这个矩阵可以实现平移、旋转、缩放、剪切的任意组合。
在Pillow中,可以使用 img.transform(size, method, data, filter) 方法进行仿射变换。其中 method 是 Image.AFFINE,data 是一个包含六个浮点数的元组 ( (a_{11}, a_{12}, b_1, a_{21}, a_{22}, b_2) )。
其中 ( (x, y) ) 是原始图像中的点坐标,( (x’, y’) ) 是变换后图像中的点坐标。这个矩阵可以实现平移、旋转、缩放、剪切的任意组合。
在Pillow中,可以使用 img.transform(size, method, data, filter) 方法进行仿射变换。其中 method 是 Image.AFFINE,data 是一个包含六个浮点数的元组 ( (a_{11}, a_{12}, b_1, a_{21}, a_{22}, b_2) )。
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt# 假设我们有一个示例图像 (例如,上面使用的 example.jpg 或模拟图像)
try:img = Image.open('example.jpg').convert('RGB') # 确保是RGB模式print("成功加载图像使用 Pillow Image.open")
except FileNotFoundError:print("示例图像 example.jpg 未找到。")# 创建一个模拟图像img = Image.new('RGB', (400, 300), color = 'lightblue')from PIL import ImageDrawdraw = ImageDraw.Draw(img)draw.text((50, 50), "Hello, Affine!", fill='black', font_size=30)print("已创建模拟图像。")# 定义仿射变换矩阵的六个参数 (a11, a12, b1, a21, a22, b2)# 示例 1: 平移 (向右平移 50 像素,向下平移 30 像素)
# 变换矩阵参数: (1, 0, tx, 0, 1, ty)
tx = 50
ty = 30
affine_params_translate = (1, 0, tx, 0, 1, ty)# 应用仿射变换
# size: 输出图像的尺寸 (width, height)
# method: Image.AFFINE
# data: 仿射变换参数元组
# filter: 重采样滤波器
translated_img = img.transform(img.size, Image.AFFINE, affine_params_translate, Image.Resampling.BILINEAR)# 示例 2: 旋转 (逆时针旋转 30 度)
# 旋转矩阵参数: (cos(theta), -sin(theta), 0, sin(theta), cos(theta), 0)
theta_deg = 30
theta_rad = np.deg2rad(theta_deg) # 将角度转换为弧度
cos_theta = np.cos(theta_rad)
sin_theta = np.sin(theta_rad)
# 如果绕原点旋转,b1和b2为0
affine_params_rotate_origin = (cos_theta, -sin_theta, 0, sin_theta, cos_theta, 0)# 绕图像中心旋转需要额外的平移步骤,或者调整变换矩阵
# 假设图像中心是 (cx, cy)
# 变换步骤:平移中心到原点 -> 旋转 -> 平移回中心
# 对应的仿射矩阵参数可以通过矩阵乘法计算得到
# 更简单的实现是先计算好中心点,然后构建变换参数
# 平移到原点: (1, 0, -cx, 0, 1, -cy)
# 旋转: (cos, -sin, 0, sin, cos, 0)
# 平移回中心: (1, 0, cx, 0, 1, cy)
# 复合变换矩阵 = [平移回中心] * [旋转] * [平移到原点]
# 计算过程比较繁琐,Pillow的 rotate() 方法更常用,但这里演示 affine 的灵活性# 应用绕原点旋转的仿射变换
rotated_img_affine = img.transform(img.size, Image.AFFINE, affine_params_rotate_origin, Image.Resampling.BILINEAR)
# 注意:绕原点旋转可能会导致图像部分移出画布,需要调整输出尺寸# 示例 3: 缩放 (x 方向缩放 1.2 倍,y 方向缩放 0.8 倍)
# 变换矩阵参数: (sx, 0, 0, 0, sy, 0)
sx = 1.2
sy = 0.8
affine_params_scale = (sx, 0, 0, 0, sy, 0)# 应用缩放仿射变换
scaled_img_affine = img.transform(img.size, Image.AFFINE, affine_params_scale, Image.Resampling.BILINEAR)# 示例 4: 剪切 (x 方向剪切,y 不变)
# 变换矩阵参数: (1, shx, 0, shy, 1, 0)
shx = 0.5 # x 方向剪切因子
shy = 0 # y 方向剪切因子 (这里 y 不变)
affine_params_shear = (1, shx, 0, shy, 1, 0)# 应用剪切仿射变换
sheared_img_affine = img.transform(img.size, Image.AFFINE, affine_params_shear, Image.Resampling.BILINEAR)# 显示结果
plt.figure(figsize=(15, 10))plt.subplot(2, 3, 1)
plt.imshow(img)
plt.title('原始图像')
plt.axis('off')plt.subplot(2, 3, 2)
plt.imshow(translated_img)
plt.title(f'平移 ({tx}, {ty})')
plt.axis('off')plt.subplot(2, 3, 3)
plt.imshow(rotated_img_affine) # 注意:绕原点旋转,可能部分移出
plt.title(f'仿射旋转 ({theta_deg} 度, 绕原点)')
plt.axis('off')plt.subplot(2, 3, 4)
plt.imshow(scaled_img_affine)
plt.title(f'缩放 ({sx}x, {sy}y)')
plt.axis('off')plt.subplot(2, 3, 5)
plt.imshow(sheared_img_affine)
plt.title(f'剪切 (shx={shx})')
plt.axis('off')plt.tight_layout()
plt.show()# 保存结果
translated_img.save('output_affine_translated.png')
rotated_img_affine.save('output_affine_rotated_origin.png')
scaled_img_affine.save('output_affine_scaled.png')
sheared_img_affine.save('output_affine_sheared.png')
print("仿射变换示例已完成并保存结果。")
代码解释:
- img = Image.open('example.jpg').convert('RGB'):加载图像并确保它是RGB模式,因为一些变换可能对模式敏感。
- img.transform(size, method, data, filter):这是Pillow中执行各种变换的通用方法。
- size:输出图像的尺寸元组(width, height)。可以保持原尺寸,也可以根据需要放大以容纳整个变换后的图像。
- method=Image.AFFINE:指定变换方法为仿射变换。
- data:一个包含6个浮点数的元组(a11, a12, b1, a21, a22, b2),对应仿射变换矩阵的前两行。
- filter=Image.Resampling.BILINEAR:指定重采样滤波器。在图像变换后,新的像素位置可能落在原始像素之间,需要通过重采样(插值)来确定新像素的值。BILINEAR是双线性插值,提供了较好的质量和速度平衡。其他选项如NEAREST (最近邻插值,速度最快,质量最低)和LANCZOS (高质量滤波器,速度较慢)适用于不同场景。
- affine_params_translate = (1, 0, tx, 0, 1, ty):平移变换的仿射参数。a11=1, a22=1保持缩放不变,a12=0, a21=0保持剪切不变,b1=tx, b2=ty实现平移。
- theta_rad = np.deg2rad(theta_deg):将角度从度转换为弧度,因为三角函数通常使用弧度。
- cos_theta = np.cos(theta_rad)和sin_theta = np.sin(theta_rad):计算旋转所需的正弦和余弦值。
- affine_params_rotate_origin = (cos_theta, -sin_theta, 0, sin_theta, cos_theta, 0):绕原点逆时针旋转的仿射参数。
- affine_params_scale = (sx, 0, 0, 0, sy, 0):缩放变换的仿射参数。a11=sx, a22=sy实现x和y方向的缩放,其他参数为0保持不变形和不平移。
- affine_params_shear = (1, shx, 0, shy, 1, 0):剪切变换的仿射参数。shx控制x方向的剪切,shy控制y方向的剪切。
- 使用Matplotlib显示原始图像和各种仿射变换后的图像。
通过调整这六个参数,我们可以组合实现复杂的仿射变换。
在OpenCV中,仿射变换通常通过cv2.getAffineTransform()计算由三个对应点对确定的仿射变换矩阵,然后使用cv2.warpAffine()应用变换。
import numpy as np
import cv2 # 导入OpenCV库
import matplotlib.pyplot as plt# 假设我们有一个示例图像 (加载为OpenCV格式)
# OpenCV 默认使用 BGR 模式加载彩色图像
try:img_cv = cv2.imread('example.jpg')if img_cv is None:raise FileNotFoundErrorprint("成功加载图像使用 cv2.imread")
except FileNotFoundError:print("示例图像 example.jpg 未找到。")# 创建一个模拟图像 (灰度)img_cv = np.full((300, 400), 150, dtype=np.uint8) # 灰度背景 150# cv2.putText(img_cv, "Hello, Affine!", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2) # 黑色文字print("已创建模拟灰度图像。")# OpenCV 图像通常是 NumPy 数组
rows, cols = img_cv.shape[:2] # 获取图像高度和宽度# 示例 1: 平移 (向右平移 50 像素,向下平移 30 像素)
tx = 50
ty = 30
# 平移矩阵 M: [[1, 0, tx], [0, 1, ty]]
M_translate = np.float32([[1, 0, tx],[0, 1, ty]]) # 必须是 float32 类型# 应用仿射变换 (cv2.warpAffine)
# src: 输入图像
# M: 2x3 变换矩阵 (float32 类型)
# dsize: 输出图像尺寸 (width, height)
# flags: 插值方法 (cv2.INTER_LINEAR, cv2.INTER_NEAREST, etc.)
# borderMode: 边界处理方式 (cv2.BORDER_CONSTANT, cv2.BORDER_REPLICATE, etc.)
# borderValue: 边界填充值 (borderMode=cv2.BORDER_CONSTANT 时使用)
translated_img_cv = cv2.warpAffine(img_cv, M_translate, (cols, rows)) # 输出尺寸通常与原图相同,部分会移出# 如果需要扩展输出尺寸以包含整个平移后的图像
# new_width = cols + abs(tx)
# new_height = rows + abs(ty)
# translated_img_cv_expanded = cv2.warpAffine(img_cv, M_translate, (new_width, new_height))# 示例 2: 旋转 (逆时针旋转 30 度,绕中心点)
angle_deg = 30
# 获取旋转矩阵
# cv2.getRotationMatrix2D(center, angle, scale)
# center: 旋转中心点 (x, y)
# angle: 旋转角度 (以度为单位,正值表示逆时针旋转)
# scale: 缩放因子 (1.0 表示不缩放)
center = (cols // 2, rows // 2)
M_rotate = cv2.getRotationMatrix2D(center, angle_deg, 1.0)# 应用旋转仿射变换
rotated_img_cv = cv2.warpAffine(img_cv, M_rotate, (cols, rows)) # 保持原尺寸,部分会裁剪# 如果需要扩展尺寸以包含整个旋转后的图像
# 可以通过计算旋转后图像的四个角点的新坐标,然后确定新的边界框来获取新的尺寸
# cv2.warpAffine 提供了额外的输出尺寸计算功能,但手动计算更灵活
# 例如,计算新尺寸以便完整包含旋转后的图像:
# (x, y) 是原始图像的四个角点坐标
# (x', y') = M * [x, y, 1].T
# 找到 (x', y') 的 min/max x 和 y 坐标,确定新的边界框和尺寸
cos = np.abs(M_rotate[0, 0]) # cos(angle)
sin = np.abs(M_rotate[0, 1]) # sin(angle)
new_width = int(rows * sin + cols * cos)
new_height = int(rows * cos + cols * sin)
# 调整平移分量以将整个图像移到新图像的中心
M_rotate[0, 2] += (new_width / 2) - center[0]
M_rotate[1, 2] += (new_height / 2) - center[1]
rotated_img_cv_expanded = cv2.warpAffine(img_cv, M_rotate, (new_width, new_height))# 示例 3: 缩放 (x 和 y 方向都缩放 1.5 倍)
M_scale = np.float32([[1.5, 0, 0],[0, 1.5, 0]])# 应用缩放仿射变换
# 注意:这里需要指定新的输出尺寸
scaled_img_cv = cv2.warpAffine(img_cv, M_scale, (int(cols * 1.5), int(rows * 1.5)))# 示例 4: 使用三对对应点进行仿射变换
# 定义原始图像的三个点和它们在目标图像中的对应位置
# points_original: [[x1, y1], [x2, y2], [x3, y3]]
# points_target: [[x1', y1'], [x2', y2'], [x3', y3']]
pts1 = np.float32([[50, 50], [200, 50], [50, 200]]) # 原始图像的三个点
pts2 = np.float32([[10, 100], [200, 50], [100, 250]]) # 目标图像中对应的三个点# 计算仿射变换矩阵 M
M_points = cv2.getAffineTransform(pts1, pts2)# 应用基于点的仿射变换
transformed_img_cv = cv2.warpAffine(img_cv, M_points, (cols, rows)) # 输出尺寸可以调整# 显示结果
plt.figure(figsize=(15, 10))plt.subplot(2, 3, 1)
plt.imshow(cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB)) # OpenCV是BGR,用Matplotlib显示需要转RGB
plt.title('原始图像')
plt.axis('off')plt.subplot(2, 3, 2)
plt.imshow(cv2.cvtColor(translated_img_cv, cv2.COLOR_BGR2RGB))
plt.title('OpenCV 平移')
plt.axis('off')plt.subplot(2, 3, 3)
plt.imshow(cv2.cvtColor(rotated_img_cv_expanded, cv2.COLOR_BGR2RGB)) # 显示扩展尺寸的旋转结果
plt.title('OpenCV 旋转 (绕中心)')
plt.axis('off')plt.subplot(2, 3, 4)
plt.imshow(cv2.cvtColor(scaled_img_cv, cv2.COLOR_BGR2RGB))
plt.title('OpenCV 缩放')
plt.axis('off')plt.subplot(2, 3, 5)
plt.imshow(cv2.cvtColor(transformed_img_cv, cv2.COLOR_BGR2RGB))
plt.title('OpenCV 基于点变换')
plt.axis('off')plt.tight_layout()
plt.show()# 保存结果 (OpenCV保存图像)
cv2.imwrite('output_cv_affine_translated.jpg', translated_img_cv)
cv2.imwrite('output_cv_affine_rotated_expanded.jpg', rotated_img_cv_expanded)
cv2.imwrite('output_cv_affine_scaled.jpg', scaled_img_cv)
cv2.imwrite('output_cv_affine_transformed.jpg', transformed_img_cv)
print("OpenCV 仿射变换示例已完成并保存结果。")
代码解释:
- import cv2:导入OpenCV库。
- img_cv = cv2.imread('example.jpg'):使用cv2.imread()加载图像。OpenCV默认加载彩色图像为BGR格式。
- M_translate = np.float32([[1, 0, tx], [0, 1, ty]]):创建平移的2x3仿射变换矩阵。必须是float32类型。
- cv2.warpAffine(img_cv, M_translate, (cols, rows)):应用仿射变换。
- 第一个参数是输入图像。
- 第二个参数是2x3的变换矩阵。
- 第三个参数是输出图像的尺寸元组(width, height)。
- cv2.getRotationMatrix2D(center, angle_deg, 1.0):获取绕指定中心旋转指定角度(度)和缩放因子为1.0的2x3仿射变换矩阵。
- 计算旋转后扩展尺寸:通过计算旋转后图像四个角点的新位置,可以确定包含整个旋转图像所需的最小矩形边界框的尺寸。
- M_scale = np.float32([[1.5, 0, 0], [0, 1.5, 0]]):创建缩放的2x3仿射变换矩阵。对角线元素[0, 0]和[1, 1]分别控制x和y方向的缩放因子。
- cv2.getAffineTransform(pts1, pts2):根据原始图像中的三对对应点pts1和目标图像中的对应点pts2,计算确定这个仿射变换的2x3矩阵。仿射变换由三对非共线点唯一确定。
- cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB):在使用Matplotlib显示OpenCV加载的BGR格式图像时,需要将其转换为RGB格式,否则颜色会不正确。
- cv2.imwrite('output_cv_affine_translated.jpg', translated_img_cv):使用cv2.imwrite()保存图像。OpenCV会自动根据文件扩展名选择编码格式。
- OpenCV在处理图像变换方面功能强大且通常效率更高,尤其是在需要计算变换矩阵(如基于点对应)或进行复杂变换时。
第六章:图像变换(续)
6.2透视变换(Perspective Transformation)
-
透视变换,也称为投影变换,比仿射变换更复杂,它可以改变图像的“视角”。仿射变换保持平行线的平行性,而透视变换则不保留平行性,但保留直线的直线性。透视变换可以将图像中的一个平面投影到另一个平面上,这对于校正由相机倾斜引起的图像畸变(如扫描文档的校正)、创建虚拟现实场景或进行图像拼接非常重要。
-
一个二维图像的透视变换可以由一个3x3的矩阵表示:
-
-
变换后的齐次坐标 ( (x’, y’, w’) ) 与二维笛卡尔坐标 ( (x_{new}, y_{new}) ) 的关系是:
-
[ x_{new} = \frac{x’}{w’} = \frac{a_{11}x + a_{12}y + b_1}{c_1x + c_2y + d}
y_{new} = \frac{y’}{w’} = \frac{a_{21}x + a_{22}y + b_2}{c_1x + c_2y + d} ] -
这个矩阵有8个自由度(因为矩阵乘法结果可以整体乘以一个非零常数而不改变 ( x_{new} ) 和 ( y_{new} ),通常令 ( d=1 )),因此需要至少4对非共线的对应点来确定变换矩阵。
-
在OpenCV中,可以使用 cv2.getPerspectiveTransform() 计算由四对对应点确定的透视变换矩阵,然后使用 cv2.warpPerspective() 应用变换。
import numpy as np
import cv2 # 导入OpenCV库
import matplotlib.pyplot as plt# 假设我们有一个示例图像 (加载为OpenCV格式)
# OpenCV 默认使用 BGR 模式加载彩色图像
try:img_cv = cv2.imread('example.jpg')if img_cv is None:raise FileNotFoundErrorprint("成功加载图像使用 cv2.imread")
except FileNotFoundError:print("示例图像 example.jpg 未找到。")# 创建一个模拟图像 (彩色),并在上面绘制一些形状以方便观察透视变换效果img_cv = np.full((400, 600, 3), 200, dtype=np.uint8) # 浅灰色背景# 绘制一个矩形cv2.rectangle(img_cv, (100, 100), (500, 300), (255, 0, 0), 5) # 蓝色矩形# 绘制一些文字cv2.putText(img_cv, "Perspective Transform", (120, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) # 红色文字# 绘制一些点cv2.circle(img_cv, (100, 100), 10, (0, 255, 0), -1) # 绿色圆点 (左上角)cv2.circle(img_cv, (500, 100), 10, (0, 255, 0), -1) # 绿色圆点 (右上角)cv2.circle(img_cv, (500, 300), 10, (0, 255, 0), -1) # 绿色圆点 (右下角)cv2.circle(img_cv, (100, 300), 10, (0, 255, 0), -1) # 绿色圆点 (左下角)print("已创建模拟彩色图像。")# 定义原始图像的四个角点坐标 (必须是 float32 类型)
# 这些点通常是需要进行透视校正的平面上的四个角点
rows, cols = img_cv.shape[:2] # 获取图像高度和宽度
# 例如,原始图像的四个角点
pts1 = np.float32([[100, 100], [500, 100], [500, 300], [100, 300]]) # 对应上面绘制的矩形的四个角点# 定义目标图像中这四个点对应的位置 (必须是 float32 类型)
# 例如,将这四个点变换到一个新的矩形区域,实现“拉直”效果
# 假设目标矩形的角点
pts2 = np.float32([[50, 50], [550, 50], [550, 350], [50, 350]]) # 变换到一个更大的矩形区域,保持矩形形状# 检查点数量是否正确 (需要四对点)
if pts1.shape != (4, 2) or pts2.shape != (4, 2):print("错误: 需要提供四对对应点进行透视变换。")
else:# 1. 计算透视变换矩阵 M# cv2.getPerspectiveTransform(src, dst)# src: 原始图像中的四对点 (float32)# dst: 目标图像中对应的四对点 (float32)M_perspective = cv2.getPerspectiveTransform(pts1, pts2)print("计算得到的透视变换矩阵 M:\n", M_perspective)# 2. 应用透视变换 (cv2.warpPerspective)# src: 输入图像# M: 3x3 变换矩阵 (float32 类型)# dsize: 输出图像尺寸 (width, height)# flags: 插值方法 (cv2.INTER_LINEAR, cv2.INTER_NEAREST, etc.)# borderMode: 边界处理方式# borderValue: 边界填充值# 输出尺寸可以根据目标点的位置自行决定,这里为了演示方便,先使用一个固定的较大尺寸output_width = 600output_height = 400transformed_img_cv = cv2.warpPerspective(img_cv, M_perspective, (output_width, output_height))# 显示原始图像和透视变换后的图像plt.figure(figsize=(12, 6))plt.subplot(1, 2, 1)# OpenCV是BGR格式,Matplotlib显示需要转RGBplt.imshow(cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB))plt.title('原始图像')# 在原始图像上标记用于变换的点for pt in pts1:plt.plot(pt[0], pt[1], 'ro') # 用红色圆点标记原始点plt.axis('off')plt.subplot(1, 2, 2)plt.imshow(cv2.cvtColor(transformed_img_cv, cv2.COLOR_BGR2RGB))plt.title('透视变换后图像')# 在变换后图像上标记目标点位置 (理论上变换后的点应该在这些位置)for pt in pts2:plt.plot(pt[0], pt[1], 'ro') # 用红色圆点标记目标点plt.axis('off')plt.tight_layout()plt.show()# 保存结果 (OpenCV保存图像)cv2.imwrite('output_cv_perspective_transformed.jpg', transformed_img_cv)print("OpenCV 透视变换示例已完成并保存为: output_cv_perspective_transformed.jpg")# 真实案例模拟:文档扫描校正# 假设我们扫描了一份倾斜的文档,需要将文档区域“拉直”成一个矩形# 加载一个包含倾斜矩形区域的模拟图像doc_img = np.full((500, 700, 3), 230, dtype=np.uint8) # 浅灰色背景# 模拟文档区域 (倾斜的四边形)doc_pts_src = np.float32([[150, 120], [550, 100], [600, 400], [100, 420]]) # 倾斜的四边形角点# 模拟在文档区域绘制一些内容cv2.putText(doc_img, "Scanned Document", (200, 250), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 150), 2) # 蓝色文字cv2.line(doc_img, tuple(doc_pts_src[0].astype(int)), tuple(doc_pts_src[1].astype(int)), (0, 100, 0), 2) # 绿色线cv2.line(doc_img, tuple(doc_pts_src[1].astype(int)), tuple(doc_pts_src[2].astype(int)), (0, 100, 0), 2)cv2.line(doc_img, tuple(doc_pts_src[2].astype(int)), tuple(doc_pts_src[3].astype(int)), (0, 100, 0), 2)cv2.line(doc_img, tuple(doc_pts_src[3].astype(int)), tuple(doc_pts_src[0].astype(int)), (0, 100, 0), 2)# 定义目标矩形区域的尺寸和位置# 假设目标是将文档区域校正为一个 400x600 像素的矩形 (例如,宽度600,高度400)doc_output_width = 600doc_output_height = 400# 目标矩形的四个角点 (通常是规则的矩形)doc_pts_dst = np.float32([[0, 0], [doc_output_width - 1, 0], [doc_output_width - 1, doc_output_height - 1], [0, doc_output_height - 1]])# 计算透视变换矩阵M_doc_correct = cv2.getPerspectiveTransform(doc_pts_src, doc_pts_dst)# 应用透视变换进行校正corrected_doc_img = cv2.warpPerspective(doc_img, M_doc_correct, (doc_output_width, doc_output_height))# 显示原始倾斜文档图像和校正后的图像plt.figure(figsize=(14, 7))plt.subplot(1, 2, 1)plt.imshow(cv2.cvtColor(doc_img, cv2.COLOR_BGR2RGB))plt.title('原始倾斜文档图像')# 标记原始文档角点for pt in doc_pts_src:plt.plot(pt[0], pt[1], 'ro')plt.axis('off')plt.subplot(1, 2, 2)plt.imshow(cv2.cvtColor(corrected_doc_img, cv2.COLOR_BGR2RGB))plt.title('透视校正后文档图像')# 标记目标文档角点for pt in doc_pts_dst:plt.plot(pt[0], pt[1], 'ro')plt.axis('off')plt.tight_layout()plt.show()# 保存校正后的文档图像cv2.imwrite('output_cv_document_corrected.jpg', corrected_doc_img)print("文档透视校正示例已完成并保存为: output_cv_document_corrected.jpg")
代码解释:
- import numpy as np, import cv2, import matplotlib.pyplot as plt:导入所需的库。
- 加载或创建模拟图像:为了示例,如果找不到‘example.jpg’,则创建一个包含矩形、文字和角点的模拟图像,以便观察透视变换如何改变形状。
- pts1 = np.float32([[100, 100], [500, 100], [500, 300], [100, 300]]):定义原始图像中用于变换的四个点。这些点通常是图像中一个已知平面(如书本封面、文档页面、地面标志等)的角点。**注意:**这些点必须是float32类型,这是OpenCV函数要求的。
- pts2 = np.float32([[50, 50], [550, 50], [550, 350], [50, 350]]):定义目标图像中与pts1中的点对应的位置。这里将原始矩形变换到一个更大的矩形,但保持矩形形状,这相当于在应用透视变换的同时进行了缩放和平移。在文档校正等应用中,pts2通常定义一个规则的矩形区域。**注意:**同样必须是float32类型。
- M_perspective = cv2.getPerspectiveTransform(pts1, pts2):使用cv2.getPerspectiveTransform()函数计算从pts1到pts2的透视变换矩阵。它需要四对对应点来唯一确定3x3的变换矩阵(最后一个元素通常固定为1)。
- transformed_img_cv = cv2.warpPerspective(img_cv, M_perspective, (output_width, output_height)):使用cv2.warpPerspective()函数将计算出的透视变换应用到原始图像上。
- 第一个参数是输入图像。
- 第二个参数是计算得到的3x3透视变换矩阵。
- 第三个参数是输出图像的尺寸元组(width, height)。这个尺寸需要足够大以包含变换后的感兴趣区域。
- Matplotlib显示:将OpenCV的BGR图像转换为RGB格式后,使用Matplotlib显示原始图像和变换后的图像,并在图像上用红点标记原始点和目标点,以便直观比较。
- cv2.imwrite(...):保存结果图像。
文档扫描校正案例:
- doc_img = np.full(...):创建一个模拟的文档扫描图像,其中包含一个倾斜的矩形区域。
- doc_pts_src = np.float32([[150, 120], [550, 100], [600, 400], [100, 420]]):定义模拟文档区域(一个四边形)的四个角点。在实际应用中,这些点可以通过图像处理方法(如边缘检测、轮廓查找、角点检测)或用户手动标记获得。
- doc_pts_dst = np.float32([[0, 0], [doc_output_width - 1, 0], [doc_output_width - 1, doc_output_height - 1], [0, doc_output_height - 1]]):定义目标图像中,文档区域应该变换到的一个规则矩形的角点。这里设置为从(0, 0)到(width-1, height-1)的标准矩形,这将把倾斜的四边形“拉直”为这个矩形。
- M_doc_correct = cv2.getPerspectiveTransform(doc_pts_src, doc_pts_dst):计算从倾斜四边形到目标矩形的透视变换矩阵。
- corrected_doc_img = cv2.warpPerspective(doc_img, M_doc_correct, (doc_output_width, doc_output_height)):将变换矩阵应用到原始文档图像,生成校正后的图像。
透视变换是图像处理中用于处理平面投影畸变的重要工具,常用于文档校正、图像拼接、相机校准等领域。
6.3图像插值(Image Interpolation)
在进行图像缩放、旋转、透视变换等几何变换时,输出图像的像素位置可能不会精确地对应到输入图像的整数像素坐标上。这时就需要通过插值算法,利用输入图像中周围已知像素的信息来估计新像素位置的值。插值算法的选择会影响变换后图像的质量和计算速度。
常见的图像插值算法包括:
- 最近邻插值(Nearest Neighbor Interpolation):取离新像素位置最近的输入像素的值作为新像素的值。
- 优点:计算速度最快。
- 缺点:会产生块状效应(马赛克),引入锯齿,图像质量最低。
- 双线性插值(Bilinear Interpolation):考虑新像素位置周围的2x2个输入像素,通过对它们的值进行加权平均(线性插值)来计算新像素的值。权重与距离成反比。
- 优点:图像质量比最近邻插值好,过渡比较平滑。
- 缺点:会引入一定的模糊。
- 双三次插值(Bicubic Interpolation):考虑新像素位置周围的4x4个输入像素,通过对它们的值进行更复杂的加权平均(使用三次多项式插值)来计算新像素的值。
- 优点:图像质量通常比双线性插值好,细节保留更多,边缘更锐利。
- 缺点:计算复杂度最高,速度相对较慢。
- Lanczos插值(Lanczos Interpolation):一种高质量的插值方法,使用Lanczos函数作为滤波器。
- 优点:在缩小图像时效果很好,能有效抑制锯齿和振铃效应,保留较多细节。
- 缺点:计算复杂度较高。
Pillow和OpenCV的图像变换函数通常都提供了选择插值方法的参数。
import numpy as np
from PIL import Image
import cv2
import matplotlib.pyplot as plt# 假设我们有一个示例图像 (为了更好演示插值,使用一张有细节的图片)
# 如果没有,可以创建一个包含文字和线条的模拟图像
try:# 尝试加载一个真实图像img_orig = Image.open('example.jpg').convert('RGB')print("成功加载图像 example.jpg 用于插值示例。")
except FileNotFoundError:print("示例图像 example.jpg 未找到,创建模拟图像用于插值示例。")img_orig = Image.new('RGB', (400, 300), color = 'white')from PIL import ImageDraw, ImageFontdraw = ImageDraw.Draw(img_orig)try:# 尝试加载一个字体文件font = ImageFont.truetype("arial.ttf", 40) # 使用Arial字体,大小40except IOError:# 如果找不到字体文件,使用默认字体font = ImageFont.load_default()print("未找到 arial.ttf,使用默认字体。")draw.text((20, 20), "Interpolation", fill='black', font=font)draw.line([(10, 150), (390, 150)], fill='red', width=3) # 红线draw.line([(200, 10), (200, 290)], fill='blue', width=3) # 蓝线for i in range(0, 300, 20): # 绘制一些斜线draw.line([(0, i), (i + 100, 0)], fill='green')print("已创建模拟图像用于插值示例。")# 将图像缩小到原尺寸的一半,并比较不同插值方法的效果
original_width, original_height = img_orig.size
new_width = original_width // 2
new_height = original_height // 2
new_size = (new_width, new_height)# Pillow 示例
# 最近邻插值
img_nearest_pil = img_orig.resize(new_size, Image.Resampling.NEAREST)
# 双线性插值
img_bilinear_pil = img_orig.resize(new_size, Image.Resampling.BILINEAR)
# 双三次插值
img_bicubic_pil = img_orig.resize(new_size, Image.Resampling.BICUBIC)
# Lanczos 插值
img_lanczos_pil = img_orig.resize(new_size, Image.Resampling.LANCZOS)# OpenCV 示例 (需要将 Pillow Image 转换为 OpenCV 格式 - NumPy 数组)
img_orig_cv = cv2.cvtColor(np.array(img_orig), cv2.COLOR_RGB2BGR) # Pillow 是RGB,OpenCV是BGR# 最近邻插值
img_nearest_cv = cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_NEAREST)
# 双线性插值
img_bilinear_cv = cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_LINEAR)
# 双三次插值
img_bicubic_cv = cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_CUBIC)
# Lanczos 插值
img_lanczos_cv = cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_LANCZOS4) # OpenCV提供多种 Lanczos 核大小# 显示原始图像和不同插值方法缩小的结果
plt.figure(figsize=(15, 10))plt.subplot(2, 3, 1)
plt.imshow(img_orig)
plt.title('原始图像')
plt.axis('off')plt.subplot(2, 3, 2)
plt.imshow(img_nearest_pil)
plt.title('Pillow 最近邻插值')
plt.axis('off')plt.subplot(2, 3, 3)
plt.imshow(img_bilinear_pil)
plt.title('Pillow 双线性插值')
plt.axis('off')plt.subplot(2, 3, 4)
plt.imshow(img_bicubic_pil)
plt.title('Pillow 双三次插值')
plt.axis('off')plt.subplot(2, 3, 5)
plt.imshow(img_lanczos_pil)
plt.title('Pillow Lanczos 插值')
plt.axis('off')# OpenCV 显示的结果需要转回RGB
plt.subplot(2, 3, 6)
# 为了比较方便,这里只显示一种OpenCV的结果,例如双线性
plt.imshow(cv2.cvtColor(img_bilinear_cv, cv2.COLOR_BGR2RGB))
plt.title('OpenCV 双线性插值')
plt.axis('off')plt.tight_layout()
plt.show()# 保存结果 (Pillow 和 OpenCV 各保存一个示例)
img_bilinear_pil.save('output_interpolation_bilinear_pil.jpg')
cv2.imwrite('output_interpolation_bilinear_cv.jpg', img_bilinear_cv)
print("不同插值方法的缩放示例已完成并保存结果。")
代码解释:
- 加载或创建模拟图像:为了清晰展示不同插值方法的区别,创建一个包含文字、直线和斜线的图像作为示例。这些元素更容易显示插值引入的锯齿或模糊。
- original_width, original_height = img_orig.size:获取原始图像的尺寸。
- new_width = original_width // 2, new_height = original_height // 2, new_size = (new_width, new_height):计算缩小到一半后的目标尺寸。
- img_orig.resize(new_size, Image.Resampling.NEAREST)等:使用Pillow的resize()方法进行缩放,并通过Image.Resampling常量指定不同的插值方法。Image.Resampling是Pillow 9.1.0+的推荐用法,早期版本使用Image.NEAREST等。
- img_orig_cv = cv2.cvtColor(np.array(img_orig), cv2.COLOR_RGB2BGR):将Pillow Image对象(RGB模式)转换为OpenCV兼容的NumPy数组(BGR模式),以便使用OpenCV的函数。
- cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_NEAREST)等:使用OpenCV的cv2.resize()函数进行缩放,并通过interpolation参数指定不同的插值方法,例如cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_LANCZOS4。OpenCV提供了更多的插值选项,包括INTER_AREA(在缩小图像时效果较好)和INTER_LINEAR_EXACT。
- Matplotlib显示:显示原始图像以及使用不同库和不同插值方法缩小的结果,以便直观比较图像质量的差异。最近邻插值通常看起来最差(锯齿明显),双线性较平滑,双三次和Lanczos通常提供更好的视觉效果,尤其是在保留边缘和细节方面。
在实际应用中,选择哪种插值方法取决于对速度和图像质量的要求。对于实时应用或对速度要求极高的场景,最近邻插值可能是首选。对于大多数一般的图像处理任务,双线性插值是一个不错的折衷。对于需要最高图像质量的场景(如图像打印、医学影像),双三次或Lanczos插值更合适。
相关文章:

使用python进行图像处理—图像变换(6)
图像变换是指改变图像的几何形状或空间位置的操作。常见的几何变换包括平移、旋转、缩放、剪切(shear)以及更复杂的仿射变换和透视变换。这些变换在图像配准、图像校正、创建特效等场景中非常有用。 6.1仿射变换(Affine Transformation) 仿射变换是一种…...

使用homeassistant 插件将tasmota 接入到米家
我写一个一个 将本地tasmoat的的设备同通过ha集成到小爱同学的功能,利用了巴法接入小爱的功能,将本地mqtt转发给巴法以实现小爱控制的功能,前提条件。1需要tasmota 设备, 2.在本地搭建了mqtt服务可, 3.搭建了ha 4.在h…...
VUE3 ref 和 useTemplateRef
使用ref来绑定和获取 页面 <headerNav ref"headerNavRef"></headerNav><div click"showRef" ref"buttonRef">refbutton</div>使用ref方法const后面的命名需要跟页面的ref值一样 const buttonRef ref(buttonRef) cons…...

【笔记】结合 Conda任意创建和配置不同 Python 版本的双轨隔离的 Poetry 虚拟环境
如何结合 Conda 任意创建和配置不同 Python 版本的双轨隔离的Poetry 虚拟环境? 在 Python 开发中,为不同项目配置独立且适配的虚拟环境至关重要。结合 Conda 和 Poetry 工具,能高效创建不同 Python 版本的 Poetry 虚拟环境,接下来…...

多模态学习路线(2)——DL基础系列
目录 前言 一、归一化 1. Layer Normalization (LN) 2. Batch Normalization (BN) 3. Instance Normalization (IN) 4. Group Normalization (GN) 5. Root Mean Square Normalization(RMSNorm) 二、激活函数 1. Sigmoid激活函数(二分类&…...

[10-1]I2C通信协议 江协科技学习笔记(17个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17...

AWSLambda之设置时区
目标 希望Lambda运行的时区是东八区。 解决 只需要设置lambda的环境变量TZ为东八区时区即可,即Asia/Shanghai。 参考 使用 Lambda 环境变量...

RFID推动新能源汽车零部件生产系统管理应用案例
RFID推动新能源汽车零部件生产系统管理应用案例 一、项目背景 新能源汽车零部件场景 在新能源汽车零部件生产领域,电子冷却水泵等关键部件的装配溯源需求日益增长。传统 RFID 溯源方案采用 “网关 RFID 读写头” 模式,存在单点位单独头溯源、网关布线…...

[C++错误经验]case语句跳过变量初始化
标题:[C错误经验]case语句跳过变量初始化 水墨不写bug 文章目录 一、错误信息复现二、错误分析三、解决方法 一、错误信息复现 write.cc:80:14: error: jump to case label80 | case 2:| ^ write.cc:76:20: note: crosses initialization…...

Unity-ECS详解
今天我们来了解Unity最先进的技术——ECS架构(EntityComponentSystem)。 Unity官方下有源码,我们下载源码后来学习。 ECS 与OOP(Object-Oriented Programming)对应,ECS是一种完全不同的编程范式与数据架构…...

uni-app学习笔记二十七--设置底部菜单TabBar的样式
官方文档地址:uni.setTabBarItem(OBJECT) | uni-app官网 uni.setTabBarItem(OBJECT) 动态设置 tabBar 某一项的内容,通常写在项目的App.vue的onLaunch方法中,用于项目启动时立即执行 重要参数: indexnumber是tabBar 的哪一项&…...

7种分类数据编码技术详解:从原理到实战
在数据分析和机器学习领域,分类数据(Categorical Data)的处理是一个基础但至关重要的环节。分类数据指的是由有限数量的离散值组成的数据类型,如性别(男/女)、颜色(红/绿/蓝)或产品类…...

【字节拥抱开源】字节团队开源视频模型 ContentV: 有限算力下的视频生成模型高效训练
本项目提出了ContentV框架,通过三项关键创新高效加速基于DiT的视频生成模型训练: 极简架构设计,最大化复用预训练图像生成模型进行视频合成系统化的多阶段训练策略,利用流匹配技术提升效率经济高效的人类反馈强化学习框架&#x…...

本地部署drawDB结合内网穿透技术实现数据库远程管控方案
文章目录 前言1. Windows本地部署DrawDB2. 安装Cpolar内网穿透3. 实现公网访问DrawDB4. 固定DrawDB公网地址 前言 在数字化浪潮席卷全球的背景下,数据治理能力正日益成为构建现代企业核心竞争力的关键因素。无论是全球500强企业的数据中枢系统,还是初创…...

可视化预警系统:如何实现生产风险的实时监控?
在生产环境中,风险无处不在,而传统的监控方式往往只能事后补救,难以做到提前预警。但如今,可视化预警系统正在改变这一切!它能够实时收集和分析生产数据,通过直观的图表和警报,让管理者第一时间…...

多模态大语言模型arxiv论文略读(112)
Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文标题:Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文作者:Jea…...

【向量库】Weaviate概述与架构解析
文章目录 一、什么是weaviate二、High-Level Architecture1. Core Components2. Storage Layer3. 组件交互流程 三、核心组件1. API Layer2. Schema Management3. Vector Indexing3.1. 查询原理3.2. 左侧:Search Process(搜索流程)3.3. 右侧&…...
PostgreSQL 对 IPv6 的支持情况
PostgreSQL 对 IPv6 的支持情况 PostgreSQL 全面支持 IPv6 网络协议,包括连接、存储和操作 IPv6 地址。以下是详细说明: 一、网络连接支持 1. 监听 IPv6 连接 在 postgresql.conf 中配置: listen_addresses 0.0.0.0,:: # 监听所有IPv4…...
python数据结构和算法(1)
数据结构和算法简介 数据结构:存储和组织数据的方式,决定了数据的存储方式和访问方式。 算法:解决问题的思维、步骤和方法。 程序 数据结构 算法 算法 算法的独立性 算法是独立存在的一种解决问题的方法和思想,对于算法而言&a…...
视觉slam--框架
视觉里程计的框架 传感器 VO--front end VO的缺点 后端--back end 后端对什么数据进行优化 利用什么数据进行优化的 后端是怎么进行优化的 回环检测 建图 建图是指构建地图的过程。 构建的地图是点云地图还是什么信息的地图? 建图并没有一个固定的形式和算法…...

统计按位或能得到最大值的子集数目
我们先来看题目描述: 给你一个整数数组 nums ,请你找出 nums 子集 按位或 可能得到的 最大值 ,并返回按位或能得到最大值的 不同非空子集的数目 。 如果数组 a 可以由数组 b 删除一些元素(或不删除)得到,…...
npm install 相关命令
npm install 相关命令 基本安装命令 # 安装 package.json 中列出的所有依赖 npm install npm i # 简写形式# 安装特定包 npm install <package-name># 安装特定版本 npm install <package-name><version>依赖类型选项 # 安装为生产依赖(默认&…...
Spring Boot 与 Kafka 的深度集成实践(二)
3. 生产者实现 3.1 生产者配置 在 Spring Boot 项目中,配置 Kafka 生产者主要是配置生产者工厂(ProducerFactory)和 KafkaTemplate 。生产者工厂负责创建 Kafka 生产者实例,而 KafkaTemplate 则是用于发送消息的核心组件&#x…...
【学习记录】使用 Kali Linux 与 Hashcat 进行 WiFi 安全分析:合法的安全测试指南
文章目录 📌 前言🧰 一、前期准备✅ 安装 Kali Linux✅ 获取支持监听模式的无线网卡 🛠 二、使用 Kali Linux 进行 WiFi 安全测试步骤 1:插入无线网卡并确认识别步骤 2:开启监听模式步骤 3:扫描附近的 WiFi…...
后端下载限速(redis记录实时并发,bucket4j动态限速)
✅ 使用 Redis 记录 所有用户的实时并发下载数✅ 使用 Bucket4j 实现 全局下载速率限制(动态)✅ 支持 动态调整限速策略✅ 下载接口安全、稳定、可监控 🧩 整体架构概览 模块功能Redis存储全局并发数和带宽令牌桶状态Bucket4j Redis分布式限…...

vue3 手动封装城市三级联动
要做的功能 示意图是这样的,因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件 所以只能自己分装 组件 当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样 毕竟是 三个 数组 省份 城市 区域 我直接粘贴组件代码了 <temp…...
Angular中Webpack与ngx-build-plus 浅学
Webpack 在 Angular 中的概念 Webpack 是一个模块打包工具,用于将多个模块和资源打包成一个或多个文件。在 Angular 项目中,Webpack 负责将 TypeScript、HTML、CSS 等文件打包成浏览器可以理解的 JavaScript 文件。Angular CLI 默认使用 Webpack 进行项目…...
大模型智能体核心技术:CoT与ReAct深度解析
**导读:**在当今AI技术快速发展的背景下,大模型的推理能力和可解释性成为业界关注的焦点。本文深入解析了两项核心技术:CoT(思维链)和ReAct(推理与行动),这两种方法正在重新定义大模…...
信息系统分析与设计复习
2024试卷 单选题(20) 1、在一个聊天系统(类似ChatGPT)中,属于控制类的是()。 A. 话语者类 B.聊天文字输入界面类 C. 聊天主题辨别类 D. 聊天历史类 解析 B-C-E备选架构中分析类分为边界类、控制类和实体类。 边界…...

Linux【5】-----编译和烧写Linux系统镜像(RK3568)
参考:讯为 1、文件系统 不同的文件系统组成了:debian、ubuntu、buildroot、qt等系统 每个文件系统的uboot和kernel是一样的 2、源码目录介绍 目录 3、正式编译 编译脚本build.sh 帮助内容如下: Available options: uboot …...