【OpenCV】Python图像处理几何变换之透视

透视变换(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):将虚拟物体贴合到真实场景的透视平面上。

五、注意事项

  1. 角点顺序一致性src_ptsdst_pts 必须按相同顺序排列(如顺时针 / 逆时针),否则会导致变换错乱;
  2. 角点准确性:手动选点时,需精准点击目标物体的四个角点(如文档的四个顶点),否则矫正效果差;自动选点时,需确保图像背景与前景对比度高(避免边缘检测失败);
  3. 输出尺寸选择:目标尺寸需匹配物体的实际宽高比(如文档的长宽比),否则会导致图像拉伸;
  4. 插值算法 :透视变换会改变像素分布,推荐用 cv2.INTER_CUBIC(高质量)或 cv2.INTER_LINEAR(速度与质量平衡),避免用 INTER_NEAREST(易产生锯齿);
  5. 边界填充 :变换后图像边缘可能出现黑边,用 borderValue 设置为白色或与背景匹配的颜色,提升视觉效果;
  6. 矩阵可逆性 :透视矩阵是可逆的(通过 cv2.invert() 求逆),可实现 "逆透视变换"(将矫正后的图像还原为原透视视角)。

通过 cv2.getPerspectiveTransform() 构造矩阵、cv2.warpPerspective() 应用变换,可轻松实现透视变换的各类需求。其中,倾斜文档自动矫正是最实用的场景,掌握角点提取、排序和透视矩阵计算的逻辑,就能应对大多数透视变换任务。

相关推荐
小鸡吃米…4 小时前
Python编程语言面试问题一
python·面试
天外飞雨5 小时前
室内重跑EKF
python
五阿哥永琪5 小时前
Spring Boot 权限控制三件套:JWT 登录校验 + 拦截器 + AOP 角色注解实战
java·spring boot·python
yoyo君~5 小时前
FAST-LIVO2 深度技术解析
算法·计算机视觉·机器人·无人机
这张生成的图像能检测吗5 小时前
Wonder3D: 跨域扩散的单图像3D重建技术
pytorch·深度学习·机器学习·计算机视觉·3d·三维重建·扩散模型
叶子丶苏5 小时前
第十七节_PySide6基本窗口控件深度补充_窗口绘图类(QPicture类) 下篇
python·pyqt
c骑着乌龟追兔子6 小时前
Day 42 复习日
python
Robot侠6 小时前
视觉语言导航从入门到精通(二)
开发语言·人工智能·python·llm·vln
无限大.6 小时前
为什么玩游戏需要独立显卡?——GPU与CPU的分工协作
python·玩游戏