基于Python skimage的表格识别程序

1. 开发环境

一提到数字图像处理编程,可能大多数人就会想到matlab,但matlab也有自身的缺点:

  1. 不开源,价格贵
  2. 软件容量大。一般3G以上,高版本甚至达5G以上。
  3. 只能做研究,不易转化成软件。

因此,我们这里使用python这个脚本语言来进行数字图像处理。
要使用python,必须先安装python,一般是2.7版本以上,不管是在windows系统,还是linux系统,安装都是非常简单的。要使用python进行各种开发和科学计算,还需要安装对应的包。这和matlab非常相似,只是matlab里面叫工具箱(toolbox),而python里面叫库或包。基于python脚本语言开发的数字图片处理包,其实很多,比如PIL,Pillow, opencv, scikit-image等。

对比这些包,PIL和Pillow只提供最基础的数字图像处理,功能有限;opencv实际上是一个c++库,只是提供了python接口,更新速度非常慢。到现在python都发展到了3.5版本,而opencv只支持到python 2.7版本;scikit-image是基于scipy的一款图像处理包,它将图片作为numpy数组进行处理,正好与matlab一样,因此,我们最终选择scikit-image进行数字图像处理。

1.1. 需要的安装包

因为scikit-image是基于scipy进行运算的,因此安装numpy和scipy是肯定的。要进行图片的显示,还需要安装matplotlib包,综合起来,需要的包有:

Python >= 2.6
Numpy >= 1.6.1
Cython >= 0.21
Six >=1.4
SciPy >=0.9
Matplotlib >= 1.1.0
NetworkX >= 1.8
Pillow >= 1.7.8
dask[array] >= 0.5.0

1.2. 下载并安装 anaconda

由于依赖的包比较多,安装起来非常费事,我们选择一款集成安装环境就行了,在此推荐Anaconda, 它把以上需要的包都集成在了一起,因此我们实际上从头到尾只需要安装Anaconda软件就行了,其它什么都不用装。

1.3. 简单测试

anaconda自带了一款编辑器spyder,我们以后就可以用这款编辑器来编写代码。我们简单编写一个程序来测试一下安装是否成功,该程序用来打开一张图片并显示。首先准备一张图片,然后打开spyder,编写如下代码:

from skimage import io
img=io.imread('png/01.png')
io.imshow(img)

如果右下角“ Ipython console" 能显示出图片,说明我们的运行环境安装成功。


1.4. skimage包的子模块

skimage包的全称是scikit-image SciKit (toolkit for SciPy) ,它对scipy.ndimage进行了扩展,提供了更多的图片处理功能。它是由python语言编写的,由scipy 社区开发和维护。skimage包由许多的子模块组成,各个子模块提供不同的功能。主要子模块列表如下:

子模块名称 主要实现功能
io 读取、保存和显示图片或视频
data 提供一些测试图片和样本数据
color 颜色空间变换
filters 图像增强、边缘检测、排序滤波器、自动阈值等
draw 操作于numpy数组上的基本图形绘制,包括线条、矩形、圆和文本等
transform 几何变换或其它变换,如旋转、拉伸和拉东变换等
morphology 形态学操作,如开闭运算、骨架提取等
exposure 图片强度调整,如亮度调整、直方图均衡等
feature 特征检测与提取等
measure 图像属性的测量,如相似性或等高线等
segmentation 图像分割
restoration 图像恢复
util 通用函数

用到一些图片处理的操作函数时,需要导入对应的子模块,如果需要导入多个子模块,则用逗号隔开,如:

from skimage import io,data,color

2. 图片中的表格识别

  1. 分割单元格。将图片中的表格全部定位出来,然后按单元格裁剪成一个个小图片,以便后续分析及操作;
  2. 聚焦。其实就是将单元格中的文本区域裁剪出来,将多余的空白去掉;
  3. 大图片的识别。对于大图片用图像相似性的算法(phash+汉明距离)做识别;
  4. 小图片的识别。对于小图片,做字符分割,然后用NN做分类识别;
  5. 识别结果输出到txt;
  6. txt输出到excel。将全部txt按照目标表格的格式,解析输出到excel。

2.1. 分割单元

既然只关心表格区域,所以第一步先将各个单元格拆分出来,截取成一个个小图片。尝试用图像的膨胀、腐蚀来定位表格区域,图像处理包skimage,最后算是定位出了表格区域,也分割出了各个单元格图片,其中部分中间过程的图片如下:



分割单元格这一块的基本流程是:

  1. 读取图像;
  2. 二值化处理;
  3. 横向、纵向的膨胀、腐蚀操作,得到横线图img_row和竖线图img_col;
  4. 得到点图,img_row + img_col=img_dot;
  5. 得到线图,img_row × img_col=img_line(线图只是拿来看看的,后续没有用到);
  6. 浓缩点团到单个像素;
  7. 开始遍历各行的点,将各个单元格从二值图像上裁剪出来,保存到temp文件夹。

2.1.1. 读取图像、二值化

#读取图像
from skimage import io
img=io.imread('png/01.png')
io.imshow(img)

#二值化
bi_th=0.81
img[img<=bi_th]=0
img[img>bi_th]=1
io.imshow(img)

2.1.2. 膨胀、腐蚀操作

# 膨胀腐蚀操作
def dil2ero(img,selem):
    img=morphology.dilation(img,selem)
    imgres=morphology.erosion(img,selem)
    return imgres
# 求图像中的横线和竖线
rows,cols=img.shape
scale=80
col_selem=morphology.rectangle(cols//scale,1)
img_cols=dil2ero(img,col_selem)
row_selem=morphology.rectangle(1,rows//scale)
img_rows=dil2ero(img,row_selem)

2.1.3. 得到点图、线图

# 线图
img_line=img_cols*img_rows    
# 点图
img_dot=img_cols+img_rows
img_dot[img_dot>0]=1
io.imsave('png/tmp/table_dot.jpg',img_dot)

2.1.4. 收缩点团为单位像素点(3×3)

   # 收缩点团为单像素点(3×3)
    def isolate(imgdot):
        idx=np.argwhere(imgdot<1) # img值小于1的索引数组
        rows,cols=imgdot.shape    
        for i in range(idx.shape[0]):
            c_row=idx[i,0]
            c_col=idx[i,1]
            if c_col+1<cols and c_row+1<rows:
                imgdot[c_row,c_col+1]=1
                imgdot[c_row+1,c_col]=1
                imgdot[c_row+1,c_col+1]=1
            if c_col+2<cols and c_row+2<rows:
                imgdot[c_row+1,c_col+2]=1
                imgdot[c_row+2,c_col]=1
                imgdot[c_row,c_col+2]=1
                imgdot[c_row+2,c_col+1]=1
                imgdot[c_row+2,c_col+2]=1
        return imgdot
    img_dot=isolate(img_dot)
    
    io.imsave('png/tmp/table_dot_del.jpg',img_dot)

2.1.5. 获得交点

 # print(dot_idxs.size)
    for m in range(img_dot.shape[0]):
        if m > 100: break
        print('col{}'.format(m),end=" ")
        for n in range(img_dot.shape[1]):
            if img_dot[m][n]==0 :
                print(img_dot[m][n], end= " ")
        print("",end="\n")
        
    dot_idxs=np.argwhere(img_dot<1) # img_dot值等于0的索引数组
    for i in range(len(dot_idxs)):
        for j in range(i,len(dot_idxs)):
            if(dot_idxs[i][0]==dot_idxs[j][0]):
                if(dot_idxs[i][1]>dot_idxs[j][1]):
                    tmp = dot_idxs[i][1];
                    dot_idxs[i][1] = dot_idxs[j][1]
                    dot_idxs[j][1] = tmp

在这里我们可以手动检查dot_idxs数据,观察其中的问题,最容易的可以手动修改dot_idxs数据,这样就可以得到正确的单元。

2.1.6. 检验单元格

def checkTableCols(dot_idxs):
    table_cols = [] #记录每行有几个单元格
    table_rows = 1
    table_row_index = dot_idxs[0][0] #第一行点图y值坐标
    colu_dot = 0 # 单元格数量
    
    for n,indx in enumerate(dot_idxs):
        if indx[0] == table_row_index : # 如果点图y值坐标相同则为同一行
            colu_dot = colu_dot + 1
            print(indx,end=' ')
        else :
            table_cols.append(colu_dot-1) # 将行单元格数量加入table_cols列表
            table_row_index = dot_idxs[n+1][0] #记录下一行位置索引
            colu_dot = 1 # 清0后,换行记录单元格数量
            print(' ',end="\n")
            print(indx, end=" ")
    
    print("")
    for n,v in enumerate(table_cols):
        print('row{}:{}'.format(n,v))
return table_cols

2.1.7. 剪裁图片

def cutImage(img,table_cols,dot_idxs):
    #开始识别单元格
    cell_num = 0
    num = 0
    #if min_row == max_row : # 标准表格 n*n
    for n,v in enumerate(table_cols):
        print('row{}:'.format(n),end= '\n')
        row_dot_num = v + 1
        #cell_num += n * row_dot_num
        for i in range(v):        
            cell_a_index = cell_num + i
            cell_b_index = cell_a_index + 1
            cell_c_index = cell_a_index + row_dot_num
            cell_d_index = cell_c_index +1
            print('cell[{}]:a{} b{} c{} d{}'.format(num+i,dot_idxs[cell_a_index],
            dot_idxs[cell_b_index],dot_idxs[cell_c_index],dot_idxs[cell_d_index]),end= ' ')
            x1=dot_idxs[cell_a_index][1]+3 # 加1去除列边框线
            x2=dot_idxs[cell_b_index][1]
            y1=dot_idxs[cell_a_index][0]+3 # 加1去除行边框线
            y2=dot_idxs[cell_c_index][0]
            print(x1,x2,y1,y2)
            roi=img[y1:y2,x1:x2] # 50~100 行,50~100 列(不包括第 100 行和第 100 列)
            io.imsave('png/tmp/pic_{}.jpg'.format(num+i),roi)
            print('', end="\n")
        cell_num += row_dot_num
        num += v
return num

2.2. 训练语言库——数字为例

  1. 训练环境:首先安装jdk-10.0.1_windows-x64_bin.exe,它是java的运行环境。然后下载工具jTessBoxEditor,它是训练样本的工具。
  2. 样本图像:截图如下图像(越多越好,不过总共也就10个数字)。


  3. 合并图像:运行jTessBoxEditor,菜单栏中Tools--Merge TIFF。在弹出的对话框中选择样本图像(按Shift选择多张),合并名为num.font.exp0.tif文件。
  4. 生成Box file文件:


  5. 文字校正:jTessBoxEditor工具打开num.font.exp0.tif,增加未识别的、修改识别错误的数字。


  6. 生成语言库:


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352