OpenCV中透视与仿射变换的区别——从矩阵到实战的全面解析
对比维度仿射变换透视变换数学本质线性变换(无除法)非线性变换(需透视除法)矩阵形状2×33×3核心特性保持平行性打破平行性,模拟近大远小自由度6个8个所需对应点3个4个典型应用平移、旋转、缩放、错切文档矫正、鸟瞰图、车牌校正OpenCV接口仿射变换与透视变换的本质区别在于是否引入“透视除法”仿射是“平面游戏”,只有乘法和加法,适合简单的平移旋转;透视是“立体游戏”,多了除法步骤,能模拟真实世界的近
在计算机视觉领域,图像的几何变换是基础中的基础。无论是文档矫正、车牌识别还是全景拼接,都离不开两种核心变换:仿射变换与透视变换。很多初学者容易混淆二者的区别,本文将从矩阵原理、计算过程、自由度、接口调用到实战示例,全方位拆解二者的差异,帮你彻底搞懂!
一、先看效果:平行四边形 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′=a⋅x+b⋅y+cy′=d⋅x+e⋅y+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}[x′y′]=[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′=g⋅x+h⋅y+1a⋅x+b⋅y+cy′=g⋅x+h⋅y+1d⋅x+e⋅y+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:若变换后需要保持平行边(如旋转后的图片),用仿射;若需要矫正视角(如斜拍的文档变正),用透视。
七、总结
仿射变换与透视变换的本质区别在于是否引入“透视除法”:
- 仿射是“平面游戏”,只有乘法和加法,适合简单的平移旋转;
- 透视是“立体游戏”,多了除法步骤,能模拟真实世界的近大远小。
掌握二者的区别,不仅能帮你写出正确的代码,更能让你在实际项目中灵活选择变换方式——毕竟,选对工具才能事半功倍!
如果觉得本文有帮助,欢迎点赞收藏,有问题欢迎在评论区留言讨论~ 🚀
原创不易,转载请注明出处!
更多推荐
所有评论(0)