CASIA-HWDB 數據集是最常見的手寫漢字識別數據集,它包含脫機、聯機兩部分,分單字、文本行兩種類型:
- HWDB1.x:脫機單字,1.0~1.2 三個版本,數據格式爲
.gnt
- OLHWDB1.x:聯機單字,1.0~1.2 三個版本,
- HWDB2.x:脫機文本行,1.0~1.2 三個版本,數據格式爲
.dgrl
- OLHWDB1.x:聯機文本行,1.0~1.2 三個版本,
一般常用的漢字識別多爲脫機單字識別,該部分數據格式爲 .gnt
,網絡上針對這種數據的解析文章也很多,這裏主要介紹文本行識別數據集,它的格式是 .dgrl
,它的解析類似於 .gnt
,但由於結構不同,具體的操作也不一樣!
1、DGRL 文件格式介紹
.dgrl
格式如下圖所示,它按照這種順序依次存儲,一張圖對應一個 DGRL 文件,大部分內容都有固定的長度,部分內容長度不固定但是也能通過其他數據推導出來,我們可以通過訪問文件特定位置的數據得到我們需要的內容:行文本標註,行圖像。
舉例來說:比如我們要讀取圖像中行數量,那首先要知道這個數據在哪個位置(就好像,python讀文件要知道數據在哪一行)(1)我們先讀取第一行的 4B
長度數據(4個字節),得到文件頭的長度(也就是文件頭佔據多少行),假設是 n 行;(2)然後讀取 n-1 行(第一行已經讀過了),就來到了圖像信息 Image Records
的位置,根據上圖的結構,再讀3個 4B
就可以拿到圖像中行數量的數據了!
2、python 代碼實現
讀取 .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)