计算机视觉——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识别

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

相关推荐
AI_56782 小时前
Python正则表达式终极指南:从模式匹配到文本工程的智能跃迁
人工智能·python·正则表达式
小草cys2 小时前
基于大模型的图像目标检测及跟踪算法
人工智能·算法·目标检测
lrh1228002 小时前
详解线性回归算法:原理、实现与优化(附损失函数与正则化)
人工智能·机器学习·回归
朱元禄2 小时前
AI Agent 实战课程 之 《RAG 闭环实操:RAG 闭环实操(讲师逐字稿)DeepSeek + LangChain》
人工智能·langchain
Elwin Wong2 小时前
浅析DeepSeek-OCR v1&v2
人工智能·大模型·llm·ocr·deepseek
火山引擎开发者社区2 小时前
火山引擎正式上线 102.4T 自研交换机,构建 AI 网络新底座
网络·人工智能·火山引擎
庄周迷蝴蝶2 小时前
CNN的底层实现方式
人工智能·神经网络·cnn
落雨盛夏2 小时前
深度学习|李哥考研——transformer
人工智能·深度学习·transformer
凤希AI伴侣2 小时前
凤希AI伴侣V1.3.5.0发布:从“功能堆砌”到“体验重塑”的思考
人工智能·凤希ai伴侣