☰
街景字符识别模型训练与验证:从DBNet检测到CRNN+CTC识别的全流程解析
2026/10/14 11:24:45 网站建设 项目流程

1. 街景字符识别到底在识别什么

第一次接触街景字符识别这个任务,很多人会以为它跟普通的OCR是一回事。实际上差别挺大。普通文档OCR面对的是扫描件、截图、PDF转出来的干净图片,文字排列规整,背景干净,字体统一。而街景字符识别面对的是行车记录仪或者街拍采集到的画面,里面有店铺招牌、路牌、门牌号、车身广告字,光照忽明忽暗,角度歪七扭八,遮挡、模糊、反光全都有。

这个任务的核心目标,是从一张自然场景图片中定位出所有文字区域,并识别出每个区域里的字符内容。它属于计算机视觉里“文本检测+文本识别”的联合任务,业界常说的OCR(Optical Character Recognition,光学字符识别)在街景场景下的一个硬骨头分支。

为什么单独把它拎出来做模型训练和验证?因为街景字符识别对模型的鲁棒性要求极高。你在实验室用干净数据集训出来的模型,放到真实街景里可能连一半的准确率都保不住。所以从数据准备、模型选型、训练策略到验证方法,每一步都需要针对街景场景做专门设计。

这篇文章适合谁看?如果你已经了解深度学习基础,做过图像分类或者目标检测的项目,想进一步搞明白场景文本识别模型的训练和验证流程,那这篇内容就是给你准备的。我会从整体设计思路讲起,把数据预处理、模型结构选择、损失函数设计、训练技巧、验证指标和排查经验都拆开说清楚。即使你之前没接触过OCR方向,跟着走一遍也能建立起完整的认知框架。

2. 整体方案设计与技术路线拆解

2.1 为什么选检测加识别的两阶段方案

街景字符识别的技术路线大致分两类:一阶段端到端方案和两阶段方案。一阶段方案用一个网络直接输出字符序列和位置,代表方法有基于Transformer的端到端模型。两阶段方案先做文本检测把文字区域框出来,再对每个框做字符识别。

我最终选的是两阶段方案,原因有三个。第一,两阶段方案的每个阶段可以独立调试和优化。检测效果不好就单独调检测模型,识别不准就单独调识别模型,排查问题的粒度更细。第二,街景场景里文字尺度变化非常大,近处的招牌字可能有几百像素高,远处的路牌字可能只有十几个像素。检测阶段可以用多尺度特征金字塔来应对这个问题,识别阶段则可以把每个文本区域统一缩放到固定高度再处理。第三,两阶段方案对训练数据的要求更灵活。检测只需要文本框标注,识别只需要字符序列标注,可以分别利用不同来源的数据集。

当然两阶段方案也有缺点,就是推理速度比端到端慢,因为要跑两个网络。但在模型训练和验证阶段,速度不是首要考虑因素,可控性和可调试性才是。

2.2 检测阶段的技术选型逻辑

文本检测这个子任务,主流方法有基于回归的(如TextBoxes、EAST)和基于分割的(如PSENet、DBNet)。我选的是DBNet(Differentiable Binarization Network,可微分二值化网络)的思路。

DBNet的核心创新在于它把二值化这个过程做成了可微分的。传统分割方法在得到概率图之后,需要设定一个阈值把概率图转成二值图,这个阈值是超参数,调起来很麻烦。DBNet让网络自己学习每个像素的阈值,概率图和阈值图一起参与训练,最后通过一个近似阶跃函数得到二值图。这样做的好处是网络能自适应不同区域的文字特征,对密集文字和稀疏文字都能处理得比较好。

另一个考虑是DBNet的后处理比较简单。它输出的概率图经过阈值化之后,用连通域分析就能得到文本区域,不需要像EAST那样做复杂的NMS(非极大值抑制)或者像素聚合。这在调试阶段特别友好,你可以直接把中间的概率图可视化出来看,直观判断模型学到了什么。

2.3 识别阶段为什么用CRNN加CTC

识别阶段我选的是CRNN(Convolutional Recurrent Neural Network,卷积循环神经网络)加CTC(Connectionist Temporal Classification,连接时序分类)的经典组合。

CRNN的结构分三部分。第一部分是卷积层,用来提取图像特征,把输入的高度压缩到1,宽度保留为特征序列长度。第二部分是循环层,通常用双向LSTM(Long Short-Term Memory,长短期记忆网络),对特征序列做时序建模,捕捉字符之间的上下文关系。第三部分是转录层,用CTC把循环层的输出序列映射到最终的字符序列。

为什么不用基于Attention的序列到序列模型?Attention类方法在规则文本上表现很好,但在街景场景下,文字可能有弯曲、旋转、透视变形,Attention的对齐机制容易跑偏。CTC的优势在于它不需要预先知道字符的位置,只需要输入序列和输出序列的长度对应关系合理就行,对字符间距不均匀的情况更鲁棒。

还有一个实际考虑是CTC的训练收敛更稳定。Attention模型在训练初期容易出现对齐混乱,需要更精细的学习率调度和预热策略。CTC虽然也有自己的问题(比如重复字符的处理),但整体上更容易调。

2.4 数据策略:合成数据加真实数据混合训练

街景字符识别的公开数据集规模通常不够大,而且标注成本极高。我的策略是先用合成数据做预训练,再用真实数据做微调。

合成数据用文本渲染引擎生成,把不同字体、不同颜色、不同背景的文字贴到各种自然场景图片上,同时施加随机透视变换、模糊、噪声、光照变化等增强。这样可以在短时间内生成几十万甚至上百万张带精确标注的训练样本。

但合成数据和真实数据之间存在域差异(Domain Gap)。合成数据的文字边缘太干净,背景融合不够自然,真实场景里的反光、阴影、运动模糊很难完全模拟。所以微调阶段必须用真实数据,哪怕只有几千张,也能显著提升模型在真实场景下的表现。

3. 核心细节解析与实操要点

3.1 数据标注格式的统一与转换

街景字符识别的数据标注有两种常见格式:一种是四边形标注,用四个点的坐标表示文本区域;另一种是矩形标注,用左上角坐标和宽高表示。DBNet训练需要的是分割图,所以要把四边形标注转成二值分割图。

转换过程是这样的:先创建一个和原图同尺寸的全零矩阵,然后用多边形填充算法把四边形区域填成1,得到文本区域掩码。接着对掩码做腐蚀操作得到收缩后的文本核心区域,对掩码做膨胀操作再减去原始掩码得到文本边界区域。最终训练用的标签有三张图:概率图标签(收缩后的核心区域)、阈值图标签(边界区域)、二值图标签(原始掩码)。

这里有个细节很容易踩坑:收缩比例的选择。收缩太多会导致文字区域太小,模型学不到完整的字符特征;收缩太少则文字区域之间容易粘连,后处理时连通域分不开。我的经验是收缩比例设在0.4到0.6之间比较合适,具体值要根据数据集中文字的平均宽度来调。文字普遍较宽时收缩比例可以大一点,文字密集时收缩比例要小一点。

3.2 识别阶段的数据增强策略

识别模型的输入是检测框裁剪出来的文本区域图片,统一缩放到高度32像素,宽度按比例缩放但不超过320像素。数据增强在这个阶段特别重要,因为识别模型需要见过足够多的字体、颜色、背景变化。

我常用的增强手段包括:随机灰度化(模拟黑白招牌)、随机调整亮度和对比度(模拟不同光照)、随机高斯模糊(模拟失焦和运动模糊)、随机透视变换(模拟拍摄角度变化)、随机添加椒盐噪声(模拟传感器噪声)。这些增强的强度不能太大,否则会把字符本身的结构破坏掉。比如高斯模糊的核大小不要超过5,透视变换的形变幅度不要超过原图宽高的10%。

还有一个容易被忽略的增强是随机padding。街景文字有时候会被截断,只露出一部分。训练时随机在文本区域两侧或上下添加背景padding,可以让模型学会处理不完整的文字输入。

3.3 CTC损失函数的实现要点

CTC损失的计算涉及一个动态规划过程,把网络输出的每个时间步的概率分布对所有可能的对齐路径求和。PyTorch里有现成的CTCLoss,但用的时候有几个参数需要特别注意。

第一个是blank标签的索引。CTC需要一个额外的blank类别来表示“无字符”状态,通常设为0,实际字符从1开始编号。如果搞错了blank的位置,训练会完全无法收敛。

第二个是输入序列长度和标签长度的关系。CTC要求输入序列长度不小于标签长度,否则损失无法计算。在CRNN里,输入序列长度等于特征图的宽度。如果某个文本区域的字符特别多,而缩放后的宽度不够,就会出现输入长度小于标签长度的情况。解决办法是在数据预处理时保证最小宽度,或者对特别长的文本区域单独处理。

第三个是zero_infinity参数。当输入序列长度小于标签长度时,CTC损失会变成无穷大。设置zero_infinity=True可以把这些样本的损失置零,避免梯度爆炸。但这只是权宜之计,根本解决办法还是保证数据预处理时不会出现这种样本。

3.4 学习率调度与 warmup 策略

街景字符识别模型的训练对学习率非常敏感。学习率太大,损失震荡不收敛;学习率太小,训练速度慢且容易陷入局部最优。

我的做法是先用warmup把学习率从很小的值线性增加到初始学习率,再按余弦退火或者阶梯下降的方式衰减。warmup的作用是让模型在训练初期不要因为随机初始化的大梯度而跑偏,特别是CRNN里的LSTM层,对初始学习率很敏感。

具体参数上,检测模型(DBNet)的初始学习率设在0.01左右,识别模型(CRNN)的初始学习率设在0.001左右。warmup的步数占总训练步数的5%到10%。衰减策略我倾向于用余弦退火,因为它不需要手动设置衰减节点,训练过程更平滑。

还有一个技巧是分层设置学习率。卷积层的学习率可以比循环层稍大,因为卷积层的特征提取能力更通用,需要更快适应新数据;循环层负责时序建模,学习率太大会破坏已经学到的序列模式。

4. 实操过程与核心环节实现

4.1 检测模型训练全流程

先讲检测模型的训练。数据准备阶段,我把所有训练图片统一缩放到640乘640,保持长宽比,短边不足的用灰色填充。标签生成按前面说的方式生成概率图、阈值图、二值图三张标签。

网络结构上,主干网络用ResNet50,去掉最后的全连接层,保留四个阶段的特征图。然后用一个特征金字塔网络(FPN)把四个尺度的特征融合起来,最终输出原图四分之一分辨率的特征图。预测头分三个分支:概率图分支输出单通道特征图,阈值图分支输出单通道特征图,二值图分支由概率图和阈值图计算得到。

损失函数是概率图损失、阈值图损失、二值图损失的加权和。概率图和阈值图用二值交叉熵损失,二值图用带类别平衡的交叉熵损失,因为文字像素通常只占整张图的很小一部分,正负样本极度不平衡。类别平衡权重我一般设成正样本权重为负样本的5到10倍。

训练时batch size设8,用Adam优化器,初始学习率0.01,warmup 500步,余弦退火到0.0001。总共训练200个epoch。每10个epoch在验证集上评估一次,保存指标最好的模型。

训练过程中要监控三个指标:概率图的二值交叉熵损失、阈值图的损失、以及验证集上的hmean(调和平均数,综合精确率和召回率)。如果概率图损失下降但hmean不升,说明模型过拟合了,需要加数据增强或者加正则化。

4.2 识别模型训练全流程

识别模型的数据准备依赖检测模型的输出。我用训练好的检测模型在训练集上跑一遍,得到所有文本区域的检测框,然后根据检测框和真实标注的IoU(交并比)做匹配,IoU大于0.5的认为是正样本,裁剪出来作为识别模型的训练数据。

裁剪出来的文本区域统一缩放到高度32,宽度按比例缩放,最大不超过320。如果宽度不足320,在右侧用0填充。标签是字符序列,每个字符映射到一个整数ID,序列末尾用CTC的blank补齐。

网络结构上,卷积部分用7层卷积加池化,把输入的高度从32逐步压缩到1,宽度从320压缩到40左右。循环部分用两层双向LSTM,隐藏层维度256。输出层是全连接层,输出维度等于字符集大小加1(加1是给blank的)。

损失函数用CTCLoss,blank索引设为0。优化器用Adam,初始学习率0.001,warmup 200步,余弦退火到0.00001。batch size设64,训练150个epoch。

训练时要注意监控验证集上的字符错误率(Character Error Rate,CER)。CER的计算方式是编辑距离除以标签总长度。如果训练集CER持续下降但验证集CER开始上升,就是过拟合的信号,需要早停或者加Dropout。

4.3 联合推理与后处理

两个模型分别训练好之后,推理流程是这样的:输入一张街景图片,先过检测模型得到文本区域概率图,二值化后用连通域分析得到文本区域边界框。然后对每个边界框做非极大值抑制,去掉重叠的框。接着把每个框裁剪出来,缩放到高度32,送入识别模型得到字符序列。最后把识别结果和检测框的位置对应起来,输出最终的文本内容和位置。

后处理阶段有几个细节需要注意。第一,检测框的排序。输出结果时通常按从上到下、从左到右的顺序排列,这样更符合人的阅读习惯。第二,短边过滤。检测框的高度小于8像素或者宽度小于16像素的,识别结果通常不可靠,可以直接过滤掉。第三,置信度过滤。识别模型输出的每个字符都有置信度,整段文本的平均置信度低于0.5的可以标记为低置信度结果,供后续人工复核。

4.4 验证集构建与评估指标选择

验证集的构建要尽量贴近真实场景。我从真实街景数据里划出20%作为验证集,确保验证集里的文字类型、光照条件、拍摄角度和训练集有足够的差异。如果验证集和训练集分布太相似,评估结果会虚高,无法反映模型在真实场景下的表现。

检测阶段的评估指标用精确率、召回率和hmean。精确率是检测到的文本框中真正是文字的比例,召回率是真实文字区域被检测到的比例,hmean是两者的调和平均数。街景场景下,我通常要求hmean达到0.85以上才算可用。

识别阶段的评估指标用字符错误率(CER)和单词准确率。CER反映的是字符级别的识别精度,单词准确率反映的是整个文本区域完全识别正确的比例。街景场景下,CER低于0.1、单词准确率高于0.8算是不错的结果。

还有一个综合指标是端到端的hmean,把检测和识别串起来评估。计算方式是:检测框和真实框的IoU大于0.5且识别结果完全正确的,算作真正例;检测到但识别错误的,算作假正例;没检测到的,算作假负例。这个指标最能反映实际使用效果。

5. 常见问题与排查技巧实录

5.1 检测模型常见问题速查

问题现象可能原因排查方法解决方案
概率图全黑或全白学习率过大或标签生成错误可视化概率图和标签图对比降低学习率,检查标签生成代码
文字区域粘连严重收缩比例太小观察二值图标签的收缩效果增大收缩比例,或后处理时用更小的连通域阈值
小文字漏检严重特征金字塔尺度不够统计漏检框的尺寸分布增加更浅层的特征图,或提高输入分辨率
验证集hmean远低于训练集过拟合对比训练和验证损失曲线加数据增强,加权重衰减,早停
训练损失震荡不收敛batch size太小或学习率太大观察损失曲线的波动幅度增大batch size,降低学习率,加warmup

5.2 识别模型常见问题速查

问题现象可能原因排查方法解决方案
CTC损失变成inf输入序列长度小于标签长度检查每个样本的宽度和标签长度设置zero_infinity,或过滤过短样本
重复字符识别错误CTC的blank机制理解有误检查标签编码和blank索引确保blank索引为0,字符从1开始
验证集CER不下降学习率太小或模型容量不足观察训练集CER是否下降增大学习率,增加LSTM层数或隐藏维度
特定字体识别差训练数据字体覆盖不足统计验证集错误样本的字体分布合成数据中增加该字体的渲染样本
长文本识别截断输入宽度不够检查长文本样本的缩放后宽度增大最大宽度限制,或对长文本分块识别

5.3 几个容易踩的坑和独家经验

第一个坑是数据泄露。检测模型和识别模型共用同一批图片时,如果划分训练集和验证集时没有按图片划分,而是按文本区域划分,同一张图片的文本区域可能同时出现在训练集和验证集里。这样验证集指标会虚高很多。正确做法是按图片划分,一张图片的所有文本区域要么全在训练集,要么全在验证集。

第二个坑是字符集不一致。检测阶段不需要字符集,但识别阶段需要。如果训练时用的字符集和推理时用的字符集不一致,识别结果会完全错乱。我的做法是把字符集保存成JSON文件,训练和推理都从这个文件加载,确保一致。

第三个坑是图像预处理不一致。训练时对图片做了归一化,推理时忘了做同样的归一化,结果模型表现一落千丈。这种问题很隐蔽,因为模型不会报错,只是结果变差。解决办法是把预处理逻辑封装成一个函数,训练和推理都调用同一个函数。

第四个坑是忽略了长尾分布。街景字符里数字和英文字母出现频率远高于汉字,而汉字里常用字和生僻字的频率差异也很大。如果不做处理,模型会对高频字符过拟合,对低频字符识别很差。我的做法是在CTC损失里给不同字符加权,低频字符的权重设高一些。另一个做法是在合成数据生成时,刻意增加低频字符的采样概率。

第五个坑是验证集指标好看但实际效果差。这通常是因为验证集和真实场景的分布差异太大。解决办法是构建一个“困难验证集”,专门收集模型在真实场景中识别错误的样本,定期评估模型在这个困难集上的表现。这个困难集不参与训练,只用于评估,能更真实地反映模型的短板。

5.4 模型调优的优先级建议

当你发现模型效果不达预期时,不要盲目调参。按以下优先级排查,效率最高:

第一优先级是数据质量。检查标注是否有错误,检测框是否准确,识别标签是否和图片内容一致。数据有问题,再好的模型也训不出来。

第二优先级是数据量。如果训练数据只有几百张,先想办法扩充数据,合成数据也好,爬取公开数据也好。数据量不够的情况下,调模型结构收益很低。

第三优先级是数据增强。在数据量和质量都到位的情况下,增强策略的丰富程度直接影响模型的泛化能力。

第四优先级是模型结构。前面的都做好了,再考虑换更大的主干网络、加更深的LSTM、引入Attention机制等。

第五优先级是超参数调优。学习率、batch size、优化器选择这些,在前四条都做好的情况下,对最终效果的影响其实有限。

6. 训练加速与部署前的准备

6.1 混合精度训练与梯度累积

街景字符识别模型的训练时间通常比较长,检测模型和识别模型加起来可能要跑好几天。混合精度训练(Mixed Precision Training)是我强烈建议开启的。它把部分计算从FP32转成FP16,在保持模型精度的同时,显存占用减少约40%,训练速度提升30%到50%。

PyTorch里开启混合精度很简单,用torch.cuda.amp.autocast包裹前向计算,用GradScaler管理梯度缩放。需要注意的是,CTC损失在FP16下容易溢出,建议把CTC损失的计算放在FP32下进行。具体做法是在autocast上下文之外单独计算CTC损失。

梯度累积是另一个实用技巧。当显存不够大、batch size设不大时,可以把多个小batch的梯度累积起来,等效于一个大batch。比如你想用batch size 64但显存只够16,那就累积4步再更新一次参数。这样做的好处是训练更稳定,特别是对BatchNorm层和LSTM层。

6.2 模型剪枝与量化思路

训练好的模型如果要部署到边缘设备,体积和速度是必须考虑的问题。模型剪枝和量化是两个主要方向。

剪枝方面,DBNet的主干网络ResNet50有很多冗余通道。可以用基于L1范数的通道剪枝,把权重绝对值小的通道整条剪掉,再微调恢复精度。剪枝率控制在30%到50%之间比较安全,剪太多精度掉得厉害。

量化方面,把FP32权重转成INT8,模型体积缩小到四分之一,推理速度提升2到3倍。但量化对CTC输出的影响需要仔细评估,因为CTC对数值精度比较敏感。我的做法是只量化卷积层和全连接层,LSTM层保持FP32,这样精度损失最小。

6.3 推理管线的工程化封装

训练和验证阶段跑通了,下一步就是封装成可调用的推理接口。我的做法是把检测模型和识别模型封装成一个类,对外只暴露一个predict方法,输入是图片路径或numpy数组,输出是文本列表和对应的边界框坐标。

封装时要注意几点。第一,模型加载只做一次,不要每次推理都重新加载。第二,预处理和后处理逻辑要独立成函数,方便单元测试。第三,批处理要支持,一次传入多张图片时能并行处理。第四,异常处理要完善,图片读取失败、模型推理出错等情况要有明确的错误码和日志。

推理管线的性能瓶颈通常在检测阶段,因为检测模型要处理整张图片。优化方向包括:降低输入分辨率(但会影响小文字检测)、用TensorRT加速、把检测模型换成更轻量的主干网络。识别阶段因为输入尺寸小,通常不是瓶颈。

7. 我个人的一些实操体会

这套流程我反复跑过很多次,有几个体会比较深。

第一个体会是,街景字符识别这个任务,数据的重要性远大于模型结构。我试过用同样的DBNet加CRNN结构,在数据质量差的数据集上hmean只有0.6出头,在数据质量好的数据集上能到0.9。所以如果你刚开始做这个任务,先把精力花在数据清洗和标注校验上,别急着换模型。

第二个体会是,合成数据的域差异问题比想象中严重。我一开始用纯合成数据训练,在合成验证集上CER只有0.05,但换到真实街景图片上CER直接飙到0.3以上。后来加入真实数据微调,真实场景CER才降到0.12左右。合成数据可以用来预训练,但微调阶段必须用真实数据,这个环节省不掉。

第三个体会是,验证集的构建方式直接决定了你对模型效果的判断。我早期偷懒,验证集直接从训练集里随机抽,结果模型在验证集上表现很好,上线后一塌糊涂。后来改成按场景划分验证集,比如训练集用白天街景,验证集用夜间和雨天街景,这样评估出来的指标才有参考价值。

第四个体会是,CTC的blank机制在训练初期很容易出问题。我遇到过模型把所有字符都预测成blank的情况,损失降不下去。排查后发现是学习率太大,LSTM层的梯度爆炸了。把学习率降到原来的十分之一,加了梯度裁剪,问题就解决了。所以训练初期一定要盯着损失曲线,发现异常及时干预。

第五个体会是,后处理的阈值调优是个细活。检测阶段的二值化阈值、连通域面积阈值、识别阶段的置信度阈值,这些参数没有理论最优值,只能根据你的数据分布去试。我的做法是写一个脚本,在验证集上网格搜索这些阈值,找到hmean最高的组合。这个过程很枯燥,但对最终效果的影响很大。

最后分享一个小技巧:如果你发现模型对某些特定场景(比如反光严重的招牌)识别效果特别差,不要急着改模型结构。先把这些困难样本单独挑出来,看看它们的共同特点是什么。有时候只需要在数据增强里加一个模拟反光的变换,效果就能明显改善。模型调优的本质是让训练数据的分布尽可能覆盖真实场景的分布,数据层面的改进往往比模型层面的改进更有效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询