代码改变世界

计算机视觉案例分享之实时文档扫描 - 指南

2025-09-16 10:45  tlnshuju  阅读(65)  评论(0)    收藏  举报

目录

一、核心技术原理

1. 图像预处理

2. 轮廓检测

3. 透视矫正

4. 内容增强

二、代码实现

1. 导入库

2. 辅助函数定义

2.1 图像显示函数

2.2 图像缩放函数

2.3 点排序函数

2.4 透视变换函数

3. 主程序

3.1 初始化摄像头

3.2 主循环 - 实时处理

3.3 资源释放

总结


一、核心技术原理

为什么需要实时文档扫描?

在数字化办公趋势下,纸质文档的电子化需求日益增长。传统方案存在明显痛点:

  • 扫描仪:便携性差,无法满足户外、即时扫描需求;
  • 普通拍照:文档易因拍摄角度产生透视变形(如 “梯形” 效果),文字模糊、边缘倾斜;
  • 第三方 APP:部分工具功能冗余,隐私数据存在泄露风险。

1. 图像预处理

原始摄像头图像包含大量噪声(如光线不均、背景杂物),直接检测轮廓会产生干扰。预处理的目标是 “突出文档边缘,抑制无用信息”,主要包含 3 个操作:

  • 灰度化(BGR→GRAY):将彩色图像转为单通道灰度图,减少计算量(彩色图 3 个通道,灰度图 1 个通道)。
    关键代码:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  • 高斯模糊(Gaussian Blur):用 5×5 的高斯核平滑图像,消除高频噪声(如纸张褶皱、背景纹理),避免边缘检测时误判。
    关键代码:gray = cv2.GaussianBlur(gray, (5, 5), 0)
  • 边缘检测(Canny):通过 “双阈值” 算法提取图像中明暗变化剧烈的区域(即文档边缘),输出黑白二值图(白色为边缘,黑色为背景)。
    关键代码:edged = cv2.Canny(gray, 75, 200)(75 为低阈值,200 为高阈值,可根据光线调整)

2. 轮廓检测

预处理后,文档边缘已清晰可见,下一步是通过 “轮廓检测” 找到文档的外形轮廓。轮廓本质是图像中连续的像素点集合,对应文档的边界。

  • 提取轮廓:使用cv2.findContours函数,仅保留最外层轮廓(cv2.RETR_EXTERNAL),并简化轮廓存储(cv2.CHAIN_APPROX_SIMPLE,只保留端点,减少数据量)。
    关键代码:cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
  • 筛选有效轮廓:现实场景中,背景可能存在多个轮廓(如桌面物品),需按 “面积” 排序(保留前 3 个最大轮廓),再通过 “形状” 筛选 —— 文档通常是四边形,因此保留 “轮廓近似后顶点数为 4” 且 “面积足够大”(避免误判小杂物)的轮廓。
    关键代码:approx = cv2.approxPolyDP(c, 0.05 * peri, True)0.05*peri为近似精度,peri是轮廓周长)

3. 透视矫正

由于拍摄角度问题,文档在图像中常呈现 “梯形” 或 “平行四边形”(透视变形),需通过 “透视变换” 将其转为标准矩形(正面视图),这是文档扫描的核心步骤。

  • 原理:透视变换通过一个 3×3 的变换矩阵(M),将原始图像中任意四边形的 4 个顶点,映射到目标图像的 4 个顶点(标准矩形的四个角),从而消除透视变形。
  • 步骤
    1. 排序顶点:将检测到的四边形顶点按 “左上→右上→右下→左下” 排序(通过 “顶点坐标和 / 差” 判断:和最小为左上,和最大为右下;差最小为右上,差最大为左下);
      关键函数:order_points(pts)
    2. 计算目标尺寸:根据原始四边形的边长,确定矫正后矩形的宽高(取两组对边长度的最大值,避免内容裁剪);
    3. 生成变换矩阵:用cv2.getPerspectiveTransform(rect, dst)计算矩阵Mrect为原始顶点,dst为目标顶点);
    4. 应用透视变换:用cv2.warpPerspective将原始图像按矩阵M变换,得到矫正后的文档图像。
      关键代码:warped = cv2.warpPerspective(orig, M, (maxWidth, maxHeight))

4. 内容增强

矫正后的文档可能存在光线不均(如阴影),需通过 “二值化” 将灰度图转为黑白图,突出文字内容,模拟扫描仪的 “黑白模式”。

  • 阈值二值化:设定一个阈值(如 220),将灰度值高于阈值的像素设为 255(白色,背景),低于阈值的设为 0(黑色,文字),消除灰度渐变干扰。
    关键代码:ref = cv2.threshold(warped, 220, 255, cv2.THRESH_BINARY)[1]

二、代码实现

1. 导入库

import numpy as np
import cv2
  • numpy:用于数值计算和数组操作
  • cv2:OpenCV 库,用于图像处理和计算机视觉任务

2. 辅助函数定义

2.1 图像显示函数
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(6)  # 显示6毫秒后继续执行
  • 封装了 OpenCV 的图像显示功能,方便在处理过程中查看中间结果
2.2 图像缩放函数
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
  • 保持图像宽高比不变的情况下调整图像大小
  • 使用面积插值法 (INTER_AREA),适用于缩小图像
2.3 点排序函数
def order_points(pts):
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)]  # 和最大的是右下角点
# 计算每个点的x-y差值
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]  # 差值最小的是右上角点
rect[3] = pts[np.argmax(diff)]  # 差值最大的是左下角点
return rect
  • 将四边形的四个点按 "左上、右上、右下、左下" 的顺序排列
  • 为后续透视变换做准备
2.4 透视变换函数
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
  • 将透视变形的文档转换为正面视图
  • 通过计算原始四边形的尺寸确定目标图像的大小
  • 使用透视变换矩阵实现视角矫正

3. 主程序

3.1 初始化摄像头
cap = cv2.VideoCapture(0)  # 打开默认摄像头
if not cap.isOpened():
print("Cannot open camera")
exit()
3.2 主循环 - 实时处理
while True:
flag = 0  # 文档检测标志
ret, image = cap.read()  # 读取一帧图像
orig = image.copy()  # 保存原始图像副本
if not ret:  # 读取失败则退出
print("不能读取摄像头")
break
cv_show("image", image)  # 显示原始图像
# 预处理:转为灰度图 -> 高斯模糊 -> 边缘检测
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)  # 减少噪声
edged = cv2.Canny(gray, 75, 200)  # 边缘检测
cv_show('1', edged)
# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
# 按面积排序,取最大的3个轮廓
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:3]
# 绘制轮廓
image_contours = cv2.drawContours(image, cnts, -1, (0, 255, 0), 2)
cv_show("image_contours", image_contours)
# 遍历轮廓,寻找四边形(文档)
for c in cnts:
peri = cv2.arcLength(c, True)  # 计算轮廓周长
# 多边形逼近
approx = cv2.approxPolyDP(c, 0.05 * peri, True)
area = cv2.contourArea(approx)  # 计算轮廓面积
# 筛选条件:面积足够大且是四边形
if area > 400 and len(approx) == 4:
screenCnt = approx
flag = 1  # 标记检测到文档
print(peri, area)
print('检测到文档')
break
# 如果检测到文档,进行处理
if flag == 1:
# 绘制文档轮廓
image_contours = cv2.drawContours(image, [screenCnt], 0, (0, 255, 0), 2)
cv_show("image", image_contours)
# 透视变换矫正文档
warped = four_point_transform(orig, screenCnt.reshape(4, 2))
cv_show("warped", warped)
# 文档内容提取:转为灰度图 -> 二值化
warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(warped, 220, 255, cv2.THRESH_BINARY)[1]
cv_show("ref", ref)
3.3 资源释放
cap.release()  # 释放摄像头
cv2.destroyAllWindows()  # 关闭所有窗口

总结

这个程序实现了一个简单但功能完整的实时文档扫描系统,主要流程包括:

  1. 从摄像头获取实时图像
  2. 对图像进行预处理(灰度化、模糊、边缘检测)
  3. 检测图像中的轮廓,筛选出可能是文档的四边形轮廓
  4. 对检测到的文档进行透视变换,矫正为正面视图
  5. 对矫正后的文档进行二值化处理,突出显示文档内容

通过这个程序,可以将倾斜或变形的文档拍摄成类似扫描件的效果,提高文档的可读性。