2024 MathorCupB 题 甲骨文智能识别中原始拓片单字自动分割与识别研究

一、问题重述

甲骨文是我国目前已知的最早成熟的文字系统,它是一种刻在龟甲或兽骨上的古老文字。甲骨文具有极其重要的研究价值,不仅对中国文明的起源具有重要意义,也对世界文明的研究有着深远影响。在我国政府的大力推动下,甲骨文研究已经进入一个全新的发展阶段。人工智能和大数据技术被应用于甲骨文全息性研究及数字化工程建设,成为甲骨文信息处理领域的研究热点。

甲骨文拓片图像分割是甲骨文数字化工程的基础问题,其目的是利用数字图像处理和计算机视觉技术,在甲骨文原始拓片图像的复杂背景中提取出特征分明且互不交叠的独立文字区域。它是甲骨文字修复、字形复原与建模、文字识别、拓片缀合等处理的技术基础[2]。然而,甲骨拓片图像分割往往受到点状噪声、人工纹理和固有纹理三类干扰元素的严重影响[3]。且甲骨文图像来源广泛,包括拓片、拍照、扫描、临摹等,不同的图像来源,其干扰元素的影响是不同的。由于缺乏对甲骨文字及其干扰元素的形态先验特征的特殊考量,通用的代表性图像分割方法目前尚不能对甲骨文原始拓片图像中的文字目标和点状噪声、人工纹理、固有纹理进行有效判别,其误分割率较高,在处理甲骨拓片图像时均有一定局限性。如何从干扰众多的复杂背景中准确地分割出独立文字区域,仍然是一个亟待解决的具有挑战性的问题。

图1为一张甲骨文原始拓片的图像分割示例,左图为一整张甲骨文原始拓片,右图即为利用图像分割算法[4]实现的拓片图像上甲骨文的单字分割。甲骨文的同一个字会有很多异体字,这无疑增加了 甲骨文识别的难度,图2展示了甲骨文中“人”字的不同异体字。

问题一

问题1:对于附件1(Pre test 文件夹)给定的三张甲骨文原始拓片图片进行图像预处理,提取图像特征,建立甲骨文图像预处理模型,实现对甲骨文图像千扰元素的初步判别和处理。

解决思路

针对问题一,我们对附件一的甲骨文图像数据进行数据预处理

包括但不限于:

尺寸调整:将图像调整为模型要求的输入尺寸,通常是正方形或者某个固定的长宽比。

归一化:将图像的像素值缩放到固定范围内,例如0,1或−1,1,以便于模型的训练。

数据增强:通过随机旋转、裁剪、翻转、变换亮度和对比度等方式来增加训练数据的多样性,从而提高模型的泛化能力。

图像增强:对图像进行增强操作,如调整亮度、对比度、锐度、颜色等,以增强图像的特征。

解题代码(python)

import cv2
import numpy as np
import torch
import matplotlib.image as mpimg
import matplotlib.pyplot as plt

# 读取图像
image_path1 = r'cup_data\1_Pre_test\h02060.jpg'
image_path2 = r'cup_data\1_Pre_test\w01637.jpg'
image_path3 = r'cup_data\1_Pre_test\w01870.jpg'
image1 = cv2.imread(image_path1)
image2 = cv2.imread(image_path2)
image3 = cv2.imread(image_path3)

# 定义目标尺寸
target_size = (416, 416)  # YOLOv5 推荐的尺寸

# 调整大小
resized_image1 = cv2.resize(image1, target_size)
resized_image2 = cv2.resize(image2, target_size)
resized_image3 = cv2.resize(image3, target_size)

# 将图像归一化为 [0, 1]
normalized_image = []
normalized_image.append(resized_image1.astype(np.float32) / 255.0)
normalized_image.append(resized_image2.astype(np.float32) / 255.0)
normalized_image.append(resized_image3.astype(np.float32) / 255.0)
# 如果需要旋转,可以在这里进行旋转操作

# 将图像转换为 PyTorch 的 Tensor 格式,并添加批次维度
tensor_image = torch.tensor(normalized_image[0]).permute(2, 0, 1).unsqueeze(0)

# 现在 tensor_image 就是你所需的输入数据,准备用于 YOLOv5 模型
plt.imshow(image1)
plt.axis('off')
plt.show()

问题二

 解题思路

针对问题二,基于yolov5模型,利用附件2中的数据对模型进行微调,使其具备单字检测分割的能力。我们将部分分割结果可视化,验证模型分割能力。

解题代码

# yolov5 训练代码
def main(opt, callbacks=Callbacks()):
    """Runs training or hyperparameter evolution with specified options and optional callbacks."""
    if RANK in {-1, 0}:
        print_args(vars(opt))
        check_git_status()
        check_requirements(ROOT / "requirements.txt")

    # Resume (from specified or most recent last.pt)
    if opt.resume and not check_comet_resume(opt) and not opt.evolve:
        last = Path(check_file(opt.resume) if isinstance(opt.resume, str) else get_latest_run())
        opt_yaml = last.parent.parent / "opt.yaml"  # train options yaml
        opt_data = opt.data  # original dataset
        if opt_yaml.is_file():
            with open(opt_yaml, errors="ignore") as f:
                d = yaml.safe_load(f)
        else:
            d = torch.load(last, map_location="cpu")["opt"]
        opt = argparse.Namespace(**d)  # replace
        opt.cfg, opt.weights, opt.resume = "", str(last), True  # reinstate
        if is_url(opt_data):
            opt.data = check_file(opt_data)  # avoid HUB resume auth timeout
    else:
        opt.data, opt.cfg, opt.hyp, opt.weights, opt.project = (
            check_file(opt.data),
            check_yaml(opt.cfg),
            check_yaml(opt.hyp),
            str(opt.weights),
            str(opt.project),
        )  # checks
        assert len(opt.cfg) or len(opt.weights), "either --cfg or --weights must be specified"
        if opt.evolve:
            if opt.project == str(ROOT / "runs/train"):  # if default project name, rename to runs/evolve
                opt.project = str(ROOT / "runs/evolve")
            opt.exist_ok, opt.resume = opt.resume, False  # pass resume to exist_ok and disable resume
        if opt.name == "cfg":
            opt.name = Path(opt.cfg).stem  # use model.yaml as name
        opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok))
# 训练结果可视化,我们微调的模型在训练集上识别准确率如下图所示:(我们提供了三张训练集上的识别准确率图像)
import matplotlib.pyplot as plt
import cv2

# 读取图像
image_path1 = "jupyter_need/test_datasets0.jpg"
image_path2 = "jupyter_need/test_datasets1.jpg"
image_path3 = "jupyter_need/test_datasets2.jpg"

image1 = cv2.imread(image_path1)
image2 = cv2.imread(image_path2)
image3 = cv2.imread(image_path3)

# 创建一个具有三个子图的图形
plt.figure(figsize=(15, 5))

# 在第一个子图中显示第一张图片
plt.subplot(1, 3, 1)
plt.imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB))
plt.axis('off')

# 在第二个子图中显示第二张图片
plt.subplot(1, 3, 2)
plt.imshow(cv2.cvtColor(image2, cv2.COLOR_BGR2RGB))
plt.axis('off')

# 在第三个子图中显示第三张图片
plt.subplot(1, 3, 3)
plt.imshow(cv2.cvtColor(image3, cv2.COLOR_BGR2RGB))
plt.axis('off')

# 展示图片
plt.show()

模型评估

# 为了评估我们模型的性能,我们计算了在训练集上50个epoch的F1_CURVE、P_CURVE、R_CURVE和PR_CURVE
# F1 曲线是用于评估二元分类器性能的一种图形化指标。它显示了在不同阈值下的 F1 分数随着真阳性率(召回率)的变化情况。
# F1 分数是精确度(Precision)和召回率(Recall)的调和平均值,它可以帮助我们在精确度和召回率之间找到一个平衡点。
# "P curve" 通常指代 "Precision-Recall curve",即精确度-召回率曲线。它是用于评估分类模型性能的一种常见工具,特别是在处理不平衡数据集时。
# Precision-Recall 曲线显示了在不同阈值下的精确度和召回率之间的关系。精确度(Precision)是被正确分类的正例占所有被分类为正例的样本的比例,
# 召回率(Recall)是被正确分类的正例占所有实际正例的样本的比例。
# 绘制 P-R 曲线的过程类似于绘制 ROC 曲线,只是在 P-R 曲线中,横轴通常是召回率,纵轴是精确度。在绘制过程中,
# 可以通过在模型输出的概率或得分上变化阈值,计算不同阈值下的精确度和召回率,并绘制曲线。
# P-R 曲线对于不平衡数据集的分类器评估尤为重要,因为它可以更清晰地显示出分类器在不同类别之间的性能差异。
# 通常情况下,当类别不平衡时,使用 P-R 曲线比 ROC 曲线更能展现出分类器的优势和缺陷。

# 以下是这些指标的具体结果图:
import matplotlib.pyplot as plt
import cv2

# 读取图像
image_path1 = "jupyter_need/F1_curve.png"
image_path2 = "jupyter_need/P_curve.png"
image_path3 = "jupyter_need/PR_curve.png"

image1 = cv2.imread(image_path1)
image2 = cv2.imread(image_path2)
image3 = cv2.imread(image_path3)

# 创建一个具有三个子图的图形
plt.figure(figsize=(15, 5))

# 在第一个子图中显示第一张图片
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB))
plt.axis('off')

# 在第二个子图中显示第二张图片
plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(image2, cv2.COLOR_BGR2RGB))
plt.axis('off')

# 展示图片
plt.show()

问题三

解题思路

针对问题三,利用第二问得到的检测分割模型,在附件三测试集上对测试数据进行检测分割,并保存结果到附件Test_results.xlsx

解题代码

# 利用第二问(前面)微调好的yolo,我们在对附件三中的200张甲骨文原始图像进行自动单字分割,分割结果保存在Test_results.xlsx文件中
#自动分割代码如下:
def main(opt):
    """Executes YOLOv5 tasks including training, validation, testing, speed, and study with configurable options."""
    check_requirements(ROOT / "requirements.txt", exclude=("tensorboard", "thop"))

    if opt.task in ("train", "val", "test"):  # run normally
        if opt.conf_thres > 0.001:  # https://github.com/ultralytics/yolov5/issues/1466
            LOGGER.warning(f"WARNING ⚠️ confidence threshold {opt.conf_thres} > 0.001 produces invalid results")
        if opt.save_hybrid:
            LOGGER.warning("WARNING ⚠️ --save-hybrid returns high mAP from hybrid labels, not from predictions alone")
        run(**vars(opt))

    else:
        weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]
        opt.half = torch.cuda.is_available() and opt.device != "cpu"  # FP16 for fastest results
        if opt.task == "speed":  # speed benchmarks
            # python val.py --task speed --data coco.yaml --batch 1 --weights yolov5n.pt yolov5s.pt...
            opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
            for opt.weights in weights:
                run(**vars(opt), plots=False)

问题四

解题思路

针对问题四,附件四给出部分甲骨文图像及其对应的简体中文,我们建立inception_v3分类模型,通过训练数据对模型进行微调。将得到的在甲骨文文字识别任务上微调后的inception_v3模型对测试集数据进行文字识别。将识别结果保存,写入论文。

解题代码

# inception model 训练代码:
import json
import os

import torch
import torch.nn as nn
import torchvision.models as models
from torchvision import transforms
from torch.utils.data import DataLoader, Dataset
from PIL import Image
from sklearn.metrics import f1_score
from torch.optim.lr_scheduler import StepLR
from tqdm import tqdm

# 检查CUDA是否可用,并设置设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')


# 构建自定义数据集
class CustomDataset(Dataset):
    def __init__(self, file_paths, labels, transform=None):
        self.file_paths = file_paths
        self.labels = labels
        self.transform = transform

    def __len__(self):
        return len(self.file_paths)

    def __getitem__(self, idx):
        image = Image.open(self.file_paths[idx]).convert('RGB')
        label = torch.tensor(self.labels[idx], dtype=torch.float32)

        if self.transform:
            image = self.transform(image)

        return image, label


# 超参数
batch_size = 32
num_epochs = 50
learning_rate = 0.001
num_classes = 76  # 假设有10个类别

# 转换图像
transform = transforms.Compose([
    transforms.Resize((299, 299)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

train_file_dir = "q4_data/train/image"
train_file_paths = os.listdir(train_file_dir)
# 给每个文件路径添加文件夹路径
train_file_paths = [os.path.join(train_file_dir, file_name) for file_name in train_file_paths]
# 从JSON文件中读取数据
with open("q4_train_inception.json", "r") as json_file:
    train_labels = json.load(json_file)
# 数据加载
train_dataset = CustomDataset(train_file_paths, train_labels, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)

# 加载预训练的InceptionV3模型
model = models.inception_v3(pretrained=True)
model.aux_logits = False  # 禁用辅助输出
# # 冻结模型参数
# for param in model.parameters():
#     param.requires_grad = False

# 修改最后一层全连接层以适应多标签分类任务
num_ftrs = model.fc.in_features
model.fc = nn.Sequential(
    nn.Linear(num_ftrs, 512),
    nn.ReLU(inplace=True),
    nn.Linear(512, num_classes),
    nn.Sigmoid()  # 多标签分类使用Sigmoid激活函数
)

model = model.to(device)
# 定义损失函数和优化器
criterion = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

# 学习率调度器
scheduler = StepLR(optimizer, step_size=5, gamma=0.5)

# 训练模型
total_step = len(train_loader)
for epoch in tqdm(range(num_epochs)):
    model.train()
    for i, (images, labels) in enumerate(tqdm(train_loader)):
        images = images.to(device)
        labels = labels.to(device)
        # 前向传播
        outputs = model(images)
        # 计算损失
        loss = criterion(outputs, labels)
        # 反向传播和优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if (i + 1) % 1000 == 0:
            print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
                  .format(epoch + 1, num_epochs, i + 1, total_step, loss.item()))

    # 调整学习率
    scheduler.step()

    # 评估循环
    model.eval()
    with torch.no_grad():
        # 初始化预测和标签列表
        all_preds = []
        all_labels = []
        correct_predictions = 0
        total_predictions = 0
        for images, labels in tqdm(train_loader):
            images = images.to(device)
            labels = labels.to(device)
            # 前向传播
            outputs = model(images)
            predicted = outputs > 0.5
            # 计算准确率
            correct_predictions += (predicted == labels.byte()).all(1).sum().item()
            total_predictions += labels.size(0)
            # 收集预测和真实标签
            all_preds.extend(predicted.cpu().numpy())
            all_labels.extend(labels.cpu().numpy())
        accuracy = correct_predictions / total_predictions
        f1 = f1_score(all_labels, all_preds, average='micro')
        print('Epoch [{}/{}], Accuracy: {:.4f}, F1 Score: {:.4f}'.format(epoch + 1, num_epochs, accuracy, f1))

    # 保存模型
    torch.save(model.state_dict(), f'ckpt/inceptionv3_ft_{epoch}_f1_{f1:.4f}_acc_{accuracy:.4f}.pth')

利用我们预处理后的数据,在inception模型上训练11个epoch后,在训练集上,模型准确率达到99.4%,F1值达到99.6%
接下来,我们利用训练好的inception模型对附件四甲骨文原始图像进行文字自动识别

资料获取

提供2024MathorCupBC题的思路分析与代码,欢迎进群讨论:953799264

B题目思路代码获取:http://app.niucodata.com/mianbaoduo/recommend.php?id=59179

B题目成品论文获取:http://app.niucodata.com/mianbaoduo/recommend.php?id=59182

  • 18
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值