透视变换(Perspective Transformation)是一种非线性几何变换 ,核心是将图像从一个透视视角映射到另一个透视视角,允许平行线变为相交线(区别于保持平行性的仿射变换),能精准模拟真实世界的透视效果。OpenCV 通过 cv2.getPerspectiveTransform() 构造透视矩阵,结合 cv2.warpPerspective() 实现变换,广泛用于文档矫正、车牌识别、3D 视角模拟等场景。
一、核心原理
1. 透视变换的数学表达
对于图像中任意像素点 (x, y)(齐次坐标 (x, y, 1)),透视变换后映射到新坐标 (x', y'),数学公式为:

最终归一化坐标为:

其中:
- 3×3 矩阵
[[a,b,c],[d,e,f],[g,h,i]]为透视变换矩阵 (OpenCV 中需用np.float32类型); - 矩阵最后一行
[g,h,i]是透视变换的核心,决定了视角的扭曲效果(仿射变换中g=h=0, i=1,无透视效果); - 透视变换矩阵有 8 个自由度,需通过 4 个非共线点 的映射关系唯一确定。
2. 透视变换的核心特点
- 不保持平行性:原图中平行的线条,变换后可相交(如道路向远方汇聚);
- 不保持距离比例:不同区域的缩放比例可不同,更贴近人眼的透视感知;
- 需 4 个点:必须通过原图 4 个非共线点及其变换后的对应点,才能计算透视矩阵(仿射变换仅需 3 个点)。
3. 与仿射变换的关键区别
| 特性 | 透视变换 | 仿射变换 |
|---|---|---|
| 变换矩阵 | 3×3 矩阵(8 个自由度) | 2×3 矩阵(6 个自由度) |
| 点映射需求 | 4 个非共线点 | 3 个非共线点 |
| 核心特点 | 不保持平行性、距离比例 | 保持平行性、共线性 |
| 适用场景 | 复杂视角矫正、3D 模拟 | 简单姿态调整、平移旋转 |
| 典型例子 | 倾斜文档 → 正射投影 | 图像旋转、平移、缩放 |
二、核心函数详解
1. cv2.getPerspectiveTransform():构造透视矩阵
函数原型
python
cv2.getPerspectiveTransform(src_pts, dst_pts)
参数说明
| 参数 | 含义 | 要求 |
|---|---|---|
src_pts |
原图中 4 个非共线点的坐标 | 格式为 np.float32([[x1,y1], [x2,y2], [x3,y3], [x4,y4]]) |
dst_pts |
变换后对应 4 个点的坐标 | 格式与 src_pts 一致,需按相同顺序排列(如顺时针 / 逆时针) |
返回值
3×3 透视变换矩阵(np.float32 类型)。
2. cv2.warpPerspective():应用透视变换
函数原型
python
cv2.warpPerspective(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
参数说明
| 参数 | 含义 | 注意事项 |
|---|---|---|
src |
输入图像 | 单通道 / 多通道均可(灰度图 / 彩色图) |
M |
透视变换矩阵 | 必须是 3×3 的 np.float32 矩阵 |
dsize |
输出图像尺寸 | 格式为 (width, height)(与 OpenCV 图像 shape 相反) |
flags |
插值算法(可选) | 默认 cv2.INTER_LINEAR,高质量场景用 INTER_CUBIC |
borderMode |
边界填充模式(可选) | 默认 cv2.BORDER_CONSTANT(常数填充),也可设为 BORDER_REPLICATE(复制边界) |
borderValue |
边界填充值(可选) | 仅 borderMode=BORDER_CONSTANT 有效,默认黑色(0),彩色图用 (b,g,r) 格式 |
返回值
变换后的输出图像(numpy.ndarray 类型)。
三、完整实现代码(多种场景)
1. 基础场景:手动选 4 点实现透视变换
通过手动指定原图 4 个点和目标点,实现任意透视效果(如将倾斜的矩形转为正矩形):
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像
img = cv2.imread("book.jpg") # 倾斜的书本图像
if img is None:
print("无法读取图像,请检查路径!")
exit()
h, w = img.shape[:2]
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转为RGB用于matplotlib显示
# 2. 定义原图4个非共线点(按顺时针顺序:左上角、右上角、右下角、左下角)
# 可通过图像查看工具(如画图)获取实际坐标,此处为示例坐标
src_pts = np.float32([[50, 60], [350, 50], [380, 400], [70, 420]])
# 3. 定义变换后目标点(正矩形,如宽300、高400)
target_w, target_h = 300, 400
dst_pts = np.float32([[0, 0], [target_w-1, 0], [target_w-1, target_h-1], [0, target_h-1]])
# 4. 计算透视变换矩阵
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
# 5. 应用透视变换
img_perspect = cv2.warpPerspective(
img, M, (target_w, target_h),
flags=cv2.INTER_CUBIC, # 高质量插值
borderValue=(255, 255, 255) # 边界白色填充
)
img_perspect_rgb = cv2.cvtColor(img_perspect, cv2.COLOR_BGR2RGB)
# 6. 绘制特征点(可视化对应关系)
for (x, y) in src_pts.astype(int):
cv2.circle(img_rgb, (x, y), 6, (0, 255, 0), -1) # 原图点:绿色
for (x, y) in dst_pts.astype(int):
cv2.circle(img_perspect_rgb, (x, y), 6, (255, 0, 0), -1) # 目标点:蓝色
# 7. 显示结果
plt.figure(figsize=(15, 8))
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.title("Original Image (Green Points)")
plt.axis("off")
plt.subplot(1, 2, 2)
plt.imshow(img_perspect_rgb)
plt.title(f"Perspective Transformation ({target_h}×{target_w})")
plt.axis("off")
plt.tight_layout()
plt.show()
2. 实用场景:倾斜文档自动矫正(核心场景)
通过边缘检测和轮廓提取,自动识别文档的 4 个角点,实现全自动透视矫正(无需手动选点):
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
def get_document_corners(img):
"""
自动提取文档的4个角点(基于边缘检测和轮廓拟合)
:param img: 输入倾斜文档图像(彩色图)
:return: 4个角点坐标(np.float32,顺时针顺序)
"""
# 1. 预处理:灰度化、模糊、边缘检测
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
edges = cv2.Canny(blur, 50, 150) # 边缘检测
# 2. 查找轮廓(仅保留最大的外接矩形轮廓)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5] # 取面积前5的轮廓
# 3. 拟合轮廓的外接多边形(获取4个角点)
for cnt in contours:
perimeter = cv2.arcLength(cnt, True) # 计算轮廓周长
approx = cv2.approxPolyDP(cnt, 0.02 * perimeter, True) # 多边形拟合
if len(approx) == 4: # 找到4个角点的轮廓
return np.squeeze(approx).astype(np.float32)
raise ValueError("未检测到文档的4个角点,请调整图像或参数!")
def order_corners(pts):
"""
对4个角点按顺时针顺序排序(左上角→右上角→右下角→左下角)
:param pts: 原始4个角点(np.float32)
:return: 排序后的角点
"""
rect = np.zeros((4, 2), dtype=np.float32)
# 按x+y求和排序(左上角和为最小,右下角和为最大)
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)] # 左上角
rect[2] = pts[np.argmax(s)] # 右下角
# 按x-y差值排序(右上角差为最大,左下角差为最小)
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)] # 右上角
rect[3] = pts[np.argmax(diff)] # 左下角
return rect
def auto_correct_document(img):
"""
自动矫正倾斜文档(透视变换)
:param img: 输入倾斜文档图像
:return: 矫正后的文档图像
"""
# 1. 提取并排序角点
raw_corners = get_document_corners(img)
src_pts = order_corners(raw_corners)
# 2. 计算目标尺寸(保持文档宽高比)
# 计算原图对角点距离
w1 = np.linalg.norm(src_pts[1] - src_pts[0]) # 上边长
w2 = np.linalg.norm(src_pts[2] - src_pts[3]) # 下边长
h1 = np.linalg.norm(src_pts[3] - src_pts[0]) # 左边长
h2 = np.linalg.norm(src_pts[2] - src_pts[1]) # 右边长
target_w = int(max(w1, w2)) # 目标宽度(取上下边最大值)
target_h = int(max(h1, h2)) # 目标高度(取左右边最大值)
# 3. 定义目标点
dst_pts = np.float32([[0, 0], [target_w-1, 0], [target_w-1, target_h-1], [0, target_h-1]])
# 4. 透视变换
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
corrected = cv2.warpPerspective(
img, M, (target_w, target_h),
flags=cv2.INTER_CUBIC,
borderValue=(255, 255, 255)
)
return corrected, src_pts
# 1. 读取倾斜文档图像
img_skew = cv2.imread("skew_doc.jpg")
if img_skew is None:
print("无法读取图像,请检查路径!")
exit()
img_skew_rgb = cv2.cvtColor(img_skew, cv2.COLOR_BGR2RGB)
# 2. 自动矫正
img_corrected, src_pts = auto_correct_document(img_skew)
img_corrected_rgb = cv2.cvtColor(img_corrected, cv2.COLOR_BGR2RGB)
# 3. 绘制原始角点(可视化)
for (x, y) in src_pts.astype(int):
cv2.circle(img_skew_rgb, (x, y), 8, (0, 255, 0), -1)
# 4. 显示结果
plt.figure(figsize=(15, 10))
plt.subplot(1, 2, 1)
plt.imshow(img_skew_rgb)
plt.title("Skewed Document (Green Corners)")
plt.axis("off")
plt.subplot(1, 2, 2)
plt.imshow(img_corrected_rgb)
plt.title(f"Corrected Document ({img_corrected.shape[0]}×{img_corrected.shape[1]})")
plt.axis("off")
plt.tight_layout()
plt.show()
# 保存矫正后的图像
cv2.imwrite("corrected_doc.jpg", img_corrected)
print("文档矫正完成,已保存为 corrected_doc.jpg")
3. 创意场景:模拟 3D 透视效果
通过调整目标点的坐标,模拟图像的 3D 倾斜效果(如将正面图像转为侧面视角):
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像
img = cv2.imread("lena.jpg")
h, w = img.shape[:2]
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 2. 定义原图4个角点(图像四个顶点)
src_pts = np.float32([[0, 0], [w-1, 0], [w-1, h-1], [0, h-1]])
# 3. 定义3D透视目标点(右上角和右下角向内偏移,模拟右侧倾斜)
dst_pts = np.float32([[0, 0], [w-1, 50], [w-1, h-50], [0, h-1]])
# 4. 透视变换
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
img_3d = cv2.warpPerspective(
img, M, (w, h),
flags=cv2.INTER_CUBIC,
borderValue=(255, 255, 255)
)
img_3d_rgb = cv2.cvtColor(img_3d, cv2.COLOR_BGR2RGB)
# 5. 显示结果
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.title("Original Image")
plt.axis("off")
plt.subplot(1, 2, 2)
plt.imshow(img_3d_rgb)
plt.title("3D Perspective Effect")
plt.axis("off")
plt.tight_layout()
plt.show()
四、关键说明与应用场景
1. 透视变换的核心优势
- 精准矫正透视畸变:如相机倾斜拍摄的文档、车牌,可转为正射投影(正面视角);
- 模拟真实视角:还原人眼观察物体的透视效果(远小近大);
- 灵活调整视角:通过修改目标点坐标,实现任意方向的透视扭曲。
2. 典型应用场景
- 文档扫描矫正:手机拍摄的倾斜文档、合同、试卷,矫正为平整的扫描件;
- 车牌识别预处理:倾斜的车牌图像矫正为水平正面视角,提升识别准确率;
- 图像拼接:多视角图像拼接时,通过透视变换统一视角;
- 3D 效果模拟:游戏、影视中模拟物体的 3D 倾斜、远近透视效果;
- 增强现实(AR):将虚拟物体贴合到真实场景的透视平面上。
五、注意事项
- 角点顺序一致性 :
src_pts和dst_pts必须按相同顺序排列(如顺时针 / 逆时针),否则会导致变换错乱; - 角点准确性:手动选点时,需精准点击目标物体的四个角点(如文档的四个顶点),否则矫正效果差;自动选点时,需确保图像背景与前景对比度高(避免边缘检测失败);
- 输出尺寸选择:目标尺寸需匹配物体的实际宽高比(如文档的长宽比),否则会导致图像拉伸;
- 插值算法 :透视变换会改变像素分布,推荐用
cv2.INTER_CUBIC(高质量)或cv2.INTER_LINEAR(速度与质量平衡),避免用INTER_NEAREST(易产生锯齿); - 边界填充 :变换后图像边缘可能出现黑边,用
borderValue设置为白色或与背景匹配的颜色,提升视觉效果; - 矩阵可逆性 :透视矩阵是可逆的(通过
cv2.invert()求逆),可实现 "逆透视变换"(将矫正后的图像还原为原透视视角)。
通过 cv2.getPerspectiveTransform() 构造矩阵、cv2.warpPerspective() 应用变换,可轻松实现透视变换的各类需求。其中,倾斜文档自动矫正是最实用的场景,掌握角点提取、排序和透视矩阵计算的逻辑,就能应对大多数透视变换任务。