手写中文文章识别(2)——样本集构建

首先要明确思路。假设有10万篇文章,每篇1000字,共计1亿字。很显然,不可能按照文章内容,逐字把图片都读取、保存下来形成样本集,这个存储量会是一个天文数字,其中有大量的冗余。更好的办法是分别保存文章和手写汉字图片集,在训练时即时生成样本集batch,训练完毕便扔掉,再重新生成下一batch。


按照这一思路以及上文(https://blog.csdn.net/foreseerwang/article/details/80833749)提到的内容,为了构建手写中文文章样本集,需要有两类资源:手写汉字图片库和中文文章库。


一、手写汉字图片库

可以从中科院自动化所下载hwdb(http://www.nlpr.ia.ac.cn/databases/handwriting/Home.html),但是其格式是私有gnt格式。为此,链接https://zhuanlan.zhihu.com/p/24698483给出了读取、处理并另存每个独立手写汉字为png文件的代码。把每个独立手写汉字保存为png文件,存在一个严重问题:每个图片只有3-7kB,数以十万至百万计的小文件,一是占用磁盘空间远超实际大小(实测约为9倍),二是读取、复制等操作都会极为耗时。因此,建议从gnt文件读取出来之后,还是保存为易于读取处理的大文件。


hwdb手写汉字库针对每个汉字有多人书写样本,因此,我们分别按照书写人、汉字两个维度保存转换后的hwdb文件。


首先,按照书写人保存,即每个书写人写的所有汉字保存为一个文件。这种方式的好处是便于构建同一个书写人的手写文章;但在实际操作中发现,每位书写人未能写完所有汉字,因此最终并未使用这种方式。基于上述gnt读取例程修改后得到的代码如下:

# -*- coding: utf-8 -*-

import os
import numpy as np
import struct
import random
from PIL import Image
try:
    import cPickle as pickle
except ImportError:
    import pickle

data_dir = os.path.join(os.path.curdir, 'GNTData')
train_data_dir = os.path.join('h:', 'hwdb_by_writer_Train_gbk')
if not os.path.exists(train_data_dir):
        os.mkdir(train_data_dir)
test_data_dir = os.path.join('h:', 'hwdb_by_writer_Test_gbk')
if not os.path.exists(test_data_dir):
        os.mkdir(test_data_dir)

trainPCT = 0.8
imgsize = 64
tr_name = 'hwdb_tr'
ts_name = 'hwdb_ts'
dict_name = 'char_dict_gbk20180518'
dict_rvs_name = 'char_dict_gbk_rvs20180518'

def one_file(f):
    header_size = 10
    while True:
        header = np.fromfile(f, dtype='uint8', count=header_size)
        if not header.size: break
        sample_size = header[0] + (header[1]<<8) + (header[2]<<16) + (header[3]<<24)
        tagcode = header[5] + (header[4]<<8)
        width = header[6] + (header[7]<<8)
        height = header[8] + (header[9]<<8)
        if header_size + width*height != sample_size:
            break
        image = np.fromfile(f, dtype='uint8', count=width*height).reshape((height, width))
        yield image, tagcode

def read_from_gnt_dir(gnt_dir=data_dir):
    for file_name in os.listdir(gnt_dir):
        if file_name.endswith('.gnt'):
                
            file_path = os.path.join(gnt_dir, file_name)
            with open(file_path, 'rb') as f:
                for image, tagcode in one_file(f):
                    yield image, tagcode
                    
if not os.path.exists(dict_name):
    char_set = set()
    for _, tagcode in read_from_gnt_dir(gnt_dir=data_dir):
        tagcode_unicode = struct.pack('>H', tagcode).decode('gbk')
        if len(tagcode_unicode)>1:
            char_set.add(tagcode_unicode[0])
            #print(tagcode_unicode)
        else:
            char_set.add(tagcode_unicode)
    char_list = list(char_set)
    char_dict = dict(zip(sorted(char_list), range(len(char_list))))
    
    fw = open(dict_name, 'wb')
    pickle.dump(char_dict, fw)
    fw.close()
    
    char_dict_rvs={}
    for key in char_dict.keys():
        char_dict_rvs[char_dict[key]] = key
    fw = open(dict_rvs_name, 'wb')
    pickle.dump(char_dict_rvs, fw)
    fw.close()
else:
    fr = open(dict_name, 'rb')
    char_dict = pickle.load(fr)
    fr.close()
    
    fr = open(dict_rvs_name, 'rb')
    char_dict_rvs = pickle.load(fr)
    fr.close()

print(len(char_dict))
print(len(char_dict_rvs))

在这个过程中,还形成了两个Python字典,用于汉字与序号的映射。


第二个方式是按照汉字维度保存,即不同人写的同一个汉字保存为一个文件,这也是后续训练模型使用的文件。直接从上述基于书写人保存的文件转换而来,代码如下:

# -*- coding: utf-8 -*-

import os
import numpy as np
import struct
import random
from PIL import Image
import pickle

train_data_src = os.path.join('h:', 'hwdb_by_writer_Train_gbk')
train_data_dir = os.path.join('h:', 'hwdb_by_char_Train_gbk')
if not os.path.exists(train_data_dir):
        os.mkdir(train_data_dir)
        
test_data_src = os.path.join('h:', 'hwdb_by_writer_Test_gbk')
test_data_dir = os.path.join('h:', 'hwdb_by_char_Test_gbk')
if not os.path.exists(test_data_dir):
        os.mkdir(test_data_dir)

def read_one_file(f):
    lblsize = 2
    imgsize = 4096
    while True:
        lbl_in_uint8 = np.fromfile(f, dtype='uint8', count=lblsize)
        if not lbl_in_uint8.size: break
        lbl = (lbl_in_uint8[0]<<8)+lbl_in_uint8[1]
        img = np.fromfile(f, dtype='uint8', count=imgsize)
        yield img, lbl

def read_from_dir(read_dir=train_data_src):
    for file_name in os.listdir(read_dir):
        
        print(file_name)
        
        file_path = os.path.join(read_dir, file_name)
        with open(file_path, 'rb') as f:
            for image, label in read_one_file(f):
                yield image, label

for img_byte, lbl in read_from_dir(test_data_src):
    dest_file = os.path.join(test_data_dir, str('%05d.char' % lbl))
    
    fw = open(dest_file, 'ab')
    fw.write(img_byte)
    fw.close()


二、中文文章库

只能大家各显神通自行去网上下载了。以下面几句话(.char文件)作为例子说明训练样本处理过程及结果:

钻石闪烁的光芒照射着世间人们日益懦弱的心灵。
那成功让钻石增值的一刀似乎在昭示着一个发人深省的道理:
人生需要勇气,
需要平常心。

每一行是一个训练样本,目标是转化成如下结果(.code文件):

06493 04187 06674 03559 04061 00528 0511503607 01631 04131 00185 06683 00278 00312 02586 04082 02178 01920 04061 0197603526 00158
06301 02189 00685 05771 06493 04187 0134100470 04061 00169 00616 00353 00222 01197 02622 04295 04131 00169 00198 0081500278 03342 04106 04061 06273 03822 00024
00278 03908 06810 05719 00702 03090 00010
06810 05719 01836 01820 01976 00158

该文件与上述文章文件逐行逐字对应,通过上述Python字典,由汉字转换成对应的序号。后续训练模型时将使用该文件,读取序号后,再到相应的手写汉字图片库里随机读取相应的图片数据,用于训练。


这个过程中还可以顺便输出一个句子长度的文件(.len文件),对应上述每一行句子的长度,如下:

22
27
7
6

这个转换过程比较简单,我就不贴代码了。主要原因是我实际不是这种过程,而是从原始文章直接生成这三个文件的。

 

上述按照汉字维度保存的手写图片库文件和序号格式的文章句子文件(包括.char/.code/.len三个),将是后续训练模型的主要输入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值