发表时间: 2023年1月7日 | 创作地点:湖北省武汉市 | 作者:ixy_com&[Aneerban Chakraborty] |
---|
本文关键词:文档智能、文档图像校正、OpenCV、形态学变换、边缘检测、轮廓检测、透视变换
基本概念:文档智能是指通过计算机进行自动阅读、理解以及分析商业文档的过程,是自然语言处理和计算机视觉交叉领域的一个重要研究方向。近年来,数字图像处理技术以及深度学习的飞速发展,极大地推动了文档智能领域的发展,以文档版面分析、文档信息抽取、文档视觉问答、文档图像分类以及文档图像校正等任务为代表的的文档智能任务得到了性能上的显著提升。
任务来源:为积极响应企业数字化转型发展的现实需求,我部承担了基于知识图谱的**研究任务。其中,收集得到的大规模文档数据中有极大比例的PDF扫描件。与可编辑文档PDF格式不同,该类型文档不可编辑,实为图像数据;同时,由于数据收集方等各种不可控因素,导致大量文档图像数据出现扭曲、倾斜、颠倒等情况;因此,为了便于数据处理,提升PDF扫描件的字符识别精度,首先对其进行文档图像校正处理。
---- 数字智能研发
内容说明:在本文中,我将简要介绍基于OpenCV的文档图像校正基本流程,并代码实践的形式加以展示。如下图所示,目前相关研究材料非常充足,本文仅做简单记录,用以交流。基本目标为:介绍OpenCV的基本使用,包括但不限于形态学处理、边缘检测、角点检测、透视变换等章节内容,核心内容为数字图像处理相关技术。全文内容包含以下部分:
内容目录:
Opencv是一个开源的计算机视觉库,内部包含丰富的数字图像处理工具。其支持多平台安装使用,安装教程与相关简介本文不做赘述。利用Opencv可以进行图像处理、特征检测与描述、视频分析、相机校准与3D重建、机器学习、计算摄影以及目标检测等任务。相关工具箱的集成化程度非常高,可参见Opencv 4.0文档。
这里用图像滤波举例,将使用各种低通滤波器(LPF)进行图像模糊,并对图像应用自定义滤波器(二维卷积)。LPF有助于降低噪音,高通滤波器HPF有助于在图像中找到边缘。OpenCV中的 c v . f i l t e r 2 D ( ) cv.filter2D() cv.filter2D()函数即可实现上述功能。首先读入原始图像,如下图:
# 使用自定义的卷积核(滤波器)对输入图像进行二维卷积操作
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
"""
print(np.__version__)
print(cv.__version__)
1.21.2
4.3.0
"""
img = cv.imread('./OpenCV.jpg')
kernel = np.ones((5, 5), np.float32)/25
dst = cv.filter2D(img, -1, kernel)
plt.subplot(121)
plt.title('Original image')
plt.imshow(img)
# 第二张子图
plt.subplot(122)
plt.title('Filtered image')
plt.imshow(dst)
plt.show()
# 对输入图像进行均值模糊
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
"""
print(np.__version__)
print(cv.__version__)
1.21.2
4.3.0
"""
img = cv.imread('./OpenCV.jpg')
dst = cv.blur(img, (5, 5))
plt.subplot(121)
plt.title('Original image')
plt.imshow(img)
plt.subplot(122)
plt.title('Blurred image')
plt.imshow(dst)
plt.show()
使用OpenCV提供的 c v 2. m o r p h o l o g y E x ( ) cv2.morphologyEx() cv2.morphologyEx()函数对输入图像做形态学的开与闭操作,其过程依赖于腐蚀(Erode)与膨胀(Dilate)。在本案例中,我们对输入图像进行闭操作,即先执行膨胀、再执行腐蚀操作。反复执行图像的闭操作,将得到输入图像的空白页面。
说明:因为后文需要对数据图像进行边缘检测,为了避免文档图像中的条纹或字符影响边缘检测的结果,这里将其处理为空白页面,实现过程如下代码block所示,其中示例数据来自于github。
# 首先读入待处理的原始图像
img = cv2.imread('./inputs/img1.jpg', cv2.IMREAD_COLOR)
plt.imshow(img)
# 图像预处理:输入图像的尺寸缩放以便于处理
# 首先获取输入文本图像的shape以及对应的最大值
max_dim = max(img.shape)
# 定义size的阈值
dim_limit = 1080
# 进行size判断
if max_dim > dim_limit:
resize_tor = dim_limit/max_dim
img = cv2.resize(img, None, fx=resize_tor, fy=resize_tor)
# 将原始图像拷贝一份
origin_img = copy()
# 重复执行闭操作,从而移除文档图像中的文本
kernel = np.ones((5,5),np.uint8)
# 这里以执行次数为4举例,通过与原图对比可以发现,文本图像中的文字已多半被移除。但仍视具体情况需调整迭代次数!
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations= 4)
plt.imshow(img)
经过上述多个步骤的文本图像预处理之后,得到了一张版面为空白的文本图像数据。则接下来的工作为:去除文本图像版面区域以外的背景。这里使用GrabCut方式实现图像数据分割。
GrabCut方法说明:
如果手动地绘制前景对象的边界框,则人为主观性太强。因此,这里使用GrabCut方法自动地确定前景和背景,实现如下:
# 通过GrabCut算法实现前景对象的分割
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (20,20,img.shape[1]-20,img.shape[0]-20)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
处理效果如下图所示,可见前景对象以外的区域均被分割出来。
经过上述小节的处理后,现在的文本图像数据以没有干扰背景的空白页面呈现,于是就可以有效地执行边缘检测操作。这里通过使用Canany算法来实现文档区域边缘的检测。其调用形式如下:
e d g e s = c v 2. C a n n y ( i m a g e , t h r e s h o l d 1 , t h r e s h o l d 2 [ , e d g e s [ , a p e r t u r e S i z e [ , L 2 g r a d i e n t ] ] ] ) edges = cv2.Canny( image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]] ) edges=cv2.Canny(image,threshold1,threshold2[,edges[,apertureSize[,L2gradient]]])
参数说明:
处理步骤:
(1)首先对输入图像进行灰度化处理,使其满足canny算法的处理需求;
(2)然后对灰度图像进行高斯模糊处理,去除图像中的噪声;
(3)对处理后的图像做边缘检测;
(4)对处理后的图像做膨胀操作(dilate),获取文档的轮廓线。
# 基于canny-edge的边缘检测,处理后的效果如下图所示:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (11, 11), 0)
# Edge Detection.
canny = cv2.Canny(gray, 100, 200)
canny = cv2.dilate(canny, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5)))
plt.imshow(canny)
说明1:边缘检测与轮廓检测的区别?
轮廓检测的目标是确定闭合物体的形状,特别是因为对于具有相同颜色强度的连续点,寻找轮廓的方法是确定的,而边缘检测是通过检测颜色强度内的变化来进行的。参考相关issue。
通过上述小节的边缘检测,得到文档图像数据的边缘,这里进一步通过轮廓检测得到这些边缘所对应的闭合轮廓。调用过程如下:
c o n t o u r s , h i e r a r c h y = c v 2. f i n d C o n t o u r s ( i m a g e , m o d e , m e t h o d [ , c o n t o u r s [ , h i e r a r c h y [ , o f f s e t ] ] ] ) contours, hierarchy = cv2.findContours( image, mode, method[, contours[, hierarchy[, offset]]] ) contours,hierarchy=cv2.findContours(image,mode,method[,contours[,hierarchy[,offset]]])
# 对边缘检测后的文档图像数据进行轮廓检测
con = np.zeros_like(img)
# 对检测得到的边缘获取其轮廓
contours, hierarchy = cv2.findContours(canny, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# 保留最大的轮廓,将其绘制在画布上,效果如下图:
page = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
con = cv2.drawContours(con, page, -1, (0, 255, 255), 3)
文档图像校正的最终目标是要进行文档区域的对齐(align),为了实现此目标,需要获取文档其余的四个角点坐标。通过轮廓检测后,已经获取到了文档图像的轮廓信息,进一步通过使用 c v 2. a p p r o x P o l y D P cv2.approxPolyDP cv2.approxPolyDP获取角点数据。实现过程如下:
# 对轮廓检测后的数据进行角点检测
con = np.zeros_like(img)
for c in page:
# arcLength计算轮廓周长或曲线长度
epsilon = 0.02 * cv2.arcLength(c, True)
corners = cv2.approxPolyDP(c, epsilon, True)
if len(corners) == 4:
break
cv2.drawContours(con, c, -1, (0, 255, 255), 3)
cv2.drawContours(con, corners, -1, (0, 255, 0), 10)
corners = sorted(np.concatenate(corners).tolist())
for index, c in enumerate(corners):
character = chr(65 + index)
cv2.putText(con, character, tuple(c), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 1, cv2.LINE_AA)
至此,获取到了文档图像数据的四个角点坐标,利用 c v 2. g e t P e r s p e c t i v e T r a n s f o r m cv2.getPerspectiveTransform cv2.getPerspectiveTransform来对其进行透视变换,从而实现图像的对齐,得到校正之后的文档图像数据。
# 透视变换
M = cv2.getPerspectiveTransform(np.float32(corners), np.float32(destination_corners))
# Perspective transform using homography.
final = cv2.warpPerspective(orig_img, M, (destination_corners[2][0], destination_corners[2][1]), flags=cv2.INTER_LINEAR)
最终实现效果如下:
至此,文档图像校正过程基本结束。基于此方法基本可以批量实现扭曲文档图像的校正处理,然后进行后去的文档智能任务将得到极大便捷,比如光学字符识别等。回归本文,相关内容总结如下:
[1] 参考文献: 原文同步转载链接 。
[2] 周丽,冯百明,关煜,等. 面向智能手机拍摄的变形文档图像校正[J]. 计算机工程与科学,2022,44(1):102-109. DOI:10.3969/j.issn.1007-130X.2022.01.012.
[3] GrabCut算法:参考开源DOC链接.
[1] 版权声明:本文为博主原创文章,转载或者引用本文内容请注明来源及原作者。