🌀Jarson Cai's Blog
头脑是日用品,不是装饰品
《数字图像处理》课程设计——表格图片文字识别
研究生课程《数字图像处理》课程设计

《数字图像处理》课程设计——表格图片文字识别

目标任务

  • 1、将图片中的信息提取为表格:提取“姓名”、“准考证号”、“已录取”,保存为新表格
  • 2、在已有表格中查找指定数据并进行标注;将“已录取”数据增加到原有表格中

思路步骤

  • 1.使用灰度方法读取图片,并利用局部阈值分割方法分割为像素只有0和255的图片
  • 2.使用基于形态学腐蚀操作和膨胀操作,分别识别横线和竖线
  • 3.找出横线和竖线的交点,并把交点像素的位置保存起来
  • 4.通过图片相减得到纯文字的图片,根据像素位置来分割图片
  • 5.对分割好的图片使用cnocr文字识别识别中文,使用tesseract识别数字,并将其保存到数组中
  • 6.将书组中的内容分别放入新的excel表格并保存,并根据名字信息对原有表格进行修改

思路具体实现

灰度读取图片&自适应阈值分割

  • 灰度读取图片的目的是为了消除色彩对后续处理的影响
  • 自适应阈值分割的目的是为了将图片的背景变为纯黑色(像素值为0),文字以及其他信息变为纯白色(像素值为255)

为什么使用阈值分割的方法?

  • 图像分割思想:控制背景环境,降低分割难度;聚焦于感兴趣的部分。在这里其实就是将图片中的文字信息都集中起来,将图片的背景设置为黑色
  • 基本策略:根据图像的像素灰度值的相似性来选择分割的阈值

为什么使用自适应阈值分割?

  • 整副图像的对比度较低,存在多个目标(多个单峰灰度分布),需要使用多阈值分割方法。
  • 自适应阈值分割的阈值选取的方法:根据图像不同区域亮度分布,计算其局部阈值,所以对于图像不同区域,能够自适应计算不同的阈值。
  • 自适应的阈值确定方法有两种:计算某个邻域(局部)的均值、高斯加权平均(高斯滤波)来确定阈值,这里采用高斯加权平均。
  • 二值化方法:这里采用THRESH_BINARY方法;超过阈值则设为最大像素值,不超过则设为0

形态学腐蚀

形态学腐蚀原理

  • 使用卷积模版在图像上移动,如果发现有像素等于目标图像,就将其模板中原点的位置变成1
  • 使用基于形态学的腐蚀和膨胀,可以将不同于该形态的区域剔除,保留相似的部分

实现

  • 构建一个类似于横线和竖线的形态学模版,对目标图像进行腐蚀和膨胀操作,单独提取出横线和竖线

寻找横线竖线的交点

横线竖线交点寻找很简单,但因为交点的大小不一,可能一个交点包含很多个像素,但我们只需要一个。所以在选取像素的横纵坐标时需要进行特别的处理

根据交点进行图片分割

  • 首先通过原图减去横线图和竖线图得到纯文字图片,再进行图片分割
  • 这里的图片分割不同于上面,其代表的是字面意思,也就是根据保存好的横纵坐标选取不同的区域的图片
  • 注意根据提取的信息,还需要对子图片再次进行分割,还有就是一些冗余信息的去除。
    • 包含多个信息:
    • 包含冗余信息:

中英文文字识别

  • 为什么不自己写文字识别?因为本课程主要是为了学习opencv而不是机器学习,所以这里只调用第三方库。
  • 中文使用cnocr库识别。
  • 英文或数字使用tesseract程序+pytesseract库进行识别。

向表格添加数据

  • 使用openpyxl库进行excel表格的操作。
  • 注意添加数据的函数,因为需要识别多张图片进行多次添加数据,每次添加时索引需要重新定位没有数据的第一行位置。

源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
import math
import cv2 as cv
import openpyxl

#图像二值化
'''
使用自适应阈值分割:
它的思想不是计算全局图像的阈值,而是根据图像不同区域亮度分布,计算其局部阈值,所以对于图像不同区域,能够自适应计算不同的阈值,因此被称为自适应阈值法。
如何确定局部阈值呢?可以计算某个邻域(局部)的均值、中值、高斯加权平均(高斯滤波)来确定阈值。

cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst=None)
src:灰度化的图片
maxValue:满足条件的像素点需要设置的灰度值
adaptiveMethod:自适应方法。有2种:ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_C(均值和高斯)
thresholdType:二值化方法,可以设置为THRESH_BINARY或者THRESH_BINARY_INV  (大于阈值部分取maxval,否则取0);另一个为反向
blockSize:分割计算的区域大小,取奇数
C:常数,每个区域计算出的阈值的基础上在减去这个常数作为这个区域的最终阈值,可以为负数
dst:输出图像,可选
'''
def binary_img(img):
    #传入函数的图片先进行取反(255-原像素值)
    #使用高斯函数滤波进行阈值选取
    binary = cv.adaptiveThreshold(~img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, -5)#dst选择默认值为None
    return binary

#识别横线和竖线
'''
形态学腐蚀:
使用模版在图像上移动,如果发现有像素等于目标图像,就将其模板中心原点位置置为1
'''

def spot(img):
    #腐蚀算法的色块大小,一般用长和宽的平方根为基数效果最好
    rows, cols = img.shape
    col_k = int(math.sqrt(cols) * 1.2) #宽
    row_k = int(math.sqrt(rows) * 1.2) #高

    #通过基于图形学的腐蚀和膨胀操作识别横线
    horizontal_line = cv.getStructuringElement(cv.MORPH_RECT, (col_k, 1)) #构造一个类似于横线的方形结构元素
    eroded_col = cv.erode(img, horizontal_line, iterations=1) #使用横线模版进行腐蚀
    dilatedcol = cv.dilate(eroded_col, horizontal_line, iterations=1)
    #cv_show("识别的横线", dilatedcol)

    #通过腐蚀识别竖线
    vertical_line = cv.getStructuringElement(cv.MORPH_RECT, (1, row_k)) #构造一个类似于竖线的方块结构元素
    eroded_row = cv.erode(img, vertical_line, iterations=1) #使用竖线模板进行腐蚀
    dilatedrow = cv.dilate(eroded_row, vertical_line, iterations=1)
    #cv_show("识别的竖线", dilatedrow)

    #识别横线竖线的交点
    bitwiseAnd = cv.bitwise_and(dilatedcol, dilatedrow)

    #识别表格:将横线图片和竖线图片相加
    table = dilatedcol + dilatedrow
    #cv_show("表格图片", table)

    return bitwiseAnd, table

#存储横线和竖线的交点
def side_point(img):
    list_x = []
    list_y = []
    x, y = img.shape
    index = 0
    for i in range(x):
        for j in range(y):
            if img[i][j] == 255:
                #通过打印结果得到,一个交点刚好只占一个像素(如果交点过大,需要进行过滤)
                if index != 0:
                    if i == list_x[-1] and j - list_y[-1] < 10:
                        continue
                if index != 0:
                    if i != list_x[-1] and i - list_x[-1] < 10:
                        continue
                list_x.append(i)
                list_y.append(j)
                index += 1
    return list_x, list_y

#根据交点的位置对表格图片进行分割
def split_table(list_x, list_y, img):
    index = 0
    y = 0
    ROI = []
    for i in range(len(list_x) - 1):
        if list_x[i] == 1 or list_x[i] == 0:
            continue
        if list_x[i] == list_x[i + 1]:
            if index == 0:
                ROI.append(img[0: list_x[i], list_y[i]: list_y[i + 1]])
            else:
                if y == 0:
                    temp = list_x[i - 1]
                ROI.append(img[temp: list_x[i], list_y[i]: list_y[i + 1]])
                y += 1
        else:
            y = 0
            index += 1
    return ROI

#文字识别(第一张图,带字段)
def ocr1(img_list):
    from cnocr import CnOcr  # 中文文字识别
    cn_ocr = CnOcr()
    import pytesseract  # OCR英文文字识别库(数字)
    pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

    name_list = []
    number_list = []
    admit_list = []
    for i in range(0, len(img_list) - 1, 4): #步长为4的循环
        index = 0
        if i < 4: #直接将第一行信息跳过
            continue
        x, y = img_list[i + 1].shape
        x1, y1 = img_list[i + 3].shape
        img1 = img_list[i + 1][0: x // 2, 0: y // 2]
        img2 = img_list[i + 1][x // 2: , 0: y // 2]
        img3 = img_list[i + 3][10 : x1 - 10, :]
        temp1 = cn_ocr.ocr(img1) #对应第二栏的信息上半部分(名字为2个字还得另外筛选)
        temp2 = cn_ocr.ocr(img2) #对应第二栏信息下半部分
        number_el = pytesseract.image_to_string(img3,  lang='eng') #对应第四栏的信息
        number = ""
        for x in number_el:
            if x >= '0' and x <= '9':
                number += x
        #print(temp1)
        #print(number)
        #print(temp2)

        #对名字信息进行处理
        for temp in temp1:
            for j in range(len(temp)):
                if j == 0:
                    name = ""
                    for y in temp[j]:
                        if y.isdigit(): #为数字字符串则跳过
                            continue
                        name += y
                    name_list.append(name)

        #放入准考证号信息
        number_list.append(str(number))

        #填入录取信息
        for temp in temp2:
            for j in range(len(temp)):
                if j == 0:
                    admit_information = ""
                    for y in temp[j]:
                        if y == '[':
                            continue
                        admit_information += y
                    admit_list.append(admit_information)
    index += 1
    return name_list, number_list, admit_list

#文字识别(第二张图)
def ocr2(img_list):
    from cnocr import CnOcr  # 中文文字识别
    cn_ocr = CnOcr()
    import pytesseract  # OCR英文文字识别库(数字)
    pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

    name_list = []
    number_list = []
    admit_list = []
    for i in range(0, len(img_list) - 1, 3): #步长为3的循环
        index = 0
        x, y = img_list[i].shape
        x1, y1 = img_list[i + 2].shape
        img1 = img_list[i][0: x // 2, 0: y // 2]
        img2 = img_list[i][x // 2: , 0: y // 2]
        img3 = img_list[i][10 : x1 - 10, :]
        temp1 = cn_ocr.ocr(img1) #对应第二栏的信息上半部分(名字为2个字还得另外筛选)
        temp2 = cn_ocr.ocr(img2) #对应第二栏信息下半部分
        number_el = pytesseract.image_to_string(img3,  lang='eng') #对应第三栏的信息
        number = ""
        for x in number_el:
            if x >= '0' and x <= '9':
                number += x


        #print(temp1)
        #print(number)
        #print(temp2)

        #对名字信息进行处理
        for temp in temp1:
            for j in range(len(temp)):
                if j == 0:
                    name = ""
                    for y in temp[j]:
                        if y.isdigit(): #为数字字符串则跳过
                            continue
                        name += y
                    name_list.append(name)

        #放入准考证号信息
        number_list.append(str(number))

        #填入录取信息
        for temp in temp2:
            for j in range(len(temp)):
                if j == 0:
                    admit_information = ""
                    for y in temp[j]:
                        if y == '[':
                            continue
                        admit_information += y
                    admit_list.append(admit_information)
    index += 1
    return name_list, number_list, admit_list

#添加数据(考虑多次添加的情况)
def edit_exTable(table_name, sheet_name, list1, list2, list3):
    workbook = openpyxl.load_workbook(table_name)
    sheet = workbook[sheet_name]
    #定位到新空白的单元格中(计算偏移量)
    offset1 = 0
    offset2 = 0
    for i in range(1000):
        if sheet.cell(i + 1, 1).value != None:
            offset1 += 1
        else:
            break

    for i in range(len(list1)):
        if sheet.cell(1, 1).value != None:
            if sheet.cell(i + offset1, 1).value == list1[i]: #防止上一次的图片末尾和新的图片头部有重复
                offset2 += 1
                continue
        sheet.cell(i + 1 + offset1 - offset2, 1).value = list1[i]
        sheet.cell(i + 1 + offset1 - offset2, 2).value = list2[i]
        sheet.cell(i + 1 + offset1 - offset2, 3).value = list3[i]
    workbook.save(table_name)
    workbook.close()

#在已有表格中标注并增加已录取(身份证在第9栏,名字在第7栏)
def mark_exTable(table_name, sheet_name, list1, list2, list3):
    workbook = openpyxl.load_workbook(table_name)
    sheet = workbook[sheet_name]
    for i in range(len(list1)):
        for j in range(1000):
            if sheet.cell(j + 1, 9).value == list3[i]: #找到身份证相等的情况
                sheet.cell(j + 1, 7).value = list1[i] + "[" + list2[i] + "]"
                break
    workbook.save(table_name)
    workbook.close()


# 显示图像
def cv_show(name, img):
    cv.imshow(name, img)
    cv.waitKey(0)
    cv.destroyAllWindows()



def main():
    #采集灰度图片
    img1 = cv.imread("1.png", 0)
    img2 = cv.imread("2.png", 0)
    #cv_show("第一张图", img1)
    #cv_show("第二张图", img2)

    #图像二值化
    binary1 = binary_img(img1)
    binary2 = binary_img(img2)
    #cv_show("二值化后的图1",binary1)
    #cv_show("二值化后的图2", binary2)

    #横线竖线交点图片识别和表格识别
    intersection_img1, table1 = spot(binary1)
    intersection_img2, table2 = spot(binary2)
    cv_show("交点图片1", intersection_img1)
    cv_show("交点图片2", intersection_img2)
    print(intersection_img1)

    #进行减法运算,提取纯文字图片
    character_img1 = binary1 - table1
    character_img2 = binary2 - table2
    cv_show("纯文字图片1", character_img1)
    cv_show("纯文字图片2", character_img2)

    #通过交点图片提取出交点像素
    myList_x1, myList_y1 = side_point(intersection_img1)  #x代表行,y代表列
    myList_x2, myList_y2 = side_point(intersection_img2)
    # print(myList_x1)
    # print(myList_y1)
    # print(myList_x2)
    # print(myList_y2)

    #根据交点位置分割表格
    img_list1 = split_table(myList_x1, myList_y1, character_img1)
    img_list2 = split_table(myList_x2, myList_y2, character_img2)

    #分割图片效果检测
    # for img in img_list1:
    #     cv_show("", img)
    # for img in img_list2:
    #     cv_show("", img)

    #对分割好的图片进行文字识别
    name_text1, number_text1, admit_text1 = ocr1(img_list1)
    name_text2, number_text2, admit_text2 = ocr2(img_list2)
    # print(name_text1)
    # print(admit_text1)
    # print(name_text2)
    # print(admit_text2)

    #新建表格并保存名字
    workbook = openpyxl.Workbook()
    workbook.create_sheet('录取情况')
    sheet = workbook['录取情况']
    sheet.cell(1, 1).value = '姓名'
    sheet.cell(1, 2).value = '准考证号'
    sheet.cell(1, 3).value = '录取情况'
    workbook.save(u'接收复试同学已录取情况.xlsx')

    #编辑表格并输入信息
    edit_exTable(u'接收复试同学已录取情况.xlsx', '录取情况', name_text1, number_text1, admit_text1)
    edit_exTable(u'接收复试同学已录取情况.xlsx', '录取情况', name_text2, number_text2, admit_text2)

    #在已有表格中查找并进行标注(通过身份证号查找)
    mark_exTable(u'接收复试通知名单_副本.xlsx', 'YZ_SYTJ_SBMCJ_085046761', name_text1, number_text1, admit_text1)
    mark_exTable(u'接收复试通知名单_副本.xlsx', 'YZ_SYTJ_SBMCJ_085046761', name_text2, number_text2, admit_text2)


if __name__ == '__main__':
    main()

结果

原图&给定表格部分截图

阈值分割结果

横线识别 & 竖线识别 & 表格识别 & 交点图片识别 & 纯文字图片识别

  • 图片1:

  • 图片2:

    部分分割图片效果

    最终识别结果(excel表格)


最后修改于 2022-05-05

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。