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. 最后
本章节研究了仿射变换各种玩法,最重要的还是要融会贯通。组合变换时才能发挥它真正的实力。