使用OpenCV构建Python文档扫描仪:从边缘检测到透视变换

林见深
林见深
发布时间:2026-10-16| 5人浏览过
原创

本文详细介绍如何使用OpenCV在Python中构建文档扫描器。通过形态学运算清理背景,利用Canny算法进行边缘检测,结合GrabCut技术去除无关背景,最终通过Douglas-Peucker算法识别角点并执行透视变换,实现从倾斜拍摄图像到正视图扫描件的自动转换。

介绍

本文将演示如何利用OpenCV库开发一个基于Python的文档扫描程序。

OpenCV是一个广泛使用的开源计算机视觉库,支持Python、C++等多种编程语言。它具备强大的图像处理能力,例如通过人脸检测系统识别照片中的面部特征。

如需深入了解OpenCV,可访问其官方文档链接:https://pypi.org/project/opencv-python/ 获取详细信息。

我们的目标是构建一个软件,能够自动校正文档角度、检测图像中的文档边界、提升文档清晰度,并输出高质量的扫描图像。具体而言,输入为相机拍摄的原始文档照片,OpenCV将处理该图像以生成优化后的结果。

系统的基本工作流程如下:

  • 形态学运算

  • 边缘与轮廓检测

  • 角点识别

  • 视角变换

执行形态学操作

形态学是一系列基于图像几何结构(如高度和宽度)而非像素值排序的图像处理算法。其核心在于处理图像的大小和形状特征。

kernel = np.ones((5,5),np.uint8)
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations= 3)

我们可以调用morphologyEx()函数来执行相关操作。在形态学中,“闭”(close)操作等效于先进行膨胀(Dilation)再进行腐蚀(Erosion)的过程。

我们将创建一个与原图尺寸相同的空白文档,因为在处理边缘时,原始图像内容可能产生干扰,我们需要避免误删有效信息。

从捕获的图像中去除背景

照片中与拍摄主体无关的部分也需要被移除。类似于图像裁剪,我们只保留文档所需的部分。这里可以使用GrabCut库来实现。

当GrabCut接收到输入图片及其边界框后,它会剔除边界外的所有元素。

为了利用GrabCut识别背景,我们还可以为用户提供手动设置文档边框的选项。

目前,GrabCut能够通过从图像四个角各取20个像素作为背景参考,自动识别前景主体。

mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (20,20,img.shape[1]-20,img.shape[0]-20)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]

这里的“rect”变量定义了我们要分离的边界区域。即使部分背景进入线条内部,这也是可接受的,因为我们的目标是确保对象的任何部分都不超出边界。

边缘与轮廓检测

目前我们拥有与原文件尺寸相同的空白文件。接下来,我们将进行边缘检测,为此使用Canny函数。

为了清理文档中的噪声,我们还应用了高斯模糊处理。

注意:Canny函数仅适用于灰度图像,因此如果图像尚未转换,需先将其转换为灰度模式。

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (11, 11), 0)
# Edge Detection.
canny = cv2.Canny(gray, 0, 200)
canny = cv2.dilate(canny, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))

在最后一行代码中,我们对图像进行了放大处理。

此后,我们可以继续进行轮廓检测:

我们仅记录最大的轮廓,并在一个新的空白文档上进行交互处理。

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (11, 11), 0)
# Edge Detection.
canny = cv2.Canny(gray, 0, 200)
canny = cv2.dilate(canny, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))

识别角点

我们将利用已检测到的四个角来对齐纸张。这里使用“Douglas-Peucker”算法和approxPolyDp()函数。

con = np.zeros_like(img)
# Loop over the contours.
for c in page:
  # Approximate the contour.
  epsilon = 0.02 * cv2.arcLength(c, True)
  corners = cv2.approxPolyDP(c, epsilon, True)
  # If our approximated contour has four points
  if len(corners) == 4:
      break
cv2.drawContours(con, c, -1, (0, 255, 255), 3)
cv2.drawContours(con, corners, -1, (0, 255, 0), 10)
corners = sorted(np.concatenate(corners).tolist())
for index, c in enumerate(corners):
  character = chr(65 + index)
  cv2.putText(con, character, tuple(c), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 1, cv2.LINE_AA)

标准化四点定位

def order_points(pts):
    rect = np.zeros((4, 2), dtype='float32')
    pts = np.array(pts)
    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.astype('int').tolist()

寻找目标坐标:

最后一组坐标用于改变图像的视角。如果照片是以一定角度拍摄的,这将非常有用。

(tl, tr, br, bl) = pts
# Finding the maximum width.
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))
# Finding the maximum height.    
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))
# Final destination co-ordinates.
destination_corners = [[0, 0], [maxWidth, 0], [maxWidth, maxHeight], [0, maxHeight]]

透视变换

源照片的坐标现在必须与我们预先发现的目标坐标对齐。完成此阶段后,图像看起来就像是从纸张正上方拍摄的一样。

# Getting the homography.
M = cv2.getPerspectiveTransform(np.float32(corners), np.float32(destination_corners))
final = cv2.warpPerspective(orig_img, M, (destination_corners[2][0], destination_corners[2][1]), flags=cv2.INTER_LINEAR)

很明显,原本以一定角度拍摄的图像现在已被完美地扫描出来。

测试观察

已在许多不同方向的照片上测试了这些代码,你也可以尝试。在每个样例中,表现均非常出色。

即使图像背景为白色(即与页面颜色相似),GrabCut也能有效工作并清晰定义边界线。

结论

本教程教会我们如何使用OpenCV快速且轻松地创建文档扫描仪。

总结:

上传图片后,执行了以下步骤:

生成与原始文件高度和宽度相同的空白文档的形态学操作

从图像中删除了背景。

检测到图像中的轮廓和边界。

检测到的图像角点,以矩形的形式

变换图像的透视图(如果有)

此文档扫描仪的一些限制: 即使文档的一部分在捕获时位于边界框架之外,该项目也应正常运行。但它也可能导致不准确的透视变换。

广泛使用的文档扫描仪应用程序采用了几种深度学习算法,因为它们的结果更加彻底和准确。

到此这篇关于使用OpenCV构建文档扫描仪的文章就介绍到这了,更多相关OpenCV构建文档扫描仪内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。