欢迎光临
我们一直在努力

小白也能学会!用 Python+OpenCV 自动矫正发票 / 文档透视畸变

📝 本文将带你从零开始,用几行代码实现自动识别发票轮廓 + 透视矫正,把歪歪扭扭的扫描件变成规整的电子文档,全程无门槛,复制粘贴就能跑!


🎯 你能学到什么?

  • 图像预处理:缩小、灰度化、边缘检测
  • 轮廓识别:自动找到发票的最大外轮廓
  • 坐标排序:给四边形顶点排好顺序,避免变换错乱
  • 透视变换:把倾斜的发票 “掰正”,输出规整图像
  • 完整代码:直接复制运行,小白也能出效果

🧰 准备工作

1. 安装依赖

打开终端 / 命令提示符,执行下面这行命令:

bash

运行

pip install opencv-python numpy matplotlib

  • opencv-python:处理图像的核心库
  • numpy:处理数组和坐标计算
  • matplotlib:可选,用来画直方图

2. 准备素材

找一张倾斜的发票 / 文档照片(比如fapiao.jpg),放在和代码同一个文件夹里。


🧩 核心函数拆解(小白友好版)

1. 图像显示工具函数

为了避免重复写cv2.imshow+cv2.waitKey,我们先封装一个显示函数:

python

运行

import cv2
import numpy as np

def cv_show(name, img):
"""显示图像,按任意键关闭窗口"""
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()

✅ 用法:cv_show("窗口名", 图像变量),调试时超方便!


2. 四边形坐标排序函数

透视变换需要固定顺序的四个顶点(左上→右上→右下→左下),否则图像会乱掉:

python

运行

def order_points(pts):
# 初始化4个坐标点的容器
rect = np.zeros((4, 2), dtype="float32")

# 按x+y的和排序:最小=左上,最大=右下
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)] # 左上
rect[2] = pts[np.argmax(s)] # 右下

# 按y-x的差排序:最小=右上,最大=左下
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)] # 右上
rect[3] = pts[np.argmax(diff)] # 左下

return rect

💡 原理:利用坐标的和与差,自动给四个顶点排好队,不用手动指定顺序!


3. 四点透视变换函数

这是 “掰正” 图像的核心:

python

运行

def four_point_transform(image, pts):
# 1. 先把坐标排好序
rect = order_points(pts)
tl, tr, br, bl = rect # 拆成四个点

# 2. 计算矫正后图像的宽和高
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)) # 取最大高度

# 3. 定义矫正后图像的目标坐标
dst = np.array([
[0, 0], # 新左上
[maxWidth – 1, 0], # 新右上
[maxWidth – 1, maxHeight – 1], # 新右下
[0, maxHeight – 1] # 新左下
], dtype="float32")

# 4. 计算透视变换矩阵
M = cv2.getPerspectiveTransform(rect, dst)
# 5. 执行变换,输出矫正后的图像
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))

return warped

✅ 一句话总结:输入原图 + 四个顶点,输出 “掰正” 的规整图像!


4. 图像缩放函数

避免图片太大导致卡顿,先缩小再处理:

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(原图, height=500) → 把高度缩到 500 像素,宽度自动等比例变化。


🚀 完整流程:从倾斜发票到规整文档

步骤 1:读取并预处理图像

python

运行

# 1. 读取发票图片
image = cv2.imread("fapiao.jpg")
cv_show("原始图像", image)

# 2. 记录缩放比例(后面要还原坐标)
ratio = image.shape[0] / 500.0
orig = image.copy() # 保存原图,最后用它矫正
image = resize(orig, height=500) # 缩小到高度500像素
cv_show("缩小后的图像", image)


步骤 2:边缘检测 + 轮廓识别

python

运行

print("STEP 1: 轮廓检测")
# 1. 转灰度图(减少计算量)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 2. 二值化(自动找阈值,突出边缘)
edged = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

# 3. 找所有轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[-2]

# 4. 画出所有轮廓(调试用)
image_contours = cv2.drawContours(image.copy(), cnts, -1, (0, 0, 255), 1)
cv_show("所有轮廓", image_contours)


步骤 3:找到最大轮廓(发票边缘)

python

运行

print("STEP 2: 获取最大轮廓")
# 1. 按面积排序,取最大的那个(就是发票的外轮廓)
screenCnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]

# 2. 轮廓近似(把复杂曲线变成四边形)
peri = cv2.arcLength(screenCnt, closed=True) # 计算轮廓周长
screenCnt = cv2.approxPolyDP(screenCnt, 0.05 * peri, closed=True) # 近似成四边形

# 3. 画出最大轮廓(调试用)
image_contour = cv2.drawContours(image.copy(), [screenCnt], -1, (0, 255, 0), 2)
cv_show("最大轮廓(发票边缘)", image_contour)

💡 关键:approxPolyDP会把锯齿状的轮廓变成规整的四边形,正好是发票的四个角!


步骤 4:执行透视矫正

python

运行

print("STEP 3: 透视矫正")
# 1. 还原坐标(因为之前缩小了图像,坐标要乘回缩放比例)
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)

# 2. 保存矫正后的图像
cv2.imwrite("invoice_new.jpg", warped)

# 3. 显示结果
cv2.namedWindow("矫正后的发票", cv2.WINDOW_NORMAL) # 可缩放窗口
cv2.imshow("矫正后的发票", warped)
cv2.waitKey(0)
cv2.destroyAllWindows()


✨ 运行效果

  • 输入:一张倾斜、拍摄角度不正的发票照片
  • 输出:invoice_new.jpg → 一张完全规整、水平的发票扫描件,和扫描仪扫出来的一样!

📌 小白常见问题解答

Q1:运行报错ModuleNotFoundError: No module named 'cv2'

A:没装 OpenCV,回到准备工作部分,执行pip install opencv-python。

Q2:找不到发票轮廓,画出的轮廓是乱的

A:

  • 检查图片:确保发票在画面中占主要部分,背景不要太复杂
  • 调整参数:把0.05 * peri改成0.03 * peri或0.08 * peri,让轮廓更精确 / 更简化
  • 光线问题:保证发票光线均匀,避免过暗 / 过曝

Q3:矫正后的图像是倒的 / 歪的

A:

  • 检查order_points函数是否正确复制,坐标顺序错了会导致图像翻转
  • 确保最大轮廓是四边形(screenCnt.shape应该是(4, 1, 2))

🎉 总结

你只用了几十行代码,就实现了专业扫描仪级别的文档矫正!

  • 核心逻辑:轮廓识别 → 坐标排序 → 透视变换
  • 适用场景:发票、身份证、合同、书本页面等所有四边形文档
  • 扩展玩法:可以结合 OCR 工具(如pytesseract),直接把矫正后的图像转成可编辑文字!

完整代码一键复制

把下面所有代码粘到invoice_correction.py里,放一张fapiao.jpg在同目录,直接运行即可:

python

运行

import cv2
import numpy as np

def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()

def order_points(pts):
rect = np.zeros((4, 2), dtype="float32")
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect

def four_point_transform(image, pts):
rect = order_points(pts)
tl, tr, br, bl = rect
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")
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
return warped

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

# 主流程
if __name__ == "__main__":
image = cv2.imread("fapiao.jpg")
ratio = image.shape[0] / 500.0
orig = image.copy()
image = resize(orig, height=500)

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]
screenCnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
peri = cv2.arcLength(screenCnt, closed=True)
screenCnt = cv2.approxPolyDP(screenCnt, 0.05 * peri, closed=True)

warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
cv2.imwrite("invoice_new.jpg", warped)
cv_show("矫正结果", warped)


要不要我再帮你写一篇OCR 文字识别的扩展教程,把矫正后的发票直接转成可复制的文字?这样就能实现 “拍照→矫正→提取文字” 的全自动流程了!

赞(0)
未经允许不得转载:171主机测评 » 小白也能学会!用 Python+OpenCV 自动矫正发票 / 文档透视畸变
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址