在线手写数据集 CASIA-OLHWDB:联机手写识别的宝藏资源

CASIA简介

CASIA是一个非常有价值的手写单字数据集。它涵盖了众多的汉字以及字母、数字和符号。由中科院自动化研究所在 2007-2010 年间收集;包含1020人书写的脱机(联机)手写中文单字样本和手写文本;联机数据是采用 Anoto 笔在点阵纸上书写后扫描、分割得到。主要由离线(HWDB)和在线(OLHWDB)两部分。

具体可以前往官网了解:https://nlpr.ia.ac.cn/databases/handwriting/Home.html

这里只介绍在线手写数据集部分,离线的网上介绍比较多。在线手写数据集单字主要包括OLHWDB1.0 与 OLHWDB1.2。一共收录汉字 7185 个,涵盖了 GB2312 中全部 6763 个汉字。这个数据集以.pot文件存储,其数据组织形式独特。这些版本的数据集可以用于训练和测试在线手写汉字识别模型。CASIA - OLHWDB 为手写汉字识别、字形分析等相关任务提供了丰富的数据资源。

pot文件的解析

pot文件的解析相关教程并不多,官网也只给出了文件头信息,和一个预览的软件。

m1yoph33.png

使用预览软件打开某个pot文件的结果:
m1yoqsjl.png

我这里按照文件头规则直接写了一版python的,会将在线的点集转换为json文件存储,方便后期使用,文件夹的命名使用的是这个汉字字符对应的md5编码,当然你也可以修改为其他的,解析时间很长,因为一个字符大概有100多个样本。

import hashlib
import json
import os
import struct


def parse_pot_file(filename):
    with open(filename, 'rb') as f:
        while True:
            # 读取样本大小
            sample_size_data = f.read(2)
            if not sample_size_data:
                break  # 文件读取结束
            sample_size = struct.unpack('<H', sample_size_data)[0]

            # 读取字符的 GBK 编码
            tag_code_data = f.read(4)
            tag_code = struct.unpack('<I', tag_code_data)[0]
            gbk_code = tag_code & 0xFFFF  # 提取低16位
            character = gbk_code.to_bytes(2, 'big').decode('gbk', errors='ignore').strip('\x00')
            # print(f"Character: {character}")
            # c.append(character)
            # 读取笔画数量
            stroke_count_data = f.read(2)
            stroke_count = struct.unpack('<H', stroke_count_data)[0]
            # print(f"Number of strokes: {stroke_count}")

            # 读取每个笔画
            strokes = []
            for _ in range(stroke_count):
                stroke = []
                while True:
                    # 读取一个坐标对
                    x, y = struct.unpack('<hh', f.read(4))
                    if (x, y) == (-1, 0):  # 笔画结束标志
                        break
                    stroke.append((x, y))
                strokes.append(stroke)

            # 将字符编码为md5
            md5_hash = hashlib.md5(character.encode('utf-8')).hexdigest()
            # 创建以MD5为名的文件夹
            folder_path = os.path.join(output_dir, md5_hash)
            os.makedirs(folder_path, exist_ok=True)
            # 计算已有文件数量,确定当前文件名
            existing_files = os.listdir(folder_path)
            json_filename = f"{len(existing_files) + 1}.json"
            # 将strokes保存为JSON文件
            json_file_path = os.path.join(folder_path, json_filename)
            with open(json_file_path, 'w', encoding='utf-8') as json_file:
                json.dump(strokes, json_file, ensure_ascii=False, separators=(',', ':'))

            # print(f"Strokes: {strokes}")

            # 检查字符结束标志 (-1, -1)
            end_marker = struct.unpack('<hh', f.read(4))
            if end_marker != (-1, -1):
                raise ValueError("Invalid character end marker.")


output_dir = 'D:\\VeenCode\\MyCode\\DataSet\\OLHWDB\\PotTrainJson'
input_dir = 'D:\\VeenCode\\MyCode\\DataSet\\OLHWDB\\Pot1.0Train'
# 遍历文件夹中的所有 .pot 文件
for root, _, files in os.walk(input_dir):
    for file in files:
        if file.endswith('.pot'):
            file_path = os.path.join(root, file)
            parse_pot_file(file_path)
            print(os.path.join(root, file))

文件夹Pot1.0Train用来存放全部pot文件,会将结果输出到PotTrainJson文件夹。

结果预览

将pot转换成json后,处理起来就方便太多了,随机来一首诗预览一下字符:
m1ypc4zt.png
下面是预览的代码,大家也可以拿去玩玩,使用了Tk库,在canvas上绘制的:

import hashlib
import json
import tkinter as tk
import os
import random
# 读取JSON文件并解析数据
def load_json(file_path):
    with open(file_path, 'r') as file:
        data = json.load(file)
    return data


# 归一化点的坐标,放缩到90x90区域并居中
def normalize_data(strokes, offset_x, offset_y, word_size=90, box_size=100):
    # 获取所有点的最大值和最小值,以便归一化
    all_x = [point[0] for stroke in strokes for point in stroke]
    all_y = [point[1] for stroke in strokes for point in stroke]

    min_x, max_x = min(all_x), max(all_x)
    min_y, max_y = min(all_y), max(all_y)

    # 归一化到90x90的绘制区域
    scale_x = word_size / (max_x - min_x) if max_x - min_x != 0 else 1
    scale_y = word_size / (max_y - min_y) if max_y - min_y != 0 else 1

    # 计算居中的偏移量
    center_x_offset = (box_size - word_size) / 2
    center_y_offset = (box_size - word_size) / 2

    normalized_strokes = []
    for stroke in strokes:
        normalized_stroke = []
        for x, y in stroke:
            norm_x = (x - min_x) * scale_x + offset_x + center_x_offset
            norm_y = (y - min_y) * scale_y + offset_y + center_y_offset
            normalized_stroke.append((norm_x, norm_y))
        normalized_strokes.append(normalized_stroke)

    return normalized_strokes


# 在canvas上绘制笔画
def draw_strokes(canvas, strokes):
    for i, stroke in enumerate(strokes):
        color = "red" if i == 0 else "black"
        for j in range(len(stroke) - 1):
            x1, y1 = stroke[j]
            x2, y2 = stroke[j + 1]
            canvas.create_line(x1, y1, x2, y2, fill=color, width=2)


# 主函数:随机挑选10个JSON文件并渲染到画布上
def main(selected_files):
    # Canvas的大小设置为500x500
    canvas_width = 500
    canvas_height = 500
    box_size = 100  # 每个字的框大小为100x100
    word_size = 50  # 字的大小为90x90
    # 创建Tkinter窗口和Canvas
    root = tk.Tk()
    root.title("Drawing Random Strokes")

    canvas = tk.Canvas(root, width=canvas_width, height=canvas_height)
    canvas.pack()

    # 按照5列2行的顺序排列和绘制笔画
    num_cols = 5

    for index, file_name in enumerate(selected_files):
        # 计算子区域的左上角的坐标
        col = index % num_cols
        row = index // num_cols
        offset_x = col * box_size
        offset_y = row * box_size

        # 加载和归一化数据
        strokes = load_json(os.path.join(folder_path, file_name))
        normalized_strokes = normalize_data(strokes, offset_x, offset_y, word_size, box_size)

        # 在子区域内绘制笔画
        draw_strokes(canvas, normalized_strokes)

    # 运行Tkinter主循环
    root.mainloop()


# 示例调用
if __name__ == "__main__":
    # 给定字符
    chars = "白日依山尽黄河入海流欲穷千里目更上一层楼"
    json_path = []
    # 遍历字符并生成对应的JSON文件路径
    for char in chars:
        # 将字符md5加密
        char_md5 = hashlib.md5(char.encode()).hexdigest()
        folder_path = os.path.join(r"D:\VeenCode\MyCode\DataSet\OLHWDB\PotTrainJson", char_md5)
        # 随机从folder_path文件夹中挑选一个json文件
        json_files = [f for f in os.listdir(folder_path) if f.endswith('.json')]
        json_file = random.choice(json_files)
        json_path.append(os.path.join(folder_path, json_file))
    main(json_path)
打赏
评论区
头像