本文讲述使用OpenCV- python以及easyocr库实现文档扫描与文字检测的思路和具体实现过程。
目录
知识准备
项目概述
实现过程
代码讲解
1.读入图片并进行预处理(灰度转换,高斯滤波)
2.对图片进行canny边缘检测,进一步处理
3.轮廓识别,轮廓排序,轮廓近似得到最外层轮廓角点
4.仿射变换,二值处理
5.文字识别
本项目需要用到以下知识:
1.OpenCV图像基础操作,如读取,灰度转换等
2.阈值操作,如二值化
3.canny边缘检测以及boundingBox构建
4.卷积核构建
5.膨胀操作
6.形态学操作如close操作
7.用pyplot查看图片,便于debug
8.高斯滤波
9.easyocr库函数调用
10.仿射变换
本项目旨在实现对于倾斜文稿的自动扫描以及文字检测
给定测试图片如下
实现最终效果如图
1.读入图片并进行预处理(灰度转换,高斯滤波)
2.对图片进行canny边缘检测,进一步处理
3.轮廓识别,轮廓排序,轮廓近似得到最外层轮廓角点
4.仿射变换,二值处理
5.文字识别
import math
import cv2
import easyocr
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('bill.png')
img_gray = cv2.imread('bill.png', 0)
# show('result', img)
# show('res', img_gray)
img_blur = cv2.GaussianBlur(img_gray, (5, 5), 1)
# show('gauss', img_blur)
本段代码实现了图片的读取和灰度转换,并按照一个5*5的卷积核进行高斯滤波操作。高斯滤波的意义在于滤除图像中的噪声点,有利于在提取边界时获得更准确有用的信息。
edges = cv2.Canny(img_blur, 50, 200)
# show('canny', edges)
kernel = np.ones((2, 2), np.uint8)
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
edges_close = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, rectKernel)
edges_dilate = cv2.dilate(edges_close, kernel, iterations=3)
# show('dilate', edges_dilate)
# show('close', edges_close)
# show('dilate', edges_dilate)
本段代码对滤波之后的图片实施了canny边缘检测,构建了一个2*2的卷积核并执行了闭操作和膨胀操作。
canny后的结果如图
闭操作结果
膨胀操作结果
可以看出,只进行一次Canny之后图片的边界信息都完整地呈现了出来,但由于我们下一步需要筛选出最外层边界,过多的边界信息会对筛选造成困难,所以加以闭操作和膨胀操作。闭操作使得文字的边界连在了一起,减少了边界的数量,增加了筛选的可靠性。膨胀操作使得边界变粗,增加了轮廓识别的可靠性。
contours, hierarchy = cv2.findContours(edges_dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours = sorted(contours, key=lambda cnts: cv2.arcLength(cnts, True), reverse=True)
img_copy = img.copy()
res = cv2.drawContours(img_copy, contours, 0, (0, 0, 255), 2)
# show('res', res)
img_copy = img.copy()
cnt = contours[0]
epsilon = 0.03 * cv2.arcLength(cnt, True) # epsilon占周长的比例
approx = cv2.approxPolyDP(cnt, epsilon, True)
res2 = cv2.drawContours(img_copy, [approx], -1, (0, 0, 255), 5)
# print(approx)
# show('res2', res2)
[[lt], [lb], [rb], [rt]] = approx
# print(lt, lb, rb, rt)
[ltx, lty] = lt
[lbx, lby] = lb
[rbx, rby] = rb
[rtx, rty] = rt
# print(ltx, lty, lbx, lby, rbx, rby, rtx, rty)
lt = (ltx, lty)
lb = (lbx, lby)
rb = (rbx, rby)
rt = (rtx, rty)
# print(lt, lb, rb, rt)
本段代码实现了轮廓检测,对检测到的所有轮廓按照长度排序,找出了长度最长的轮廓(小票的外边界)对这个轮廓进行了近似,得到了一个矩形,而且获得了它四个角点的坐标,为下一步进行仿射变换做准备。
cnt画在原图中的效果如下:
# 仿射变换
width = max(math.sqrt((rtx - ltx) ** 2 + (rty - lty) ** 2), math.sqrt((rbx - lbx) ** 2 + (rby - lby) ** 2))
height = max(math.sqrt((ltx - lbx) ** 2 + (lty - lby) ** 2), math.sqrt((rtx - rbx) ** 2 + (rty - rby) ** 2))
pts1 = np.float32([[ltx, lty], [rtx, rty], [lbx, lby], [rbx, rby]])
pts2 = np.float32([[0, 0], [width, 0], [0, height], [width, height]])
M = cv2.getPerspectiveTransform(pts1, pts2)
width = int(width)
height = int(height)
dst = cv2.warpPerspective(img, M, (width, height))
plt.subplot(121), plt.imshow(img), plt.title('Input')
plt.subplot(122), plt.imshow(dst), plt.title('Output')
plt.show()
print(dst)
resu = cv2.threshold(dst, 120, 255, cv2.THRESH_BINARY)[1]
plt.imshow(resu), plt.title('Result')
plt.show()
cv2.imwrite('OCR.jpg', resu)
这段代码实现了对小票的“摆正”。前两行求出了小票的宽和高,由于之前获得的角点围成的图形不一定是矩形,这里取了对应边长的最大值。pts1和pts2分别表示四个角点在原图中的坐标和其对应目标位置的坐标。M矩阵是原坐标向目标坐标的变换矩阵,这里可以通过cv自带的getPerspectiveTransform方法计算得出。最后通过warpPerspective方法将M与原图片相乘得到目标图片,另外还进行了二值化处理,保存,以便下一步进行OCR。
运行结果如图
# ocr
# 创建reader对象
reader = easyocr.Reader(['ch_sim', 'en'])
# 读取图像
result = reader.readtext('OCR.jpg')
print(result)
for i in result:
print(i[1])
这段代码调用了easyocr里的方法进行OCR,由于result列表的特殊结构,文字被储存在了每个列表元素的第二项。
运行结果
可以看出easyocr库对中文的支持度还是比较差的,但是基本达到了我们的目的。读者可以通过使用其他的OCR库来达到更高的识别成功率。另外,还可以对仿射变换后的图片进行更加优化的图形学处理来达到更好的效果。
此外,easyocr库需要利用CUDA来实现GPU加速,由于笔者使用的电脑为M1Pro芯片,并非nvadia显卡,故无法使用CUDA,而用CPU跑这段代码需要花一些时间,请大家务必注意。