第一张是我自己拍的,字比较清楚;第二张是网图,比较模糊,但总体效果可以接受。
首先,我们可以认定,我们要处理的图都是符合下面两点的:
当然,如果背景是个比较单纯的底色,识别效果肯定会更好。
自然语言来描述的话,流程大致如下:
至于流程1,我在我的上 一篇博客透视变换里详细介绍过,这里不再赘述。
为了实现流程2,当然使用阈值分割。关键问题在于阈值如何选取。我尝试了全局阈值(即固定一个值,比如img > 75这样),效果很差,文档会白一块黑一块。全局阈值只适用于背景分布均匀的图片。
对于这个问题,可以用动态阈值(自适应阈值、局部阈值)(dynamic,adaptive or local thresholding)来解决。文档大多是白底黑字的,每一个字符与其旁边的空白对比都很强烈,所以我们可以将图像分割成很多小块,找到小块里的最合适阈值(实际代码中是进行一次滤波,得到mask)。这样效果好很多。详细请参见threshold。
详细流程:
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()
imutils是Adrian Rosebrock大神开发的一个图像处理工具库,非常非常实用,封装了一些cv2的常用函数。(夹带一点私货)可以关注我,我后面会更新这个库的使用方法