使用图像文字识别技术获取失信黑名单

2019-04-01 13:56:54 浏览数 (1)

最近接了一个新需求,需要获取一些信用黑名单数据,但是找了很多数据源,都是同样的几张图片,目测是excel表格的截图,就像下面这样:

既然没有找到文本类型的数据源,只能对图片上的文字进行识别了。

尝试一,利用第三方API识别:

说到图像识别我首先想到了网上的各类图像识别服务。试用了一下百度、腾讯的识别服务,效果并不好,部分文字识别错误甚至无法识别,不付费只能使用有限的几次。总之,使用第三方的识别服务是行不通的。

尝试二,利用Tesseract-OCR识别:

接下来只能自己想办法识别了,首先试一下google的工具Tesseract-OCR。Python里的pytesseract模块对这个工具进行了封装,使用起来很方便。

test.png

In [1]: import Image

In [2]: import pytesseract

In [3]: image = Image.open('/home/****/shixin/test.png')

In [4]: image = image.convert('L')

In [5]: text = ''.join(pytesseract.image_to_string(image,, config='-psm 6'))

In [6]: print text

〔Zol l 〕西中执字第

口口o22号

识别结果不太准确,原因是识别场景比较复杂,识别的内容包含了标点符号、汉字、数字和字母。对于只含有数字或者字母的识别场景,pytesseract 的识别已经足够了,但是对于当前较复杂的识别需求,识别的准确率不高。

对于这种情况,可以采用训练字库的方式提高准确率,感兴趣的同学可以参考这篇文章(http://www.cnblogs.com/wzben/p/5930538.html)。但是对于目前的需求,我们并不能得到足够的训练样本,所以此路不通。

尝试三,利用机器学习识别:

机器学习我没有接触过,也没有做过相关的需求,于是我开始学习它。然后我发现这是一门很广博的学科,短期的学习难有成效,只好暂时放弃,尝试用其他办法解决问题。

尝试四,利用图像对比识别:

虽然新技能Get失败了,但是对于搞定需求,我从来都是不抛弃不放弃的。我想到了利用图像相似度识别文字的方法,在这里感谢大学教导我数字图像处理的导师。经过尝试,这是一个可行的方案,接下来就介绍一下识别的过程。识别过程主要分为以下几个步骤:

1. 图像预处理

从网上下载的图片需要进行预处理,包括:

(1)灰度化

将彩色图像转化成为灰度图像的过程称为图像的灰度化。彩色图像中每个像素点的颜色由R、G、B三个分量决定,分别代表红绿蓝三种原色。灰度图像就是R、G、B三个分量相同的特殊彩色图像,每个像素点只有一个代表亮度的灰度值,将图像转变成灰度图像可以方便后续的计算。

(2)二值化

图像的二值化,就是将图像的灰度值(https://baike.baidu.com/item/灰度值)设置为0或255,使得图像上只存在黑白两种颜色,而没有中间的灰色。二值化后灰度图像的噪点会被去除,可以使后续的图像对比更简单。二值化需要指定一个阀值,经过测试,这次要识别的图像的最优二值化阀值为69,即灰度图像中灰度值低于69的像素的灰度值会变为0,反之变为255。代码如下:

代码语言:javascript复制
def binarizing(image, threshold):    pixdata = image.load()    w, h = image.size    for y in range(h):        for x in range(w):            if pixdata[x, y] < threshold:                pixdata[x, y] = 0            else:                pixdata[x, y] = 255    return image

原图.png

灰度化.png

二值化.png

2. 图像切割为单元格

从上面的图可以看出,图像是一个表格的截图,我们需要把它分割成单元格,这是为了方便数据的分类和图像的二次切割。

图片像素矩阵轮廓如下所示:

使用横向和纵向扫描线分别扫描图像的像素矩阵,根据像素灰度值的变化确定表格分割线的坐标,再根据坐标把图像切割成单元格。

有的单元格有多行内容,需要把多行合并为一行,如下所示:

使用横向扫描线扫描这一单元格图像,找到行之间空白部分的坐标,根据坐标确定分割线的位置,然后根据分割线分割图像,最后合并为一行。要注意的是类似于下图的情况:

这种情况下第二行的“号”字是上下结构而且单独占一行,单元格会被分成三行,解决办法是根据汉字的高度跳过间隔高度不足的分割线。

3. 单元格分类

图像的第一行是表头,图像分割为单元格后先使用tesseract识别表头,这样就可以根据表头判断列的类型,如案号、组织机构代码等,从而指定不同的策略将单元格分割为字符。比如案号的内容含有数字、字母、汉字和标点符号,而组织机构代码只含有数字,这就需要使用不同的分割方式。

4. 单元格分割为字符

单元格中字符的分割可以说是耗时最久最难的部份了,有很多需要注意的点。

对于如下所示的只含有数字和字母的单元格,分割起来比较简单,直接使用纵向扫描线扫描,得出字符间的空白部分的坐标,然后根据坐标计算分割线进行分割即可。

而对于含有数字、字母、汉字和标点符号的单元格,需要对分割线进行二次加工,这是因为存在左右结构、左中右结构、左中中右结构的汉字。如下所示:

其中“刑”字是左右结构,“川”字是左中右结构,“顺”字是左中中右结构。

在本次识别过程中,对含有汉字的单元格分割出的每个字符做如下处理:

(1)判断是否是左右结构的汉字

若当前字符与它后面一个字符的高度均大于9px,或者这两个字符中有一个的宽度小于4px,说明这两个字符可能是一个左右结构的汉字。那么忽略两个字符间的分割线,将这两个字符作为一个完整字符进行识别,识别成功则说明这两个字符是一个汉字,去除两个字符中间的分割线,为二次分割做准备。

(2)判断是否是左中右结构的汉字

若上一步的两个字符没有判断为汉字,将当前字符与它后面的两个字符作为一个新字符切割下来,如果这三个字符合并后的新字符宽度等于12px且高度大于10px,说明新字符是一个左中右结构的汉字,去除三个字符中间的分割线,为二次分割做准备。

(3)判断是否是左中中右结构的汉字

若上一步的三个字符没有判断为汉字,将当前字符与它后面的三个字符作为一个新字符切割下来,如果这四个字符合并后的新字符宽度等于12px且高度大于10px,说明新字符是一个左中中右结构的汉字,去除四个字符中间的分割线,为二次分割做准备。代码逻辑片段如下:

代码语言:javascript复制
tmp_img_l = clear_image(image.crop((white_line[index - 1]   1, 0, white_line[index], h))) tmp_img_r = clear_image(image.crop((white_line[index]   1, 0, white_line[step_1], h))) tmp_w_l, tmp_h_l = tmp_img_l.size tmp_w_r, tmp_h_r = tmp_img_r.size word_image = clear_image(image.crop((white_line[index - 1]   1, 0, white_line[step_1], h))) if ((tmp_h_r > 9 and tmp_h_l > 9) or (tmp_w_l < 4 or tmp_w_r < 4)) and parse_character(word_image, word_data):    for tmp_index in range(index, step_1):        tmp_white_line[tmp_index] = None else:    for tmp_index in range(step_1   1, len(white_line)):        if white_line[tmp_index] - white_line[tmp_index - 1] > 1:            step_2 = tmp_index            if None not in tmp_white_line[index - 1: step_2] and white_line[step_2] - white_line[index - 1] == 12:                tmp_img = clear_image(image.crop((white_line[index - 1]   1, 0, white_line[step_2], h)))                tmp_w, tmp_h = tmp_img.size                if tmp_h > 10:                    for tmp_index in range(index, step_2):                        tmp_white_line[tmp_index] = None            else:                for tmp_index in range(step_2   1, len(white_line)):                    if white_line[tmp_index] - white_line[tmp_index - 1] > 1:                        step_3 = tmp_index                        if None not in tmp_white_line[index - 1: step_3] and white_line[step_3] - white_line[index - 1] == 12:                            tmp_img = clear_image(image.crop((white_line[index - 1]   1, 0, white_line[step_3], h)))                            tmp_w, tmp_h = tmp_img.size                            if tmp_h > 10:                                for tmp_index in range(index, step_3):                                    tmp_white_line[tmp_index] = None                        break            break

5. 生成对比字符时使用的参照数据集

仔细的观察图片里的文字,再利用网站识别字体,很幸运的找到了图片原作者使用的字体。接下来我们就可以生成对比字符时使用的参照数据集了。

首先下载字体文件,然后利用字体文件把文字渲染到空白图片上,最后把图片转换为矩阵存储到文件中。渲染的字体的大小要和识别的图片上的字体一致,这里是12px。下面给出文字转换为图像矩阵的函数:

代码语言:javascript复制
def paste_word(word):  # 生成单个文字矩阵    pygame.init()    font = pygame.font.Font('***/***.TTF', 12)    rtext = font.render(word, False, (0, 0, 0), (255, 255, 255))    sio = StringIO.StringIO()    pygame.image.save(rtext, sio)    sio.seek(0)    image = Image.open(sio)    image = clear_image(image.convert('L'))  # 去除图片四周的空白    if image:        return numpy.asarray(image)    return None

本次识别搜录的字符有7869个,生成的数据集保存到一个文件中。

6. 对比识别字符

最后的一步是对比识别字符,代码如下:

代码语言:javascript复制
def parse_character(word_image, word_data):    word_matrix = numpy.asarray(word_image)    for word, matrix in word_data.items():        try:            if word in '4679BCDEFHIJKLMNOQRSTUVWXYZ':                threshold = 256            elif word in u'()AG':                threshold = 516            else:                threshold = 1            if numpy.subtract(matrix, word_matrix).sum() <= threshold:                if word == 'B' and numpy.subtract(word_data['P'], word_matrix).sum() <= 1:                    word = 'P'                if word == 'E' and numpy.subtract(word_data['F'], word_matrix).sum() <= 1:                    word = 'F'                return word        except Exception:            pass    return ''

其中word_image是分割得到的字符图片,word_data是预先生成的参照字符矩阵,把word_image转化为矩阵,然后对两个矩阵的差求和,如果求得的和小于等于阀值threshold,说明字符匹配完成,字符识别成功。

总结:

这种识别方法的准确率并不是100%,识别失败的情况有两种。一种情况是有些含有多行文本的单元格高度不足,单元格中最上和最下两行的文字只显示了一半,如下图所示:

这种情况人眼也无法识别,只能放弃;另一种情况是识别的汉字中存在异体字,如“昇”、“堃”等,字体文件无法生成这类文字的图像矩阵,导致识别失败。识别失败的情况极少,所以手动矫正就可以了。

这种识别方法的缺点是适用范围小,识别的效率也不高。不过识别效率仍有提升空间,比如可以利用多线程和多进程并发识别,提高资源利用率,也可以使用二分法、插值法等算法优化识别字符时的匹配过程。

识别过程中最大的难点是汉字的分割,这需要对汉字的特点进行深入了解。

这种识别方法只能算是“权宜之计”,要更快更好的识别图像,还是要用机器学习,有兴趣的同学可以一起学习。更多文字识别内容详见商业新知-文字识别

0 人点赞