尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于CNN的文字语种识别算法复现与工程实践

发布时间:2026/9/26 5:15:20

资讯中心
01
ARTICLE

基于CNN的文字语种识别算法复现与工程实践

基于CNN的文字语种识别算法复现与工程实践
简介面向深度学习与图像处理开发者的文字语种识别项目基于卷积神经网络构建解决多语言文字自动分类问题可用于跨语言文档处理与智能审核等场景。压缩包共12个文件其中9个Python脚本覆盖模型搭建含VGG、ResNet、LSTM及SPP层、训练与评估流程2个txt文件提供配置说明或数据指引1个Markdown文档便于快速上手。资源包整体仅16KB结构紧凑适合有一定神经网络基础的读者学习参考。项目融合多种模型对比与数据增强思路可从核心脚本中掌握图像预处理、特征提取到语种判别的完整实现路径尤其有助于理解CNN在文字形态与笔画结构识别上的应用。当前已有155人学习值得作为语种识别算法的入门范例与扩展基础。1. 这个压缩包在解决什么问题一张文字图像怎么被 CNN 认出来是哪国话做文档 OCR 的人都有过这种经历拿到一张扫描件先得搞清楚里面印的是中文还是英文、是俄文还是阿拉伯文。OCR 引擎是按语种拆的你拿一个中英文混合模型去认泰文输出全是乱码。最笨的办法是每个语种引擎各跑一遍再比对得分慢不说误判率还高。所以我第一次看到“基于卷积神经网络文字语种识别算法.zip”这个压缩包时第一反应就是这不就是我要的预分类器吗。它把文字图像直接送进卷积神经网络输出一个语种概率分布不用先做字符切分也不依赖词典。这篇笔记我就按自己的实操顺序把里面涉及的原理、代码、参数和踩过的坑完整写出来给同样在做 OCR 前置处理或文档分类的工程师做参考。2. 为什么语种识别首选 CNN从字符形状到全局纹理卷积在学什么2.1 语种识别是分类任务但输入不是字符串而是图像文字语种识别听起来像是文本分类实际上在图像场景里它更像“看”字形。中文文本里汉字一大堆笔画横平竖直结构方方正正英文单词由 26 个字母拼成高度基本一致有大量圆弧和竖向笔画阿拉伯文从右往左连写字符高度落差大日文满屏平假名、片假名韩文则大量出现圈圈和横竖组合。这些差异在图像上非常明显比直接分析字符串更直观。所以常见的做法是把语种识别当作图像分类任务输入一张文本行截图输出语种标签。这个方案的好处是不需要先 OCR 出一串字符也不依赖字体编码和语言模型坏处是你要处理好字型、字号、背景噪声这些无关因素。另一种路线是文本分类比如用 TextCNN 或 LSTM 对字符序列建模但它只适合已经拿到干净字符串的场景。压缩包里的算法走的是图像路线这也是落地中最常遇到的输入形态扫描件、手机拍照、截图都是图像。这里有一个容易混淆的点语种识别不是 OCR。OCR 要回答“图里有哪几个字、它们的排列顺序”语种识别只回答“这段文字是什么语言”。前者是序列标注问题后者是分类问题。分类问题用卷积神经网络天然合适因为它不需要建模字符之间的长程依赖只需要抓住文字整体外观上的强烈统计特征。换句话说就算一张图中的字一个都认不出来只要字形风格和标点特征在CNN 就能把语种猜个八九不离十。2.2 输入张量怎么构造尺寸、通道和归一化CNN 的输入是固定尺寸的张量。语种识别通常用灰度图就够因为颜色本身与语种无关反而可能让模型学到“黄色背景 中文”这种假特征。如果数据来源是彩色 PDF我一般会在预处理里先做灰度化再按需保留 RGB 做对照实验。通道数量不是越多越好灰度加简单对比度增强往往比三通道更稳尤其在老式扫描件上彩色信息常常是噪声。尺寸的坑在于宽高比。文本行图像天然是长条形的如果强行缩放到 64×64 正方形字会被压扁中文的撇捺和英文的弧线都变形。我的做法是固定一个高度比如 32宽度按文本长度裁剪后在 32 到 128 之间浮动如果网络要求固定尺寸就把宽高比超过一定范围的图先缩放再补边保持字形不被压。下表是我在实验中常用的三组参数图像尺寸 (H×W)通道适用场景备注64×64单通道印刷体字符截图正方形输入网络最简单适合快速验证32×128单通道文档文本行保持文本行长条比例需要宽卷积核96×320单通道含复杂背景的拍照文本分辨率高模型更重训练慢归一化直接用除以 255 再做 z-score 也行但要注意推理时一定要复用训练时的均值方差否则灰度分布差一点就翻车。数据增强方面随机加高斯噪声、轻微透视变形、模糊对语种识别都有帮助。我见过一个项目因为少了“随机亮度扰动”换了个暗的扫描仪后准确率直接从 97% 掉到 85%原因就是模型学会了绝对亮度而不是相对纹理。2.3 网络结构从 LeNet-5 到 ResNet语种识别需要多深的网络卷积神经网络的核心是局部感受野和权值共享。语种识别里第一层卷积学到的是横竖撇捺、圆弧、角点这些基础笔画第二层卷积把笔画组合成部首、字母连笔甚至单词轮廓再往上就是全局的文本纹理。所以关键不是“网络多深”而是感受野能不能覆盖到一个字符或者几个字符。LeNet-5 这种经典结构的感知范围对 64×64 的输入就够用了原本是识 MNIST 手写数字的简单迁移到语种分类上效果也不差。如果换成 ResNet-18精度会高一点但训练时间会长很多对于 6 到 10 类别的语种识别一个大致规律是没必要超过 ResNet-18更深的网络容易在中小数据集上过拟合。关于“卷积神经网络的汇聚层”也就是池化层它在语种识别里的作用是降低特征图冗余、提供平移不变性。文字在图像中的位置不一定居中同一个汉字偏左或偏右都正常池化能让模型对这种位移不那么敏感。MaxPooling 比 AveragePooling 在笔画边缘提取上更锐利但全局平均池化AdaptiveAvgPool2d作为全连接层之前的最后一层往往比直接 Flatten 更稳因为它把每个通道压缩成一个统计量大大减少参数。我实际用的一个小网络结构如下它类似简化版 VGG三层卷积加全局平均池化。注意这里的“汇聚层”就是池化层参数选 kernel2, stride2这是最常见的选择特征图尺寸直接减半。层输出尺寸参数Conv1 ReLU BN64×64×323×3, padding1MaxPool32×32×322×2, stride2Conv2 ReLU BN32×32×643×3, padding1MaxPool16×16×642×2, stride2Conv3 ReLU BN16×16×1283×3, padding1MaxPool8×8×1282×2, stride2AdaptiveAvgPool1×1×128全局池化Dropout FC6全连接分类这种结构参数量在百万级单张 64×64 的图在 CPU 上跑一次预测只需几毫秒很适合作为 OCR 流水线的第一个前置模块。3. 复现这个算法的最小方案数据、训练与推理代码3.1 数据准备用合成数据和公开语料生成多语种图文样本训练语种识别模型最怕的是数据集里只有一种字体。真实文档会用宋体、黑体、Helvetica、Times New Roman还有手写体。我不建议直接去爬网图而是先用合成数据把模型基础打稳再用真实样本微调。合成数据的做法很简单用 Python 的 PIL 在随机背景上渲染文字。下面这段代码生成 6 个语种的文本图像顺便把字体路径和示例字符集列出来。注意每种语言要使用独立字体否则模型会偷懒通过“同一个字体”来猜语种。import numpy as np from PIL import Image, ImageDraw, ImageFont, ImageFilter # 语种对应的字体路径和示例字符集实际项目里请换成你自己的字体文件 LANG_FONTS { zh: (fonts/NotoSansCJK-Regular.ttc, 中文语种识别测试混合汉字与标点。), en: (fonts/arial.ttf, The quick brown fox jumps over the lazy dog.), ja: (fonts/NotoSansJP-Regular.ttf, 日本語の文章を生成してモデルを訓練します。), ko: (fonts/NotoSansKR-Regular.ttf, 한국어 텍스트를 사용하여 모델을 학습합니다.), ru: (fonts/NotoSans-Regular.ttf, Это русский текст для проверки распознавания.), ar: (fonts/NotoNaskhArabic-Regular.ttf, هذا نص عربي لاختبار التعرف على اللغة.), } def render_sample(lang, output_size(64, 64)): font_path, text LANG_FONTS[lang] # 随机选一个字体大小模拟不同字号 font_size np.random.randint(18, 28) font ImageFont.truetype(font_path, font_size) fg (np.random.randint(0, 80),) * 3 # 深色前景 bg np.random.randint(180, 256) # 浅色背景 img Image.new(L, (256, 64), bg) draw ImageDraw.Draw(img) draw.text((8, 8), text, fontfont, fillfg) # 随机加一点噪声和模糊模拟扫描件 img img.filter(ImageFilter.GaussianBlur(radiusnp.random.uniform(0, 0.8))) img img.resize(output_size) return np.array(img, dtypenp.float32) / 255.0逻辑说明render_sample先创建一个 256×64 的灰度图背景色是随机浅色文字是固定深色。高斯模糊半径在 0 到 0.8 之间随机这一步能让模型对扫描模糊更鲁棒。最后统一缩放到 64×64并除以 255 归一化到 [0,1]。参数说明output_size必须与模型输入一致后面训练和推理都要用同一个值。font_size范围 18-28 是为了让字符在 64×64 的图里占 50%-80% 面积如果字号太小图像里大片空白模型只能学到背景。fg取深色 (0-80) 是为了保证前景与浅色背景的对比度足够如果你要模拟白底黑字的扫描件这个范围没问题。生成数据时不要只生成一批就固定下来。我建议每个 epoch 都重新调用render_sample生成新样本相当于无限量数据增强。配合torch.utils.data.Dataset可以这样写from torch.utils.data import Dataset class SynthLangDataset(Dataset): def __init__(self, lang_list, samples_per_epoch5000): self.lang_list lang_list self.samples_per_epoch samples_per_epoch def __len__(self): return self.samples_per_epoch def __getitem__(self, idx): # 每次随机选一个语种从对应字符集里随机截取一段文本 lang np.random.choice(self.lang_list) # 这里为了简化直接用 render_sample实际可以传入随机文本 x render_sample(lang) y self.lang_list.index(lang) return torch.tensor(x).unsqueeze(0), torch.tensor(y, dtypetorch.long)Dataset每次__getitem__都调用render_sample所以同一个 epoch 内不会出现重复样本。这样你甚至可以省掉单独的验证集因为模型永远看不到重复样本。但要注意验证阶段要换用固定种子或固定一批真实样本否则无法稳定评估。3.2 定义 CNN 模型一个能跑通的可调网络模型定义我采用三层卷积加全局平均池化的结构适合输入尺寸不固定的情况。全局平均池化会把最后一层特征图压缩成 1×1这样全连接层尺寸不会绑定输入分辨率以后想换 32×128 的输入也不用改全连接层。import torch import torch.nn as nn class LangNet(nn.Module): def __init__(self, num_classes6): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 32, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), nn.BatchNorm2d(32), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.BatchNorm2d(64), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.BatchNorm2d(128), nn.MaxPool2d(2), ) self.avgpool nn.AdaptiveAvgPool2d((1, 1)) self.classifier nn.Sequential( nn.Dropout(0.5), nn.Linear(128, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.3), nn.Linear(256, num_classes), ) def forward(self, x): x self.features(x) x self.avgpool(x) x x.flatten(1) return self.classifier(x)逻辑说明输入是单通道灰度图所以第一个nn.Conv2d的in_channels1。三层卷积的通道数从 32 加到 128每层后面都接 BatchNorm因为语种识别数据是合成图像分布波动大BN 能稳定训练。MaxPool2d(2)把特征图尺寸减半三层池化后 64×64 输入变成 8×8 特征图再经过全局平均池化得到 128 维向量。参数说明num_classes是语种数量如果是 6 类就传 6。Dropout 0.5 和 0.3 是调过的值如果你发现训练集准确率远高于验证集可以提高到 0.6 和 0.4如果欠拟合降到 0.3 和 0.2。AdaptiveAvgPool2d((1,1))不挑输入尺寸所以这里不需要你算全连接层的输入维度。这里有一个容易踩坑的点如果你要使用 ImageNet 预训练模型比如 ResNet18需要把模型的第一个卷积层从 3 通道改成 1 通道或者在预处理时把灰度图复制成 3 通道。很多开源项目没有处理这一步直接加载预训练权重会报 shape 错误。我一般用上面这个小型自定义网络因为语种识别任务简单没必要引入大模型而且自定义网络在 CPU 上推理也更快。3.3 训练与评估交叉熵、Adam 和早停的配套设置训练部分的核心是损失函数用交叉熵优化器用 Adam 并加一点 weight decay学习率用 1e-3 起步每个 epoch 后计算验证集准确率并做早停。不要小看早停语种识别模型在合成数据上很容易在某个 epoch 突然过拟合停晚一点就白练了。def train_epoch(model, loader, optimizer, criterion, device): model.train() total, correct, loss_sum 0, 0, 0.0 for x, y in loader: x, y x.to(device), y.to(device) optimizer.zero_grad() out model(x) loss criterion(out, y) loss.backward() optimizer.step() total y.size(0) correct (out.argmax(1) y).sum().item() loss_sum loss.item() * y.size(0) return loss_sum / total, correct / total逻辑说明标准训练循环。out.argmax(1)取概率最大的类作为预测与标签y比较计算准确率。损失loss_sum用loss.item() * y.size(0)还原批量总损失避免不同 batch 大小对平均 loss 造成偏差。主训练循环里我建议使用ReduceLROnPlateau或固定步长衰减的学习率调度器。如果验证准确率连续 3 个 epoch 不提升就把学习率除以 10。下面是一个带早停的训练入口def train_model(model, train_loader, val_loader, epochs30, lr1e-3): device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lrlr, weight_decay1e-4) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemax, factor0.5, patience3 ) best_acc 0.0 for epoch in range(epochs): train_loss, train_acc train_epoch(model, train_loader, optimizer, criterion, device) val_loss, val_acc evaluate(model, val_loader, criterion, device) scheduler.step(val_acc) print(fepoch {epoch1}: train_acc{train_acc:.3f} val_acc{val_acc:.3f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), langnet_best.pt) if optimizer.param_groups[0][lr] 1e-5: break # 学习率太小就停止参数说明weight_decay1e-4是 L2 正则能抑制过拟合。ReduceLROnPlateau的modemax表示监控验证集准确率当连续 3 个 epoch 不上升时学习率减半。factor0.5是减半速率。早停条件用学习率低于 1e-5 或 epoch 耗尽简单直接。evaluate函数和train_epoch的区别是不做梯度更新def evaluate(model, loader, criterion, device): model.eval() total, correct, loss_sum 0, 0, 0.0 with torch.no_grad(): for x, y in loader: x, y x.to(device), y.to(device) out model(x) loss criterion(out, y) total y.size(0) correct (out.argmax(1) y).sum().item() loss_sum loss.item() * y.size(0) return loss_sum / total, correct / total注意model.eval()会关闭 Dropout 和 BatchNorm 的统计量更新这是必须的。很多人推理时忘记调用.eval()导致每次预测结果都不一样原因就是 Dropout 还在随机丢弃。3.4 推理部署单张预测和批量输出的实现推理阶段最需要注意的是“预处理一致性”。训练时用 PIL 的Image.new(L)生成灰度图推理时打开一张彩色图也要先转L再 resize 到同一个output_size最后除以 255。如果你用 OpenCV 读图并 resize得到的 BGR 通道和像素插值方式可能与训练不一致准确率会掉 1-2 个点。def predict(model, img_path, output_size(64, 64)): from PIL import Image img Image.open(img_path).convert(L) img img.resize(output_size) # 与训练时的 resize 一致 x np.array(img, dtypenp.float32) / 255.0 x torch.from_numpy(x).unsqueeze(0).unsqueeze(0) # (1, 1, 64, 64) model.eval() with torch.no_grad(): out model(x) probs torch.softmax(out, dim1).squeeze().tolist() return probs # 长度为语种类别的概率列表逻辑说明unsqueeze(0)两次第一次加 batch 维第二次加 channel 维。因为训练时数据 shape 是(batch, 1, 64, 64)所以这里必须是四维张量。torch.softmax把输出变成概率概率之和为 1。返回的probs列表可以让你根据置信度决定是否交给下游 OCR。如果是批量推理比如一次处理 1000 张文档图像我建议把数据堆成 batch 一起送进模型而不是写 Python 循环逐张预测。GPU 上的 batch 推理能快几十倍CPU 上也有向量化加速。一个简单的做法是把所有图片预处理后的 numpy 数组堆在一起用torch.from_numpy(np.stack(images))一次前向。4. 语种识别模型训练常见问题排查5 个让你翻车的坑4.1 现象一模型把所有输入都识别成同一语种现象训练了 20 个 epoch准确率也有 80%但拿出来测任何图预测结果都集中在一个语种比如全是中文。原因最常见的是类别样本不均衡中文样本是其他语种的 3 倍模型学会“放弃思考”直接输出先验概率最高的类别。另一个原因是最后一个全连接层的 bias 初始化过大导致输出 logit 偏向某一类。学习率过大时也可能出现梯度爆炸让分类层的权重全部退化为一个方向。解决先统计数据集中每个语种的样本数用torch.utils.data.WeightedRandomSampler重采样让每个语种每个 epoch 出现的次数接近。然后给CrossEntropyLoss传入weight参数常用取值是1 / 类别样本数。最后把学习率降到 1e-4 重新训练几次观察验证集准确率是否立刻变化。如果还是不行手动把最后一个 Linear 的 bias 初始化为 0再训练。4.2 现象二中文、日文、韩文互相混淆现象测试集上其他地方都准只有中日韩三类之间频繁误判中文图被预测成日文韩文图被预测成中文。原因这三种语言共享大量汉字字形。中文“語”和日文“語”几乎长得一样韩文汉字也经常出现。CNN 在局部感受野内很难判断一个字到底是中文还是日文只能靠上下文中的假名或谚文来识别。如果训练样本里日文假名和韩文谚文占比太少模型就直接用汉字字形猜了。解决训练数据生成时日文文本不能只放汉字要保证平假名、片假名占比不低于 40%。韩文文本一定要用谚文为主汉字词占比降到 20% 以下。另一个有效技巧是把标点符号也作为特征保留中文漏出来的句号“。”和日文的句号“。”还是有差别英文的句点在图像上高度和位置上都不一样。我在数据集里专门加入了 20% 的“全角标点”样本让模型学会关注标点。4.3 现象三训练损失直线下降验证准确率卡在 70% 不上涨现象训练集准确率已经 99%验证集准确率在 70% 附近怎么调都不动。原因这是典型的过拟合 分布不匹配。合成数据里的字体和验证集真实扫描件的字体不一样模型记住了合成字体的纹理比如衬线、笔画粗细而这些纹理在真实图像上不存在。另一个原因是 Dropout 和 weight decay 太轻正则没有起到作用。解决先加大正则强度Dropout 从 0.5 调到 0.7weight_decay从 1e-4 调到 5e-4。然后把合成数据的背景从纯色改成真实纸张纹理可以在数据生成时叠加一个随机噪声图层。如果验证集约 3000 张且标注成本可接受可以额外收集 2000 张真实样本做微调用合成数据预训练真实数据微调lr降到 1e-4 以下。这一步是解决分布不匹配最有效的手段没有之一。4.4 现象四长文本和短文本的表现差异巨大现象对单行短文本5 个字符以内识别准确率 95%对整段长文本50 个字符以上准确率掉到 70%越长的文本越容易误判。原因固定 resize 到 64×64 时长文本的每个字符被压缩成几个像素笔画糊成一团CNN 只能看到模糊的横线原始语言结构全丢了。短文本则相反每个字符都很清晰模型可以按单字形状判断。这是固定输入分辨率方案最典型的坑。解决改成“固定高度、可变宽度”的输入策略。比如统一高度 32宽度在 32 到 128 之间按文本原始比例缩放然后用AdaptiveAvgPool2d去除全连接层对宽度敏感的依赖。如果网络只支持固定尺寸那就对长文本做滑窗切割每 64 像素切一块分别预测语种再对概率取平均。这个做法的代价是增加了推理时间但长文本的准确率能回弹 10 个点以上。4.5 现象五换了一个字体准确率崩了现象训练时只用了 Arial测试时遇到 Times New Roman英文语种准确率从 98% 跌到 50%。换成另一种中文字体中文直接没了。原因模型在偷懒。它发现“Arial 的无衬线字形 → 英文”这个捷径于是根本没有学习英文单词的结构特征而是直接匹配字体轮廓。语种识别里字体是比语种更强的视觉信号不处理好字体模型永远在学字体分类而不是语种分类。解决生成训练数据时每个语种至少用 5 种不同字体并且字体之间不能有交叉。比如中文字体用宋体、黑体、楷体英文用 Arial、Times New Roman、Courier New。关键是要做到“同一个语种有多个字体”让模型被迫忽略字体差异。验证集单独留出 2 种训练时没见过的字体专门看泛化能力。如果换字体后准确率还崩说明你的数据增强还不够继续加旋转、透视、缩放等扰动。5. 验证与进阶用混淆矩阵和 CRNN 把语种识别准确率再往上提验证模型是否真正学到语种特征最直接的手段是混淆矩阵。你可以把测试集上所有预测结果和真实标签交给 sklearn一行代码打印出矩阵。from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay # y_true 是真实标签y_pred 是模型 argmax 后的预测 cm confusion_matrix(y_true, y_pred) disp ConfusionMatrixDisplay(confusion_matrixcm) disp.plot()观察矩阵时重点看“偏斜”的地方。如果中日韩互相混淆就去查那些错例图像里是不是汉字占比太大如果英文和法文混淆就看是不是因为法文重音字符在低分辨率下看不清。混淆矩阵能告诉你下一个加数据的方向而不是盲目调网络。这个习惯我一直保留先诊断再动手绝不在看不见错误分布的情况下瞎调参数。进阶方向我推荐把 CNN 和 LSTM 结合的 CRNN 架构。CNN 负责从图像中提取特征序列LSTM 对序列建模上下文再用 CTC 对齐到字符序列。这样不仅输出语种还能顺带输出文字内容。训练时语种分类分支和字符识别分支可以共享主干网络损失函数是两个任务的加权和。代价是训练复杂度上升需要更多样本但如果你最终目标是做小语种 OCR这条路线值得投入。部署加速方面训练好的 PyTorch 模型可以转成 ONNX用torch.onnx.export做一次导出然后交给 ONNX Runtime 推理。在我的测试里CPU 上单张 64×64 图像的推理时间可以从 3 毫秒降到 1 毫秒以下。要注意导出时的固定输入尺寸和训练保持一致动态轴只在 batch 维度上开。我现在拿到这类语种识别压缩包会先不看模型结构去找它的数据生成脚本和类别列表。因为语种识别七成功夫在数据上网络结构只要别太离谱都能 work。这个方向确定值得做尤其配合 OCR 流水线能省下大量小语种适配成本。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。