highlight: a11y-dark theme: smartblue
文档识别
一、总体方法个流程讲解
将发票或者纸张上面的文本信息提取出来。
如何将图片中的纸张提取出来?
利用轮廓检测。利用周长或者面积筛选出所需要的纸张结果.
如何将纸张朝向转化为正对屏幕(便于识别)?
利用定位点信息,解出变换矩阵。对原图像的纸张像素点都乘以此变换矩阵,得到结果图像。具体来说就是利用识别出来的轮廓中的四个定位点和最终需要展现的窗口四个角联立方程解出,然后对原纸张的所有像素点都经过此方程进行处理即可。
如何得到文本信息?
使用tesseract-ocr辅助识别并输出。
二、tesseract-ocr的安装配置方法
下载Tesseract-OCR
网址:https://digi.bib.uni-mannheim.de/tesseract/
一路next就行
配置:
首先在系统环境变量中加入对应的安装目录(如E:\Program Files (x86)\Tesseract-OCR),然后在cmd终端输入 tesseract -v进行测试,显示版本号则安装成功。
安装成功后可以在终端使用 tesseract XXX.png 得到识别结果。
配置python内的pytesseract
在anaconda prompt中输入 pip install pytesseract 下载对应的模块,便于python后续调用。
另外还要在anaconda/ lib /site-packges /pytesseract/ pytesseract.py目录下找到 tesseract_cmd 并将上面的 tesseract.exe路径输入到此,千万注意转义字符。
三、代码部分
1. scan.py
这个处理程序的主要功能是将图像中的ROI提取出来并进行变换,使ROI正对屏幕,便于进一步处理,提取文本信息。
# 这个程序的主要调参是在:# 1. 读取的图片的ROI一定要便于切割,浅色纸张一定要用深色背景# 2. 在对图像进行初步预处理生成轮廓时操作时,阈值可以进行调整,一定要将纸张的轮廓绘制成闭合的# 3. 若 ROI 提取后,文本内容被阈值操作抹除了,则应该在最后的二值化操作中调整下限
引入文件并配置参数
# 导入工具包import numpy as npimport argparseimport cv2# 设置启动参数# 对应的参数在启动环境里面配置 : Edit Configuration -> parameters -> --image ./images/XXX.jpgap = argparse.ArgumentParser()ap.add_argument("-i", "--image", required = True, help = "Path to the image to be scanned") args = vars(ap.parse_args())
自定义函数部分(核心)
# 下面的两个自定义函数是整个文档预处理工作的核心# 自定义函数,pts 就是 4 行 2 列的阵列,此例传入参数为四个轮廓角点# 其实也就是将获取到的4各轮廓角点进行规范化,编上号,便于后面得矩阵变换def order_points(pts): # 一共4个坐标点,生成 4 行 2 列 的阵列 rect = np.zeros((4, 2), dtype = "float32") # 四个角点的顺序为: # 0 1 # 3 2 # 计算左上,右下,按照 列 来求和,就是合并 两列为 一列 # 即所有横纵坐标进行相加,最小的就是左上,最大的就是右下 s = pts.sum(axis = 1) # s 是 4 值 列表 rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] # 计算右上和左下,按列相减(对应到角点坐标(x, y),就是计算 y - x),会出现负数 # 最小的 diff = np.diff(pts, axis = 1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] return rect# 自定义函数,用于计算变换矩阵, pts 就是 4 行 2 列的阵列,此例传入参数为四个轮廓角点def four_point_transform(image, pts): # pts是轮廓点集,获取输入坐标点 rect = order_points(pts) (tl, tr, br, bl) = rect # 计算输入的 w 和 h 值 # 因为输入的轮廓四个点并不一定是规规矩矩的矩形 # 所以我们要计算各点之间的欧氏距离,并将较大者作为对应的宽和长 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)) # 变换后对应坐标位置,长度 -1 是为了防止越界 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# 重写resize函数,按照比例对图像进行缩放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
图像处理部分
# 预处理# 灰度化gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 处理放缩后的图像中的噪声点,虽然Canny也包括了高斯滤波,但是预先使用一次高斯滤波可以模糊一些# 细小的边缘,让Canny专注于那些“大边缘”gray = cv2.GaussianBlur(gray, (5, 5), 0)edged = cv2.Canny(gray, 75, 200)# 展示预处理结果print("STEP 1: 边缘检测")cv2.imshow("Image", image)cv2.imshow("Edged", edged)cv2.waitKey(0)cv2.destroyAllWindows()# 轮廓检测# 下面的下标有问题# 在cv2.findContour()里只返回两个值: contours, hierachy,而我们要的是contours,所以后面下标应该是0而不是1。# 注意返回值的顺序,这里的轮廓检测传入 cv2.RETR_EXTERNAL 只检测外轮廓也可cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]# 根据轮廓面积进行逆序排序,从大到小,利用切片截取前 5 个cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5]# 遍历轮廓,轮廓不一定连续的# for 语句不会限制作用域for c in cnts: # 计算轮廓长度 peri = cv2.arcLength(c, True) # c表示输入的点集 # epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数 # epsilon越小表示越精确,边缘段数就越多,越大表示越规则,边缘段数就越少 # True表示封闭的,也就是会把最后一个轮廓点和首点相连 # approxPolyDP方法返回的是轮廓点集 approx = cv2.approxPolyDP(c, 0.02 * peri, True) # 4个点的时候就拿出来,不满足大轮廓是闭合四条边的话就不会生成screenCnt从而报错 if len(approx) == 4: screenCnt = approx break# 展示结果print("STEP 2: 获取轮廓")# 直接在原图像上作画# 若此处的drawContours报错,可能是轮廓凑不齐四条边cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2)cv2.imshow("Outline", image)cv2.waitKey(0)cv2.destroyAllWindows()# 透视变换,将非正对的图像转化为正对warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)# 二值处理,便于后续识别内容warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)ref = cv2.threshold(warped, 150, 255, cv2.THRESH_BINARY)[1]cv2.imwrite('scan.jpg', ref)# 展示结果print("STEP 3: 变换")cv2.imshow("Original", resize(orig, height = 650))cv2.imshow("Scanned", resize(ref, height = 650))cv2.waitKey(0)
2. 处理结果演示
原始图像
图像预处理(灰度化),经过高斯滤波滤除噪声,以及Canny边缘检测结果
在边缘检测的基础上使用轮廓检测并绘制出纸张轮廓
将ROI转化为正对屏幕并进行阈值处理,对比效果演示
上图可以看出,ROI变换是基于外轮廓的。
3. test.py
本程序主要用于文本识别。由于前文处理已经将文本图像进行二值处理了,故此程序的预处理变为中值滤波操作。
from PIL import Imageimport pytesseractimport cv2import ospreprocess = 'blur' #thresh# 预处理gray = cv2.imread('scan.jpg',-1)image = gray.copy()# gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)## # 中值滤波或者阈值操作,cv2.THRESH_OTSU便于双峰处理if preprocess == "thresh": gray = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]if preprocess == "blur": gray = cv2.medianBlur(gray, 3)# 存储处理后的图像,以进程名称作为文件名filename = "{}.png".format(os.getpid())cv2.imwrite(filename, gray)# 调用 pytesseract 进行文字识别text = pytesseract.image_to_string(Image.open(filename))print(text)os.remove(filename)# 显示原图和处理后的灰度(二值)图cv2.imshow("Image", image)cv2.imshow("Output", gray)cv2.waitKey(0)
4. 文档识别效果演示
原图像
预处理(中值滤波)后图像
结果提取