计算机视觉——Opencv(图像透视变换)

我们经常需要将纸质文档转换为电子档,但拍摄的文档照片往往存在倾斜、透视畸变等问题,导致文档内容歪斜、不易识别。想要解决这个问题,就需要用到**图像透视变换。**它能够将不规则的四边形区域映射为规整的矩形,完美消除透视畸变,广泛应用于文档矫正、车牌识别、全景拼接等场景。

本文将通过一个完整案例,从图像读取、轮廓检测,到透视变换、结果后处理,逐步完成发票/文件的自动矫正。

图片准备:

透视变换的核心流程

透视变换的实现不需要手动推导复杂的矩阵公式,OpenCV 已经封装了核心函数,整个流程可以概括为 3 步:

  1. 从原始图像中精准提取目标区域的 4 个顶点坐标(文档的四个角点)。

  2. 定义变换后图像的4 个对应顶点坐标(规整矩形的四个角点)。

  3. 利用 OpenCV 计算透视变换矩阵,再通过该矩阵完成图像的透视映射,得到矫正后的图像。

最关键的步骤是提取准确的 4 个顶点坐标,这直接决定了透视变换的最终效果。

案例

1.导入相关库

python 复制代码
import cv2
import numpy as np

2.图像显示工具函数

方便调试过程中查看每一步结果

python 复制代码
def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)

3.图像按比例缩放工具函数

避免图像过大导致处理速度慢、轮廓检测不准确

python 复制代码
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    dim = None
    (h, w) = image.shape[:2]     # 获取图像原始高度和宽度
    if width is None and height is None:
        return image       # 若未指定缩放尺寸,直接返回原始图像

    if width is None:
# 仅指定高度,按比例计算宽度
        r = height / float(h)
        dim = (int(w * r), height)
    else:
# 仅指定宽度,按比例计算高度
        r = width / float(w)
        dim = (width, int(h * r))
    resized = cv2.resize(image, dim, interpolation=inter)
    return resized

resize()函数:实现图像的等比例缩放,避免拉伸变形,核心是计算缩放比例r,再通过cv2.resize()完成缩放

4.图像读取与预处理

python 复制代码
#读取原图
image = cv2.imread(r"C:\Users\LEGION\Desktop\83a7a5a67856e5690a7a34da17c4fda7.jpg")
cv_show('image', image)

# 缩小图像,便于处理
#高度缩放到500像素,宽度按比例缩放
ratio = image.shape[0] / 500
orig = image.copy()
image = resize(orig, height=500)
cv_show('1', image)

运行结果:

5.轮廓检测

python 复制代码
print('STEP 1: 轮廓检测')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 转成灰度图
# 自动阈值二值化,突出目标区域
edged = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 寻找所有轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[-2]
# 绘制所有轮廓,便于可视化
image_contours = cv2.drawContours(image.copy(), cnts, -1, (0, 0, 255), 1)
cv_show('image_contours', image_contours)

这里使用了cv2.threshold()函数的组合参数cv2.THRESH_BINARY | cv2.THRESH_OTSU,其中:

cv2.THRESH_BINARY:二值化模式,将超过阈值的像素设为 255(白色),低于阈值的像素设为 0(黑色)。

cv2.THRESH_OTSU:自动寻找最佳阈值,无需手动指定,适用于背景和目标区域对比度较为明显的图像(如文档照片),能够有效提升二值化效果。

cv2.findContours():用于寻找图像中的轮廓,返回值取[-2]是为了兼容不同版本的 OpenCV,避免因版本差异导致报错。

运行结果:

6.找到最大轮廓(逼近为四边形)

python 复制代码
print("STEP 2: 获取最大轮廓")
screenCnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]  # 按面积排序,取最大
peri = cv2.arcLength(screenCnt, True)  # 计算周长
screenCnt = cv2.approxPolyDP(screenCnt, 0.05 * peri, True)  # 多边形逼近,保留主要拐点
image_contour = cv2.drawContours(image.copy(), [screenCnt], -1, (0, 0, 255), 2)
cv2.imshow("image_contour", image_contour)
cv2.waitKey(0)

cv2.approxPolyDP():用于多边形逼近,核心作用是简化轮廓。我们的目标是获取文档的 4 个角点,因此通过该函数将复杂的最大轮廓简化为四边形,逼近精度0.05 * peri可根据实际图像微调,过大会导致轮廓失真,过小则无法简化为四边形。

运行结果:

7.顶点坐标排序函数order_points()

python 复制代码
def order_points(pts):
#一共4个坐标点
    rect = np.zeros((4, 2), dtype="float32")#用来存储排序之后的坐标位置#按顺序找到对应坐标0123分别是左上,右上,右下,左下
    s=pts.sum(axis=1) #对pts矩阵的每一行进行求和操作。 (x+y)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    diff=np.diff(pts,axis=1) #对pts矩阵的每一行进行求差操作。(y-x)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect

和最小的为左上顶点

和最大的为右下顶点

差最小的为右上顶点

差最大的为左下顶点

8.透视变换核心函数four_point_transform()

调用order_points()函数,对输入顶点坐标进行排序。

计算变换后图像的宽度和高度(取两组对边的最大值,保证文档完整显示,不被裁剪)。

定义变换后图像的 4 个对应顶点坐标(规整矩形的四个角点)。

利用cv2.getPerspectiveTransform()计算透视变换矩阵M

利用cv2.warpPerspective()执行透视变换,返回矫正后的图像。

python 复制代码
def four_point_transform(image, pts):
#获取输入坐标点
    rect = order_points(pts)
    (tl, tr, br, bl) = rect
#计算输入的w和h值
    widthA=np.sqrt(((br[0] -bl[0]) ** 2) + ((br[1] -bl[1]) ** 2))
    widthB=np.sqrt(((tr[0] -tl[0]) ** 2) + ((tr[1] -tl[1]) ** 2))
    maxWidth = max(int(widthA), int(widthB))
    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB=np.sqrt(((tl[0]-bl[0])** 2) + ((tl[1] - bl[1]) ** 2))
    maxHeight = max(int(heightA), int(heightB))
#变换后对应坐标位置
    dst = np.array( [[0, 0], [maxWidth -1, 0],[maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype="float32")
#图像透视变换 cv2.getPerspectiveTransform(src,dst[,solveMethod]) MP获得转换之间的关系
# src:变换前图像四边形顶点坐标
# dst:变换后图像四边形顶点坐标
# cv2.warpPerspective(src, MP, dsizel, dst[, flags[, borderNode[, borderValue]]]])  dst
#参数说明:
#src:原图
#MP:透视变换矩阵,3行3列
#dsize:输出图像的大小,二元元组(width,height)
    M = cv2.getPerspectiveTransform(rect, dst)
    warped = cv2.warpPerspective(image, M,  (maxWidth, maxHeight))#返回变换后结果
    return warped

运行结果:

9.执行透视变换与结果优化

python 复制代码
# 执行透视变换(将缩放后图像的顶点坐标还原到原始图像坐标体系)
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)

# 对透视变换结果进行逆时针旋转90度(不覆盖原始矫正结果)
warped_rotated = cv2.rotate(warped, cv2.ROTATE_90_COUNTERCLOCKWISE)

# 保存并显示原始矫正结果(未旋转)
cv2.imwrite('invoice_new_original.jpg', warped)
cv2.namedWindow('xx_original(未旋转透视结果)', cv2.WINDOW_NORMAL)
cv2.imshow('xx_original(未旋转透视结果)', warped)
cv2.waitKey(0)
cv2.destroyWindow('xx_original(未旋转透视结果)')

# 保存并显示逆时针旋转90度后的矫正结果
cv2.imwrite('invoice_new_rotated.jpg', warped_rotated)
cv2.namedWindow('xx_rotated(逆时针旋转90度后结果)', cv2.WINDOW_NORMAL)
cv2.imshow('xx_rotated(逆时针旋转90度后结果)', warped_rotated)
cv2.waitKey(0)

# 释放所有窗口,清理内存资源
cv2.destroyAllWindows()

运行结果:

除此之外,还可以有如下的后续处理步骤:

  • 二值化:提高文字对比度,便于后续OCR识别

  • 腐蚀:去除孤立噪点,让边缘更干净

相关推荐
勾股导航2 小时前
大模型Skill
人工智能·python·机器学习
卷福同学4 小时前
【养虾日记】Openclaw操作浏览器自动化发文
人工智能·后端·算法
春日见4 小时前
如何入门端到端自动驾驶?
linux·人工智能·算法·机器学习·自动驾驶
光锥智能4 小时前
从自动驾驶到 AI 能力体系,元戎启行 GTC 发布基座模型新进展
人工智能
luoganttcc4 小时前
自动驾驶 世界模型 有哪些
人工智能·机器学习·自动驾驶
潘高5 小时前
10分钟教你手撸一个小龙虾(OpenClaw)
人工智能
禁默5 小时前
光学与机器视觉:解锁“机器之眼”的核心密码-《第五届光学与机器视觉国际学术会议(ICOMV 2026)》
人工智能·计算机视觉·光学
深小乐5 小时前
不是DeepSeek V4!这两个神秘的 Hunter 模型竟然来自小米
人工智能
laozhao4325 小时前
科大讯飞中标教育管理应用升级开发项目
大数据·人工智能
rainbow7242445 小时前
AI人才简历评估选型:技术面试、代码评审与项目复盘的综合运用方案
人工智能·面试·职场和发展