python图像处理实践:拍照文档扫描

  • 效果
  • 原理
  • 代码
  • 补充说明


效果

python 扫描目录下文件名 python扫描仪_python 扫描目录下文件名


python 扫描目录下文件名 python扫描仪_图像处理_02


第一张是我自己拍的,字比较清楚;第二张是网图,比较模糊,但总体效果可以接受。

原理

首先,我们可以认定,我们要处理的图都是符合下面两点的:

  1. 拍的文档是长方形的
  2. 文档在整个图里占很大比例

当然,如果背景是个比较单纯的底色,识别效果肯定会更好。

自然语言来描述的话,流程大致如下:

  1. 找到文档,将其摆平
  2. 黑白化


为了实现流程2,当然使用阈值分割。关键问题在于阈值如何选取。我尝试了全局阈值(即固定一个值,比如img > 75这样),效果很差,文档会白一块黑一块。全局阈值只适用于背景分布均匀的图片。

对于这个问题,可以用动态阈值(自适应阈值、局部阈值)(dynamic,adaptive or local thresholding)来解决。文档大多是白底黑字的,每一个字符与其旁边的空白对比都很强烈,所以我们可以将图像分割成很多小块,找到小块里的最合适阈值(实际代码中是进行一次滤波,得到mask)。这样效果好很多。详细请参见threshold

详细流程:

  1. 原图进行缩放,去掉细节信息。
  2. 转灰度图,高斯滤波,提取边缘,提取轮廓。
  3. 选择面积最大的5个轮廓,进行轮廓近似,若近似后的轮廓只有4个顶点,则认定其是我们要寻找的文档轮廓。
  4. 透视变换,计算局部阈值,二值化。

代码

import cv2 
from skimage.filters import threshold_local
import imutils 
from imutils import perspective
img = cv2.imread('scan1.png')

#转换大小 保存副本
orig = img.copy()
ratio = img.shape[0] / 500.0
img = imutils.resize(img,height = 500)

#预处理 转灰度图->滤波->边缘检测
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray,(5,5),0)
edge = cv2.Canny(gray,75,200)

#寻找轮廓 按照面积排序
cnt,_ = cv2.findContours(edge,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
cnt = sorted(cnt,key=cv2.contourArea,reverse = True)[:5]
for c in cnt:
    #用vertex来记录每个轮廓的顶点
    peri = cv2.arcLength(c,True)
    vertex = cv2.approxPolyDP(c,0.02 * peri,True)
    #顶点个数是4 说明找到了四边形
    if len(vertex) == 4:
        break

#透视变换
transformed = perspective.four_point_transform(orig,vertex.reshape(4,2) * ratio)
transformed = cv2.cvtColor(transformed,cv2.COLOR_BGR2GRAY)

#动态阈值
T = threshold_local(transformed,15,method='gaussian',offset = 5)
transformed = (transformed > T).astype('uint8') * 255
cv2.imshow('origin',imutils.resize(orig,height=500))
cv2.imshow('scanned',imutils.resize(transformed,height=500))
cv2.waitKey(0)
cv2.destroyAllWindows()