python图像处理实践:拍照文档扫描
- 效果
- 原理
- 代码
- 补充说明
效果
第一张是我自己拍的,字比较清楚;第二张是网图,比较模糊,但总体效果可以接受。
原理
首先,我们可以认定,我们要处理的图都是符合下面两点的:
- 拍的文档是长方形的
- 文档在整个图里占很大比例
当然,如果背景是个比较单纯的底色,识别效果肯定会更好。
自然语言来描述的话,流程大致如下:
- 找到文档,将其摆平
- 黑白化
为了实现流程2,当然使用阈值分割。关键问题在于阈值如何选取。我尝试了全局阈值(即固定一个值,比如img > 75这样),效果很差,文档会白一块黑一块。全局阈值只适用于背景分布均匀的图片。
对于这个问题,可以用动态阈值(自适应阈值、局部阈值)(dynamic,adaptive or local thresholding)来解决。文档大多是白底黑字的,每一个字符与其旁边的空白对比都很强烈,所以我们可以将图像分割成很多小块,找到小块里的最合适阈值(实际代码中是进行一次滤波,得到mask)。这样效果好很多。详细请参见threshold。
详细流程:
- 原图进行缩放,去掉细节信息。
- 转灰度图,高斯滤波,提取边缘,提取轮廓。
- 选择面积最大的5个轮廓,进行轮廓近似,若近似后的轮廓只有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()