OpenCV 表格文字识别

最近项目需要识别图片中的表格文字,遂学习了一波OpenCV。现在将整个流程记录下来以供交流和分享。

本次的目标是识别下图中的表格,最终需要知道每一部分填写的内容内容是什么,比如知道用户电话13988881234。我将此次任务划分为3个步骤:

  1. 识别出每一个单元格,得到其矩形区域的位置
  2. 知道每一个矩形区域的意义。比如知道e矩形区域代表用户的电话号码
  3. 对该区域的图片进行文字识别

图

本文将要讲解的就是如何完成第一步:得到所有矩形区域的位置。其余步骤可能会在之后的文章进行讲解。我采用的语言是Python,即使你不懂Python也应该能看懂流程,因为Python非常易读。我采用的OpenCV为当前的最新版3.3.1。

如果你是Linux用户且需要安装OpenCV及其相关的Python库,请看我的另一篇文章:Linux OpenCV安装指南

原理

我们知道OpenCV提供了轮廓识别的API:findContours,根据该API的描述,在轮廓检测之前,首先要对图片进行二值化或者Canny边缘检测。寻找的物体是白色的,而背景必须是黑色的。

经过测试后使用二值化的方式对图片进行处理比较好,要注意的是调用threshold时的阈值方法要使用THRESH_BINARY_INV(因为我们需要变成白色,背景变成黑色)。

其次经过二值化处理之后我发现有些边框会出现一些小的空隙,可能是打印问题或者其他因素造成的。对于这种细小的空隙我采用对二值化后的图片进行一次膨胀操作进行去除。

最后一步就是检测轮廓,使用findContours方法,并且轮廓检索模式(Contour retrieval mode)使用RETR_TREE,使用模式我们不仅可以得到所有的轮廓,同时还可以得到轮廓之间的关系(父子关系,兄弟关系)检测出所有轮廓之后,我们需要找出其中最大的轮廓,它代表表格的外边框,然后其所有的子轮廓就是我们要找的内容。

这一步遇到的问题是OpenCV会识别出多余的轮廓,或者识别出的轮廓形状不像长方形。为了防止识别出的多余的轮廓,我们采用面积过滤,估算一下表格中最小的单元格区域的面积,然后所有小于该面积的都过滤掉。对于轮廓不像长方形,我们采用boundingRect得到包含该区域的最小矩形。

代码

# -*- coding: utf-8 -*-
import numpy as np
import cv2
img = cv2.imread('1.jpg')
# 灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 阈值
_, threshold = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV)
# 膨胀
kernel = np.ones((3, 3), np.uint8)
dilated = cv2.dilate(threshold, kernel, iterations=1)
# 轮廓检测
_, contours, hierarchy = cv2.findContours(dilated, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 找到表格最外层轮廓
tree = hierarchy[0]
max_size = 0
outer_contour_index = None
for i, contour in enumerate(contours):
size = cv2.contourArea(contour)
if size > max_size:
max_size = size
outer_contour_index = i
contour = contours[outer_contour_index]
# 估算最小的单元格大小
x, y, w, h = cv2.boundingRect(contour)
cell_size = w * h * 0.0015
# 遍历所有最外层轮廓的子轮廓,过滤掉面积小于cell_size的,然后全部用矩形标注起来
contour_index = tree[outer_contour_index][2]
count = 0
while 1:
contour = contours[contour_index]
if cv2.contourArea(contour) > cell_size:
x, y, w, h = cv2.boundingRect(contour)
count += 1
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 3)
contour_index = tree[contour_index][0]
if contour_index < 0:
cv2.imwrite('11.jpg', img)
break

效果图

图

参考链接

分享到