在计算机视觉领域,图像的几何变换是基础中的基础。无论是文档矫正、车牌识别还是全景拼接,都离不开两种核心变换:仿射变换透视变换。很多初学者容易混淆二者的区别,本文将从矩阵原理计算过程自由度接口调用实战示例,全方位拆解二者的差异,帮你彻底搞懂!

一、先看效果:平行四边形 vs 梯形

在开始枯燥的数学推导前,我们先直观感受一下两种变换的效果差异:

变换类型 核心特性 典型效果示意图
仿射变换 保持平行性 矩形 → 平行四边形(如平移后的卡片)
透视变换 打破平行性,模拟近大远小 矩形 → 梯形(如斜拍文档矫正为正视图)

二、数学本质:有没有“透视除法”?

1. 仿射变换:线性运算,无除法

仿射变换本质是二维平面内的线性变换(平移+旋转+缩放+错切),其核心公式为:
{x′=a⋅x+b⋅y+cy′=d⋅x+e⋅y+f\begin{cases} x' = a \cdot x + b \cdot y + c \\ y' = d \cdot x + e \cdot y + f \end{cases}{x=ax+by+cy=dx+ey+f
对应的矩阵形式为(2×32 \times 32×3 矩阵):
[x′y′]=[abcdef]⋅[xy1]\begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}[xy]=[adbecf] xy1

通俗理解:想象你在桌面上推一张扑克牌——它可以平移、旋转、拉伸,但始终是“平的”,原来平行的边(如上下边)变换后依然平行。

2. 透视变换:非线性运算,必须除法

透视变换模拟三维空间的视角变换(近大远小),其核心公式为:
{x′=a⋅x+b⋅y+cg⋅x+h⋅y+1y′=d⋅x+e⋅y+fg⋅x+h⋅y+1\begin{cases} x' = \frac{a \cdot x + b \cdot y + c}{g \cdot x + h \cdot y + 1} \\ y' = \frac{d \cdot x + e \cdot y + f}{g \cdot x + h \cdot y + 1} \end{cases}{x=gx+hy+1ax+by+cy=gx+hy+1dx+ey+f
对应的矩阵形式为(3×33 \times 33×3 矩阵):
[uvw]=[abcdefgh1]⋅[xy1]\begin{bmatrix} u \\ v \\ w \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ g & h & 1 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} uvw = adgbehcf1 xy1
最终坐标需通过透视除法得到:x′=uw, y′=vwx' = \frac{u}{w},\ y' = \frac{v}{w}x=wu, y=wv

通俗理解:想象你把扑克牌拿起来对着眼睛看——离你近的边显得宽,远的边显得窄,原来平行的边(如上下边)会在远处相交于一点(消失点)。

三、自由度与未知数:为什么仿射要3点,透视要4点?

很多人会问:“自由度是不是就是未知数的个数?”——不完全是!自由度是描述变换复杂度的指标,而未知数可能因“冗余约束”(如尺度不变性)多于自由度。

1. 仿射变换:6个自由度,需3个点

仿射变换由**平移(2自由度)+旋转(1自由度)+缩放(2自由度)+错切(1自由度)**组成,共 6个自由度
每个对应点 (x,y)→(x′,y′)(x,y) \to (x',y')(x,y)(x,y) 提供2个方程(x方向和y方向),因此需要 6÷2=36 \div 2 = 36÷2=3 个点才能解出全部参数。

2. 透视变换:8个自由度,需4个点

透视变换的 3×33 \times 33×3 矩阵本应有9个未知数,但因尺度不变性(矩阵乘以任意非零常数效果相同),可固定最后一个参数为1,剩余 8个自由度
同理,每个点提供2个方程,因此需要 8÷2=48 \div 2 = 48÷2=4 个点。

四、OpenCV实战:接口调用与代码示例

搞清楚原理后,我们用OpenCV实际跑一遍两种变换,看看代码层面的差异。

1. 仿射变换:cv2.warpAffine

接口原型:
dst = cv2.warpAffine(
    src,       # 输入图像
    M,         # 2×3仿射矩阵
    dsize,     # 输出图像尺寸 (width, height)
    flags=cv2.INTER_LINEAR,  # 插值方式
    borderMode=cv2.BORDER_CONSTANT,  # 边界填充模式
    borderValue=(0,0,0)      # 边界填充颜色
)
如何获取矩阵 M
  • 旋转/平移:直接用 cv2.getRotationMatrix2D
    # 绕中心点(200,200)旋转45°,缩放1.0倍
    M = cv2.getRotationMatrix2D(center=(200,200), angle=45, scale=1.0)
    
  • 三点映射:用 cv2.getAffineTransform(需3组对应点)
    src_pts = np.float32([[0,0], [100,0], [0,100]])  # 原图3点
    dst_pts = np.float32([[10,20], [110,30], [5,120]])  # 目标3点
    M = cv2.getAffineTransform(src_pts, dst_pts)
    
完整示例:平移+旋转
import cv2
import numpy as np

img = cv2.imread("card.jpg")
rows, cols = img.shape[:2]

# 1. 定义仿射矩阵:向右平移100,向下平移50
M = np.float32([[1, 0, 100], [0, 1, 50]])

# 2. 执行变换
dst = cv2.warpAffine(img, M, (cols, rows))  # dsize保持原图尺寸

cv2.imshow("Affine Result", dst)
cv2.waitKey(0)

2. 透视变换:cv2.warpPerspective

接口原型:
dst = cv2.warpPerspective(
    src,       # 输入图像
    M,         # 3×3透视矩阵(单应性矩阵)
    dsize,     # 输出图像尺寸 (width, height)
    flags=cv2.INTER_LINEAR,
    borderMode=cv2.BORDER_CONSTANT,
    borderValue=(0,0,0)
)
如何获取矩阵 M

必须用 cv2.getPerspectiveTransform,且需4组对应点

# 原图4个角点(斜拍的文档)
src_pts = np.float32([[56,65], [368,52], [28,387], [389,390]])
# 目标4个角点(正视图的文档,宽300,高300)
dst_pts = np.float32([[0,0], [300,0], [0,300], [300,300]])
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
完整示例:斜拍文档矫正
import cv2
import numpy as np

img = cv2.imread("document_skewed.jpg")
rows, cols = img.shape[:2]

# 1. 定义4组对应点(手动标注或用特征点检测)
src_pts = np.float32([[56,65], [368,52], [28,387], [389,390]])
dst_pts = np.float32([[0,0], [300,0], [0,300], [300,300]])

# 2. 计算透视矩阵
M = cv2.getPerspectiveTransform(src_pts, dst_pts)

# 3. 执行变换(输出300×300的正视图)
dst = cv2.warpPerspective(img, M, (300, 300))

cv2.imshow("Perspective Result", dst)
cv2.waitKey(0)

五、核心区别总结表

对比维度 仿射变换 透视变换
数学本质 线性变换(无除法) 非线性变换(需透视除法)
矩阵形状 2×3 3×3
核心特性 保持平行性 打破平行性,模拟近大远小
自由度 6个 8个
所需对应点 3个 4个
典型应用 平移、旋转、缩放、错切 文档矫正、鸟瞰图、车牌校正
OpenCV接口 cv2.warpAffine cv2.warpPerspective

六、常见问题解答(FAQ)

Q1:为什么透视变换的矩阵是3×3,但OpenCV里有时看到2×4?

A:3×3是数学上的完整矩阵,矩阵最右下角的值默认为1,2×4是是OpenCV为了计算方便而设计的“计算中间体”,是计算时中间步骤的“紧凑存储格式”(将8个自由度展开为2行4列),实际使用时仍需reshape为3×3。

Q2:仿射变换能实现“近大远小”吗?

A:不能。仿射变换是平面内的线性变换,无法模拟三维视角的深度变化,只能产生平行四边形变形。

Q3:如何判断该用哪种变换?

A:若变换后需要保持平行边(如旋转后的图片),用仿射;若需要矫正视角(如斜拍的文档变正),用透视。

七、总结

仿射变换与透视变换的本质区别在于是否引入“透视除法”

  • 仿射是“平面游戏”,只有乘法和加法,适合简单的平移旋转;
  • 透视是“立体游戏”,多了除法步骤,能模拟真实世界的近大远小。

掌握二者的区别,不仅能帮你写出正确的代码,更能让你在实际项目中灵活选择变换方式——毕竟,选对工具才能事半功倍!

如果觉得本文有帮助,欢迎点赞收藏,有问题欢迎在评论区留言讨论~ 🚀

原创不易,转载请注明出处!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐