2024年第十四届MathorCup高校数学建模挑战赛
B题 甲骨文智能识别中原始拓片单字自动分割与识别研究
原题再现:
甲骨文是我国目前已知的最早成熟的文字系统,它是一种刻在龟甲或兽骨上的古老文字。甲骨文具有极其重要的研究价值,不仅对中国文明的起源具有重要意义,也对世界文明的研究有着深远影响。在我国政府的大力推动下,甲骨文研究已经进入一个全新的发展阶段。人工智能和大数据技术被应用于甲骨文全息性研究及数字化工程建设,成为甲骨文信息处理领域的研究热点。
甲骨文拓片图像分割是甲骨文数字化工程的基础问题,其目的是利用数字图像处理和计算机视觉技术,在甲骨文原始拓片图像的复杂背景中提取出特征分明且互不交叠的独立文字区域。它是甲骨文字修复、字形复原与建模、文字识别、拓片缀合等处理的技术基础[2]。然而,甲骨拓片图像分割往往受到点状噪声、人工纹理和固有纹理三类干扰元素的严重影响[3]且甲骨文图像来源广泛,包括拓片、拍照、扫描、临摹等,不同的图像来源,其干扰元素的影响是不同的。由于缺乏对甲骨文字及其干扰元素的形态先验特征的特殊考量,通用的代表性图像分割方法目前尚不能对甲骨文原始拓片图像中的文字目标和点状噪声、人工纹理、固有纹理进行有效判别,其误分割率较高,在处理甲骨拓片图像时均有一定局限性。如何从干扰众多的复杂背景中准确地分割出独立文字区域,仍然是一个亟待解决的具有挑战性的问题。
图1为一张甲骨文原始拓片的图像分割示例,左图为一整张甲骨文原始拓片,右图即为利用图像分割算法[4]实现的拓片图像上甲骨文的单字分割。甲骨文的同一个字会有很多异体字,这无疑增加了甲骨文识别的难度,图2展示了甲骨文中“人”字的不同异体字。
现希望通过对已标记的甲骨文图像进行分析、特征提取和建模,从而实现对一张新的甲骨文图像进行单个文字的自动分割和识别。具体任务如下:
问题1:对于附件1(Pre test 文件夹)给定的三张甲骨文原始拓片图片进行图像预处理,提取图像特征,建立甲骨文图像预处理模型,实现对甲骨文图像于扰元素的初步判别和处理。
问题 2:对甲骨文原始拓片图像进行分析,建立一个快速准确的甲骨文图像分割模型,实现对不同的甲骨文原始拓片图像进行自动单字分割,并从不同维度进行模型评估。其中附件2(Train 文件夹)为已标注分割的数据集。
问题 3:利用建立的甲骨文图像分割模型对附件3(Test文件夹)中的200 张甲骨文原始拓片图像进行自动单字分割,并将分割结果放在“Test results.xlsx”中,此文件单独上传至竞赛平台。
问题 4:基于前三问对甲骨文原始拓片图像的单字分割研究,请采用合适的方法进行甲骨文原始拓片的文字识别,附件4(Recognize 文件夹)中给出了部分已标注的甲骨文字形(不限于此训练集,可自行查找其他资料,如使用外部资料需在论文中注明来源),请对测试集中的 50 张甲骨文原始拓片图像进行文字自动识别,并以适当结果呈现。
整体求解过程概述(摘要)
甲骨文拓片图像分割一直是甲骨文研究中重要的研究内容,任务是从甲骨拓片中找到甲骨字符并提取独立的文字区域,这是字形破译的前提。然而,甲骨文拓片图像分割常受点状噪声、人工纹理和固有纹理干扰,使得现有传统图像分割方法误分割率较高,难以有效区分文字目标与各类干扰元素。针对这一难点,本文提出了一种基于深度学习的甲骨文单字自动分割与识别方法。
针对问题一,基于附件一所提供的三张图片,分析得到图像的主要干扰因素包括字符过小、图像质量差异大、字形破损和噪声干扰。为尽量解决以上因素带来的干扰,设计了一种Laplace变换—稀疏表示和低秩逼近去噪—同态滤波—高斯滤波的甲骨文原始拓片图像预处理模型,通过边缘增强、对比度增强、图像去噪等方法实现图像质量的提升,使字符特征更加突出,为后续字符分割提供可靠的图像数据支持。
针对问题二,通过任务分析可知,甲骨文原始拓片图像的自动单字分割任务不仅具有图像分割的基本特性,还融合了目标检测的元素,所以我们采用了目标检测领域应用最广泛的 YOLO 算法来实现字符的检测与分割。首先对数据集图像进行预处理;接着针对数据集给出的标签,将其转化成适用于YOLO算法的标注格式;然后在不同划分比例的数据集下,选择不同的YOLO 版本和不同的网络模型进行训练与效果对比,并且针对训练中存在的微小字符难以识别的问题,引入了SIOU损失函数来提高小字符检测精。最后通过对比评估得出,在训练集与验证集的比例为8:2的条件下,引入SIOU损失函数后的 YOLOv8m 网络模型既达到了最高的检测精度又权衡了模型复杂度。并且在验证网络模型时,我们发现部分原始标注存在偏移误标现象,这在很大程度上制约了网络精度的进一步提升。
针对问题三,我们选择问题二中训练精度最高的改进 YOLOv8m 网络模型来实现自动单字检测并分割,最终分割结果保存于Test_results.xlsx文件中。
针对问题四,根据给定的76种甲骨文汉字原始数据,建立甲骨文拓片图像分类识别模型。在分类识别模型中,采用卷积神经网络与YOLOv8 分类网络两种方法实现图像的分类训练和验证,数据集划分比例为 7:3。通过观察测试集文字特征,建立了原始数据集扩充模型,加入“王”字构建了77种甲骨文汉字数据集,分类任务变为77分类。卷积神经网络采用AlexNet、VGG16、GoogleNet、ResNet18-50-152 六类模型,通过对比选取分类精度最高的GoogleNet 模型与YOLOv8 分类网络用于测试集文字识别。考虑到每张测试集图片并非单个文字,因此又建立了图像分割模型,该模型用问题而的目标检测模型对拓片进行分割。最后使用GoogleNet模型与YOLO算法分别对测试图片进行文字识别,保存结果,发现两种方法分类精度相当,YOLOv8分类精度可达94.5%,GoogleNet 可达 93.7%,最终预测结果表明,YOLOv8能够更好地挖掘甲骨文图像的深层次特征,文字识别效果更好。
模型假设:
1. 我们认为,所有的数据来源于权威的原始数据,并且这些数据都是真实可靠的。
2. 假设在神经网络预测中,输入变量作为网络的第一层合理有效。
问题分析:
问题一的分析
对于问题一,即对给定的三张甲骨文原始拓片图像进行图像预处理,实现对甲骨文图像干扰元素的判别和处理。首先需要利用所给三张甲骨文图像,分析出甲骨文原始拓片图像存在的常见干扰情况及图像噪声类型。原始拓片图像中可能包含各种噪声,如灰尘、划痕等,需要通过适当的降噪技术减少这些影响。同时,由于拍摄条件和拓片材质的差异,图像的亮度和对比度可能不均匀,需进行调整以便更好地突出文字信息。甲骨上还可能存在非文字的干扰。针对以上不同情况,基于数字图像处理相关方法进行图像的去噪和增强,去除图像中的干扰元素,实现图像的去噪和增强。
问题二的分析
问题二聚焦于甲骨文原始拓片图像的自动单字分割任务,旨在构建一种高效且精确的甲骨文图像分割模型。这一任务本质上属于图像分割的范畴,但与传统的图像分割任务相比,它呈现出不同的任务需求。传统的图像分割任务侧重于识别并区分图像中的不同部分,如人物、汽车等对象。从技术层面分析,这要求算法能够根据不同语义信息将像素点进行归类,以区分出具有相同语义的区域。然而,在甲骨文图像分割任务中,情况有所不同。根据题目所提供的训练集与对应的标注框信息,如图所示,我们发现这些标注框均为平行矩形框,这与目标检测任务中的边界框(bounding box)类似。因此,本任务的要求也更倾向于目标检测,即不仅需要检测到甲骨文的存在,还需确认其是否属于甲骨文,并给出相应的检测框,以确保文字能够完整地被分割出来。
综上,甲骨文原始拓片图像的自动单字分割任务不仅具有图像分割的基本特性,还融合了目标检测的元素,对模型的准确性和效率提出了更高的要求。因此,构建一个能够适应这些要求的模型是本文的主要研究目标。鉴于甲骨文原始拓片图像的特点,决定采用目标检测领域中表现出色且经广泛验证的 YOLOv5 算法来解决本问题中的甲骨文图像分割任务。YOLOv5算法以其出色的性能、高准确性和实时处理能力而著称,能够很好地适应甲骨文图像分割的需求。
问题三的分析
问题三需要采用问题二中建立模型对附件中的甲骨文原始拓片图像进行单字分割。我们在问题二中已经完成了对网络模型的训练,只需要将图像输入网络即可获得甲骨文字符的坐标,再将YOLO的标注格式转化成所需格式,即可得到字符分割结果。
问题四的分析
问题四需要利用训练集中已经标注的76类甲骨文字形实现对测试集中50张甲骨文原始拓片图像的自动识别。在利用问题二和问题三所建立的甲骨文字符分割模型对拓片
图像进行分割后,字符的识别任务转化为字符的分类任务。 因此,选择多种不同卷积神经网络模型进行字符分类,并对比分类精度;再利用YOLO网络实现甲骨文字符识别,对比并分析两种网络模型的性能。
模型的建立与求解整体论文缩略图
全部论文请见下方“ 只会建模 QQ名片” 点击QQ名片即可
部分程序代码:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from skimage.feature import hog, local_binary_pattern
from sklearn.svm import SVC
from tensorflow.keras.applications import VGG16
from tensorflow.keras.preprocessing import image
from tensorflow.keras.models import Model
class OracleBonePreprocessor:
def __init__(self):
self.clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
self.svm = SVC(kernel='rbf', class_weight='balanced')
self.feature_extractor = self._build_feature_extractor()
def _build_feature_extractor(self):
"""构建混合特征提取器"""
base_model = VGG16(weights='imagenet', include_top=False)
return Model(inputs=base_model.input, outputs=base_model.get_layer('block5_pool').output)
def preprocess(self, img_path, visualize=False):
"""主处理流程"""
# 读取图像
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
if img is None:
raise ValueError(f"无法读取图像: {img_path}")
# 预处理流水线
denoised = self._denoise(img)
enhanced = self._enhance_contrast(denoised)
texture_suppressed = self._suppress_texture(enhanced)
edges = self._detect_edges(texture_suppressed)
# 特征提取
features = self._extract_features(enhanced)
# 可视化
if visualize:
self._visualize_process(img, denoised, enhanced, texture_suppressed, edges)
return {
'processed_img': texture_suppressed,
'edges': edges,
'features': features
}
def _denoise(self, img):
"""混合去噪"""
# 中值滤波去除椒盐噪声
median = cv2.medianBlur(img, 5)
# 非局部均值去噪
denoised = cv2.fastNlMeansDenoising(median, h=15, templateWindowSize=7, searchWindowSize=21)
return denoised
def _enhance_contrast(self, img):
"""对比度增强"""
clahe = self.clahe.apply(img)
# Gamma校正
gamma = 1.5
invGamma = 1.0 / gamma
table = np.array([((i / 255.0) ** invGamma) * 255 for i in np.arange(0, 256)]).astype("uint8")
return cv2.LUT(clahe, table)
def _suppress_texture(self, img):
"""纹理抑制"""
# Gabor滤波
kernel = cv2.getGaborKernel((25,25), 5, np.pi/4, 10, 0.5, 0, ktype=cv2.CV_32F)
gabor = cv2.filter2D(img, cv2.CV_8UC3, kernel)
# 小波变换
coeffs = pywt.dwt2(gabor, 'haar')
cA, (cH, cV, cD) = coeffs
cD_thresh = pywt.threshold(cD, 15, mode='soft')
reconstructed = pywt.idwt2((cA, (cH, cV, cD_thresh)), 'haar')
return np.uint8(reconstructed)
def _detect_edges(self, img):
"""自适应边缘检测"""
# 自动Canny算法
sigma = 0.33
v = np.median(img)
lower = int(max(0, (1.0 - sigma) * v))
upper = int(min(255, (1.0 + sigma) * v))
edges = cv2.Canny(img, lower, upper)
# 形态学优化
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel, iterations=2)
return closed
def _extract_features(self, img):
"""混合特征提取"""
# LBP特征
lbp = local_binary_pattern(img, P=16, R=2, method='uniform')
hist_lbp, _ = np.histogram(lbp, bins=256, range=(0, 256))
# HOG特征
fd_hog, _ = hog(img, orientations=8, pixels_per_cell=(16,16),
cells_per_block=(1,1), visualize=True)
# 深度特征
img_rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
img_tensor = image.img_to_array(img_rgb)
img_tensor = np.expand_dims(img_tensor, axis=0)
deep_features = self.feature_extractor.predict(img_tensor).flatten()
return np.concatenate([hist_lbp, fd_hog, deep_features])
def _visualize_process(self, original, denoised, enhanced, suppressed, edges):
"""处理过程可视化"""
plt.figure(figsize=(20, 10))
plt.subplot(151), plt.imshow(original, cmap='gray')
plt.title('Original'), plt.axis('off')
plt.subplot(152), plt.imshow(denoised, cmap='gray')
plt.title('Denoised'), plt.axis('off')
plt.subplot(153), plt.imshow(enhanced, cmap='gray')
plt.title('Enhanced'), plt.axis('off')
plt.subplot(154), plt.imshow(suppressed, cmap='gray')
plt.title('Texture Suppressed'), plt.axis('off')
plt.subplot(155), plt.imshow(edges, cmap='gray')
plt.title('Edges'), plt.axis('off')
plt.tight_layout()
plt.show()
def train_classifier(self, features, labels):
"""训练干扰判别分类器"""
self.svm.fit(features, labels)
def predict_region(self, region):
"""区域分类预测"""
features = self._extract_features(region)
return self.svm.predict([features])[0]
# ========== 使用示例 ==========
if __name__ == "__main__":
processor = OracleBonePreprocessor()
# 单张图像处理
result = processor.preprocess("Pre_test/w01906.jpg", visualize=True)
# 批量处理
import os
output_dir = "processed_results"
os.makedirs(output_dir, exist_ok=True)
for img_file in os.listdir("Pre_test"):
if img_file.endswith((".jpg", ".png")):
try:
result = processor.preprocess(os.path.join("Pre_test", img_file))
cv2.imwrite(f"{output_dir}/processed_{img_file}", result['processed_img'])
np.save(f"{output_dir}/features_{img_file[:-4]}", result['features'])
except Exception as e:
print(f"处理失败: {img_file} - {str(e)}")
print("预处理完成!结果保存在", output_dir)