OpenCV-python小玩意20 仿射变换

0. 什么是仿射变换?

0.1 概念

仿射变换(Affine Transformation)是计算机视觉中的基础空间变换技术,它是一种对图像进行几何形变 的基本方法,通过一个线性映射 + 平移 的方式,对图像中的每个像素点进行重新定位,从而实现如旋转、缩放、平移、错切(shear) 等操作。

0.2 公式

它的公式如下图:

0.3 常见的仿射变换类型

变换类型 效果 应用场景
平移(Translation) 图像整体移动 图像对齐、ROI裁剪
旋转(Rotation) 绕某点转动 图像校正、数据增强
缩放(Scaling) 放大或缩小 图像金字塔、多尺度分析
错切(Shear) 倾斜变形(矩形→平行四边形) 字体变形、数据增强
组合变换 如"先旋转再平移" 任意仿射形变

1. OpenCV中的关键函数

OpenCV 提供两个关键函数:

  • cv2.getAffineTransform(src_pts, dst_pts):通过3对对应点计算仿射矩阵。
  • cv2.warpAffine(img, M, dsize):用仿射矩阵 M 对图像 img 进行变换。

1.1 getAffineTransform

函数原型:

python 复制代码
M = cv2.getAffineTransform(src_pts, dst_pts)
  • src_pts:源图像中的3个点(浮点数数组,形状为 (3,2)
  • dst_pts:目标图像中对应3个点的位置(浮点数数组,形状为 (3,2)),通过指定这些点,你可以控制图像如何变换(如旋转、缩放、平移或错切)。
  • M:返回的3x2的仿射变换矩阵

1.2 warpAffine

它是OpenCV中用于对图像应用**仿射变换(Affine Transformation)**的核心函数。它根据给定的2×3仿射变换矩阵M,将输入图像img映射到一个新的坐标系中,生成变换后的图像。

函数原型:

python 复制代码
warped_img = cv2.warpAffine(
    src,                # 输入图像
    M,                  # 2x3 仿射变换矩阵
    dsize,              # 输出图像尺寸 (width, height)
    dst=None,           # (可选)输出图像
    flags=cv2.INTER_LINEAR,     # 插值方法
    borderMode=cv2.BORDER_CONSTANT,  # 边界填充方式
    borderValue=(0, 0, 0)        # 边界填充颜色(默认黑色)
)
  • img:输入图像,类型是numpy.ndarray,可以是灰度图(单通道)或彩色图(多通道,如 BGR)
  • M:仿射变换矩阵(3x2)
  • dsize:输出图像的尺寸(宽度、高度)
  • flags:插值方法,控制如何计算非整数坐标的像素值(因为变换后坐标可能是小数),默认是 cv2.INTER_LINEAR
标志 说明
cv2.INTER_NEAREST 最近邻插值(最快,但锯齿明显)
cv2.INTER_LINEAR 双线性插值(默认,平衡速度与质量)
cv2.INTER_CUBIC 双三次插值(较慢,质量高)
cv2.INTER_LANCZOS4 Lanczos 插值(高质量,适合缩放)
  • borderMode:边界填充方式,默认是 cv2.BORDER_CONSTANT等。
模式 效果
cv2.BORDER_CONSTANT 用固定颜色填充(由 borderValue 指定,默认黑色)
cv2.BORDER_REPLICATE 复制边缘像素(如 [a b c] → [a a a b c c c]
cv2.BORDER_REFLECT 镜像反射(如 [a b c] → [c b a b c b a]
cv2.BORDER_WRAP 平铺(环绕)
  • borderValue:边界填充颜色,默认是黑色(0,0,0),可以根据需要修改。
  • warped_img:输出变换后的图像

1.3 示例

python 复制代码
import cv2
import numpy as np

# 创建一个简单的示例图像
img = np.zeros((400, 400, 3), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (350, 350), (0, 255, 0), -1)  # 绿色矩形框
cv2.circle(img, (200, 200), 30, (255, 0, 0), -1)  # 蓝色圆圈

# 定义源图像上的三个点
src_points = np.float32([[50, 50], [350, 50], [50, 350]])
# 定义目标图像上的三个对应点
dst_points = np.float32([[100, 150], [300, 50], [100, 300]])

# 计算仿射变换矩阵
affine_matrix = cv2.getAffineTransform(src_points, dst_points)

# 应用仿射变换
rows, cols = img.shape[:2]
transformed_img = cv2.warpAffine(img, affine_matrix, (cols, rows))

# 显示原图和变换后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Transformed Image', transformed_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

效果如下图:

它实现的是一个组合变换(Combined Transformations)。

2. 经典案例

2.1 旋转

这里需要再介绍一个函数:

python 复制代码
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)

该函数用于生成一个 2×3 的变换矩阵,用于对图像进行 绕指定中心点的旋转 + 缩放操作。
参数说明如下:

  • center : 这是一个包含两个值的元组 (x, y),表示你希望图像围绕哪个点进行旋转。通常情况下,我们会选择图像的中心作为旋转中心。例如,对于一张 400x400 像素大小的图像,其中心点可以是 (200, 200)

  • angle : 表示旋转的角度(以度为单位)。正值表示逆时针方向旋转,而负值则表示顺时针方向旋转。比如,如果你想让图像逆时针旋转 90 度,则设置 angle=90

  • scale : 缩放因子。允许你在旋转的同时对图像进行放大或缩小。如果 scale=1.0,则图像尺寸保持不变;若 scale>1.0 则图像被放大;反之,若 scale<1.0,图像则会被缩小。

返回值

该函数返回一个 2x3 的浮点数数组(即矩阵),这个矩阵包含了旋转和缩放所需的所有信息。它可以直接用作 cv2.warpAffine() 函数的输入参数之一,用于执行实际的仿射变换。

旋转实现

python 复制代码
import cv2
import numpy as np

# 创建一个示例图像
img = np.zeros((300, 300, 3), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (250, 250), (0, 255, 0), -1)  # 绿色矩形
cv2.circle(img, (150, 150), 30, (255, 0, 0), -1)  # 蓝色圆
cv2.putText(img, 'Affine', (90, 160), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)

# 获取图像尺寸
(h, w) = img.shape[:2]
center = (w // 2, h // 2)

# 定义旋转角度
angle = 30  # 逆时针旋转30度
scale = 1.0

# 计算旋转矩阵
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)

# 应用仿射变换
rotated_img = cv2.warpAffine(img, rotation_matrix, (w, h))

# 显示原图和旋转后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Rotated Image (Affine Transform)', rotated_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

2.2 缩放

根据上面基础例子稍加修改,就可以得到一个缩放demo。

python 复制代码
import cv2
import numpy as np

# 创建一个简单的示例图像
img = np.zeros((400, 400, 3), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (350, 350), (0, 255, 0), -1)  # 绿色矩形框
cv2.circle(img, (200, 200), 30, (255, 0, 0), -1)  # 蓝色圆圈

scale = 0.75
src_points = np.float32([[50, 50], [350, 50], [50, 350]])
dst_points = np.float32([[int(50*scale), int(50*scale)],
                         [int(350*scale), int(50*scale)],
                         [int(50*scale), int(350*scale)]])

# 计算仿射变换矩阵
affine_matrix = cv2.getAffineTransform(src_points, dst_points)

# 应用仿射变换
rows, cols = img.shape[:2]
transformed_img = cv2.warpAffine(img, affine_matrix, (cols, rows))

# 显示原图和变换后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Transformed Image', transformed_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果如下:

还有另外一种简单的玩法:

python 复制代码
import cv2
import numpy as np

img = np.zeros((300, 300, 3), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (250, 250), (0, 255, 0), -1)
cv2.circle(img, (150, 150), 40, (255, 0, 0), -1)
cv2.putText(img, 'Affine Scale', (60, 160), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)

# 缩放因子
sx = 1.5  # x方向
sy = 0.8  # y方向

# 构建仿射变换矩阵(绕原点缩放)
M = np.float32([[sx, 0, 0],
                [0, sy, 0]])

# 计算输出图像尺寸
new_w = int(img.shape[1] * sx)
new_h = int(img.shape[0] * sy)

# 应用仿射变换
scaled_img = cv2.warpAffine(img, M, (new_w, new_h), flags=cv2.INTER_LINEAR)

cv2.imshow('Original', img)
cv2.imshow('Scaled (Affine)', scaled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果如下:

2.3 平移

根据上面基础例子稍加修改,就可以得到一个平移demo。

python 复制代码
import cv2
import numpy as np

# 创建一个简单的示例图像
img = np.zeros((400, 400, 3), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (350, 350), (0, 255, 0), -1)  # 绿色矩形框
cv2.circle(img, (200, 200), 30, (255, 0, 0), -1)  # 蓝色圆圈

src_points = np.float32([[50, 50], [350, 50], [50, 350]])
dst_points = np.float32([[100, 150], [400, 150], [100, 450]])  # 向右50像素,向下100像素

# 计算仿射变换矩阵
affine_matrix = cv2.getAffineTransform(src_points, dst_points)

# 应用仿射变换
rows, cols = img.shape[:2]
transformed_img = cv2.warpAffine(img, affine_matrix, (cols, rows))

# 显示原图和变换后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Transformed Image', transformed_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

效果如下:

还可以用另一种玩法:

python 复制代码
import cv2
import numpy as np

# 假设img是你想要平移的图像。这里创建一个简单的示例图像。
img = np.zeros((300, 300, 3), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (250, 250), (0, 255, 0), -1)  # 绿色矩形
cv2.circle(img, (150, 150), 30, (255, 0, 0), -1)  # 蓝色圆
cv2.putText(img, 'Translation', (90, 160), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)

# 定义平移距离(dx, dy),即在x轴和y轴上的位移量
dx, dy = 50, 30

# 创建平移变换矩阵
translation_matrix = np.float32([[1, 0, dx], [0, 1, dy]])

# 应用仿射变换
translated_img = cv2.warpAffine(img, translation_matrix, (img.shape[1], img.shape[0]))

# 显示原图和平移后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Translated Image', translated_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

2.4 、错切(shear)

python 复制代码
import cv2
import numpy as np

# 同样地,假设img是你想要进行错切变换的图像。这里创建一个简单的示例图像。
img = np.zeros((300, 300, 3), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (250, 250), (0, 255, 0), -1)  # 绿色矩形
cv2.circle(img, (150, 150), 30, (255, 0, 0), -1)  # 蓝色圆
cv2.putText(img, 'Shear', (90, 160), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)

# 定义错切系数(shx, shy),分别为水平方向和垂直方向的错切系数
shx, shy = 0.3, 0.2

# 创建错切变换矩阵
shear_matrix = np.float32([[1, shx, 0], [shy, 1, 0]])

# 计算变换后图像的新尺寸,避免裁剪
new_width = int(img.shape[1] + abs(shx * img.shape[0]))
new_height = int(img.shape[0] + abs(shy * img.shape[1]))
shear_matrix[0, 2] += (new_width / 2) - img.shape[1] / 2
shear_matrix[1, 2] += (new_height / 2) - img.shape[0] / 2

# 应用仿射变换
sheared_img = cv2.warpAffine(img, shear_matrix, (new_width, new_height))

# 显示原图和错切后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Sheared Image', sheared_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

效果如下:

3. 最后

本章节研究了仿射变换各种玩法,最重要的还是要融会贯通。组合变换时才能发挥它真正的实力。

相关推荐
AngelPP3 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年3 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
AI探索者3 小时前
LangGraph StateGraph 实战:状态机聊天机器人构建指南
python
AI探索者3 小时前
LangGraph 入门:构建带记忆功能的天气查询 Agent
python
九狼3 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS3 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区4 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈5 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
FishCoderh5 小时前
Python自动化办公实战:批量重命名文件,告别手动操作
python
躺平大鹅5 小时前
Python函数入门详解(定义+调用+参数)
python