CASIA-HWDB 数据集是最常见的手写汉字识别数据集,它包含脱机、联机两部分,分单字、文本行两种类型:
.gnt
.dgrl
一般常用的汉字识别多为脱机单字识别,该部分数据格式为 .gnt
,网络上针对这种数据的解析文章也很多,这里主要介绍文本行识别数据集,它的格式是 .dgrl
,它的解析类似于 .gnt
,但由于结构不同,具体的操作也不一样!
.dgrl
格式如下图所示,它按照这种顺序依次存储,一张图对应一个 DGRL 文件,大部分内容都有固定的长度,部分内容长度不固定但是也能通过其他数据推导出来,我们可以通过访问文件特定位置的数据得到我们需要的内容:行文本标注,行图像。
举例来说:比如我们要读取图像中行数量,那首先要知道这个数据在哪个位置(就好像,python读文件要知道数据在哪一行)(1)我们先读取第一行的 4B
长度数据(4个字节),得到文件头的长度(也就是文件头占据多少行),假设是 n 行;(2)然后读取 n-1 行(第一行已经读过了),就来到了图像信息 Image Records
的位置,根据上图的结构,再读3个 4B
就可以拿到图像中行数量的数据了!
读取 .dgrl
时先以二进制方式打开文件:
f = open(dgrl, 'rb')
然后用 numpy 挨个去读取,它的读取方式跟 f.readline()
类似,一个一个读,所以之前读了多少个数据就很重要!所以要指定读的格式、数量:
np.fromfile(f, dtype='uint8', count=4)
一般 dtype
都选择 uint8
,count
需要根据上图结构中的长度 Length
做相应变化。
要注意的一个地方是:行文本标注读取出来以后,是一个 int
列表,要把它还原成汉字,一个汉字占用两个字节(具体由 code_length
决定),使用 struct
将其还原:
struct.pack('I', i).decode('gbk', 'ignore')[0]
上面的 i
就是提取出来的汉字编码,解码格式为 gbk
,有些行文本会有空格,解码可能会出错,使用 ignore
忽略。
import struct
import os
import cv2 as cv
import numpy as np
def read_from_agrl(dgrl):
if not os.path.exists(dgrl):
print('DGRL not exis!')
return
dir_name,base_name = os.path.split(dgrl)
label_dir = dir_name+'_label'
image_dir = dir_name+'_images'
if not os.path.exists(label_dir):
os.makedirs(label_dir)
if not os.path.exists(image_dir):
os.makedirs(image_dir)
with open(dgrl, 'rb') as f:
# 读取表头尺寸
header_size = np.fromfile(f, dtype='uint8', count=4)
header_size = sum([j<<(i*8) for i,j in enumerate(header_size)])
# print(header_size)
# 读取表头剩下内容,提取 code_length
header = np.fromfile(f, dtype='uint8', count=header_size-4)
code_length = sum([j<<(i*8) for i,j in enumerate(header[-4:-2])])
# print(code_length)
# 读取图像尺寸信息,提取图像中行数量
image_record = np.fromfile(f, dtype='uint8', count=12)
height = sum([j<<(i*8) for i,j in enumerate(image_record[:4])])
width = sum([j<<(i*8) for i,j in enumerate(image_record[4:8])])
line_num = sum([j<<(i*8) for i,j in enumerate(image_record[8:])])
print('图像尺寸:')
print(height, width, line_num)
# 读取每一行的信息
for k in range(line_num):
print(k+1)
# 读取该行的字符数量
char_num = np.fromfile(f, dtype='uint8', count=4)
char_num = sum([j<<(i*8) for i,j in enumerate(char_num)])
print('字符数量:', char_num)
# 读取该行的标注信息
label = np.fromfile(f, dtype='uint8', count=code_length*char_num)
label = [label[i]<<(8*(i%code_length)) for i in range(code_length*char_num)]
label = [sum(label[i*code_length:(i+1)*code_length]) for i in range(char_num)]
label = [struct.pack('I', i).decode('gbk', 'ignore')[0] for i in label]
print('合并前:', label)
label = ''.join(label)
label = ''.join(label.split(b'\x00'.decode())) # 去掉不可见字符 \x00,这一步不加的话后面保存的内容会出现看不见的问题
print('合并后:', label)
# 读取该行的位置和尺寸
pos_size = np.fromfile(f, dtype='uint8', count=16)
y = sum([j<<(i*8) for i,j in enumerate(pos_size[:4])])
x = sum([j<<(i*8) for i,j in enumerate(pos_size[4:8])])
h = sum([j<<(i*8) for i,j in enumerate(pos_size[8:12])])
w = sum([j<<(i*8) for i,j in enumerate(pos_size[12:])])
# print(x, y, w, h)
# 读取该行的图片
bitmap = np.fromfile(f, dtype='uint8', count=h*w)
bitmap = np.array(bitmap).reshape(h, w)
# 保存信息
label_file = os.path.join(label_dir, base_name.replace('.dgrl', '_'+str(k)+'.txt'))
with open(label_file, 'w') as f1:
f1.write(label)
bitmap_file = os.path.join(image_dir, base_name.replace('.dgrl', '_'+str(k)+'.jpg'))
cv.imwrite(bitmap_file, bitmap)