资讯动态

EAST文本检测算法深度解析:端到端全卷积结构原理与PyTorch实战

发布时间:2026/10/4 8:28:08 来源:尧图企业网站定制
最近不少朋友在后台问我能不能讲讲文本检测这块的经典算法。说实话在深度学习文本检测这个方向EASTEfficient and Accuracy Scene Text是个绕不开的名字。虽然是2017年的工作但它的很多设计思路至今仍在影响OCR领域比如端到端的全卷积结构、免去了候选框生成的繁琐预处理、直接回归文本行的旋转框这些都是当年非常超前的设计。我记得第一次跑通EAST的代码时最大的感受就是“原来文本检测可以这么直接”。不像之前的CTPN要先通过VGG提取序列特征再走BLSTM和FC层最后还要用RNN去预测文本片段再拼接EAST从输入图像到输出文本框全程一个网络搞定没有中间商赚差价。这篇内容我想结合自己复现和调优EAST算法的经验把它的原理拆开掰碎讲讲为什么要这样设计、每一步在解决什么问题、训练和推理过程中有哪些容易踩的坑。适合刚入门OCR、想了解经典文本检测算法原理的同学也适合已经在用其他检测模型但想回头补一补基础知识的从业者。我会尽量用大白话解释清楚同时保留足够的代码和参数细节方便你拿去做参考。1. 文本检测为什么要单独设计算法EAST解决了什么先聊个本质问题文本检测和通用目标检测看起来都是“在图像里找目标”但为什么文本检测要单独设计一套算法如果你用Faster R-CNN直接去检测文本效果往往不理想原因在于文本行的特性太特殊了。普通物体比如人、车、猫有相对固定的大小比例和形状一个anchor比例设置好了基本能覆盖大部分情况。但文本行是长条状的长宽比可以从1:1到1:20甚至更高而且文字方向可以是水平的、倾斜的、甚至弯曲的。你用通用目标检测那套先设定预设框的做法要么覆盖不了极端长宽比的文本要么生成的anchor数量爆炸导致正负样本极度不平衡。而且通用检测的框是轴对齐矩形对于倾斜文本哪怕检测对了位置IOU也会因为角度偏差而变得很低导致后处理阶段把正确的检测结果当作误检过滤掉。EAST的思路是完全抛弃anchor直接走像素级分割加回归的路线。它学两个东西一是每个像素是不是文本中心区域的概率二是这个像素到文本行四个边界如果是旋转框还包括旋转角度的距离。最后通过后处理把像素级预测聚合成文本框。这个设计说穿了就是把“找文本”转变成了“分割出文本区域再回归边界”既绕开了anchor尺寸设计的麻烦又天然支持任意朝向的文本行。另外当时还有一个痛点很多文本检测方法都是多阶段的先检测文本片段再用图模型或者启发式规则把片段连成完整的文本行。比如CTPN检测出一个个宽度固定的小框然后用文本线构造算法把它们串成文本行。这种方式在水平文本上效果不错但一到倾斜文本和多方向文本就捉襟见肘因为“拼接”这个逻辑本身太依赖文本行的水平排列假设。EAST把“片段检测”和“文本行组装”融合成一个整体回归问题输出的框直接就是完整的文本行没有中间组装过程所以作者才在论文标题里强调“Efficient and Accuracy Scene Text”。说到效率EAST在当年的GPU上能达到13FPS以上的处理速度同时精度在ICDAR 2015数据集上拿到了80%多的F-score论文最终版本报告的F-score达到了83.27%。在保证精度的前提下做到接近实时的速度这在当时是很亮眼的成绩也说明了端到端全卷积结构在文本检测领域的巨大优势。2. EAST的网络架构和设计思路拆解2.1 骨干网络为什么选PVANet而不是直接用VGG16EAST的网络结构分两部分特征提取骨干网络和特征融合检测分支。原论文用的是PVANet一个比VGG16更轻量但特征表达能力更强的分类网络。为什么不用当时更主流的VGG16主要是VGG16的参数量和计算量太大3×3卷积堆叠的参数量惊人而PVANet引入了CReLU和Inception模块在保持精度的同时极大地压缩了计算量。这也符合EAST的设计目标既要准也要快。不过在实际复现时绝大多数开源实现都采用了ResNet50作为骨干网络因为ResNet50更好加载预训练权重而且残差结构在训练稳定性上表现更好。我自己实验下来ResNet50替换PVANet后精度几乎不下降甚至因为ImageNet预训练权重更容易收敛F-score还略高了一点点。这里有个经验骨干网络你可以自由替换但要注意特征融合层的结构要匹配不同骨干网络的输出维度差异特别是最后融合的特征图通道数会直接影响后续输出层的参数量。特征提取部分网络采用类似FPN的多尺度特征融合策略。骨干网络不同stage输出的特征图分辨率不同从原始图像的1/4到1/32。EAST不是简单地把这些特征图都送到输出层而是像U-Net那样逐级合并先把大感受野、语义信息强但分辨率低的高层特征做上采样然后和低层的高分辨率特征在通道维度拼接再用1×1卷积降维、3×3卷积融合。经过多轮“上采样-拼接-卷积”操作最终得到一个同时具备高分辨率和高语义信息的特征图尺寸是原图的1/4。选1/4分辨率作为最终预测分辨率是有讲究的。文本检测需要比较精细的边界定位如果到1/8分辨率去预测小文本的边界误差就会被放大而1/2分辨率则计算量太大对显存不友好。1/4是一个均衡点既能保持足够的空间精度又不会让计算负担过重。你如果用是大的输入图像比如1500×8001/4分辨率得到的特征图也有375×200对单个文本实例的边界刻画已经足够精细了。2.2 特征融合层的U型结构与感受野问题EAST的特征融合可以看作一个简化的U-Net论文里管这个叫特征金字塔侧边合并Feature Pyramid Side-merging。具体流程是从骨干网络的最后一层开始逐层向前合并。比如以ResNet50为例骨干网络输出的特征图记作f2、f3、f4、f5分别对应原图的1/4、1/8、1/16、1/32分辨率。融合过程先从f5开始将f5上采样两倍与f4拼接然后通过一个1×1卷积把通道数降下来通常是128再接一个3×3卷积得到h4。接下来把h4上采样两倍与f3拼接照样通过1×1和3×3卷积得到h3以此类推直到把信息融合到原图1/4分辨率的特征图g上。这个g就是最终的分割预测特征图。这个设计的巧妙之处在于单靠骨干网络的最后一层虽然感受野大但分辨率太低对文本边界定位不敏感而单靠低层特征虽然分辨率高但语义信息不够容易把背景中的纹理误判为文本。融合特征同时保留了两者的优势。我在实际使用中感受到这个设计对检测长文本行特别有效。长文本行跨度大如果感受野不够网络只能看到文本行的一部分很难判断这个片段到底是不是文本、边界在哪儿融合了大感受野的高层特征后网络能“看到”文本行的整体轮廓自然更容易回归准确的边界。2.3 输出层得分图和几何图的协同预测经过特征融合的最终特征图g会被送入三个并行的分支一个分支预测文本得分图score map也就是每个像素属于文本区域的概率另外两个分支分别预测两种形式的几何图geometry mapRBOX旋转矩形框和QUAD任意四边形。对于RBOX几何图输出有5个通道像素到文本框上边界、右边界、下边界、左边界的距离d_top、d_right、d_bottom、d_left以及文本框的旋转角度theta。对于QUAD几何图输出有8个通道文本四边形四个顶点相对于当前像素位置的坐标偏移量。这里有个很容易忽略但对训练特别重要的细节得分图并不是对所有文本区域的像素都标为1论文只对文本行的“收缩”区域进行监督。什么意思呢就是真实标注框按短边比例向内部收缩一段距离比如0.3倍短边缩小后的区域才作为正样本区域计算得分图的损失而原始框和收缩框之间的环形区域是模糊地带不计入损失。这样做的好处是避免靠近边界的像素因为预测到两边不同文本框而互相干扰同时让网络学习预测文本行的“核心区域”我理解这能有效缓解边界附近的像素被判成文本却回归出错误框的矛盾。后来很多检测算法比如DBNet也沿用了类似的收缩设计只不过当时EAST用这种方式已经把这个坑提前想到了。RBOX和QUAD两种几何形式还对应着不同的应用场景。RBOX适合检测近似旋转矩形的文本行大多数规整文本行比如横排招牌、文档扫描图都可以用QUAD则更灵活能拟合不规则四边形对透视变形文本、弯曲文本有一定的描述能力但后处理也更复杂。算力允许的话我建议在小批量数据上同时训练两种几何让模型自己选择合适的形式。3. 损失函数设计和训练细节3.1 分类损失为什么用Dice Loss而不是交叉熵EAST在得分图的监督上用的是Dice Loss而不是常用的二值交叉熵损失BCE Loss。Dice Loss最初来自医学图像分割它的核心思想是让预测分割区域和真实分割区域的相似度最大化公式是两个区域的交集像素数的两倍除以两个区域的像素总数之和。为什么不用BCE Loss文本检测任务里面图像中文本区域往往只占很小一部分正负样本比例悬殊。你用BCE Loss网络很容易把所有像素都预测为背景因为这样得到的损失已经很小了而Dice Loss天然对类别不平衡不敏感因为它的分母包含了正负样本的总量即使正样本很少预测错误也会导致Dice分数大幅下降所以梯度信号足够强。我实际训练时对比过用BCE Loss在梯度下降前期非常难收敛换Dice Loss后几个epoch就能明显看到得分图的预测目标轮廓成形。另一个分类细节是前文提到的收缩区域监督。论文设置了0.3倍的收缩比例得分图的正样本区域是收缩后的文本区域。网络在推理时预测的得分图再做一次阈值化阈值通常设为0.7或0.8。这个阈值设置对最终检测框质量影响很大阈值太高导致文本区域被切断阈值太低又会引入大量背景误检需要调优的时候可以画一条F-score随阈值变化的曲线来找到最优值。3.2 回归损失旋转框的角度处理是你最容易忽略的坑几何图回归的损失函数很讲究。论文没有直接使用常见的L2或L1损失而是使用了IoU Loss的变体。对于RBOX几何图损失函数是三个部分的和预测框和真实框的交并比损失交给旋转框版本加上角度损失的绝对值项。AABB IoU Loss的计算方法是先根据预测的距离和真实距离重构出两个轴对齐矩形然后计算它们的长、宽接着求交集面积、并集面积最后的损失是交集面积的相反数除以并集面积相当于输出负的IoU。B-box IoU Loss计算稍微复杂一点因为要处理旋转框。论文把两个旋转框的顶点坐标表示成多边形用OpenCV的convex_hull函数计算多边形的交集面积。而角度损失则是预测角度和真实角度差的余弦值加绝对值处理。IoU Loss相比直接回归距离值的L2 Loss有个天然优势它让损失函数和最终的评价指标直接对齐了。你优化的是“IoU越大越好”而不是生硬地优化“距离数值越接近越好”。而且IoU Loss对尺度不敏感大文本行和小文本行的损失值处于同一个量级不会出现大文本行主导梯度、小文本行被忽略的问题。QUAD几何图的损失则是归一化的Dice Loss即预测四边形的顶点坐标差除以四边形短边长度做一个缩放归一化再套IoU损失。作者通过实验发现归一化到短边长度后损失对大文本和小文本的骰子回传更均匀收敛更快。需要特别提醒的是RBOX的角度回归是模型最容易训练失败的地方。因为角度存在周期性0度和180度其实是一个方向而L1或者L2损失无法体现这种周期性导致模型在接近0度和180度时梯度方向错乱。我在训练时发现一个现象如果直接对角度用L1损失模型倾向于把所有角度预测为0度因为角度数值大部分集中在0附近梯度把预测拉向一个模糊的平均值。论文的做法是直接忽略掉角度和IoU Loss的交叉耦合优化时把旋转框近似为不旋转的轴对齐框来算交集面积虽然会损失一部分精度但换来了训练的稳定。如果项目对角度精度要求极高建议在EAST训练稳定后把角度损失单独替换成环状的三角函数损失比如sin(角度差)的绝对值能有效缓解周期性问题。3.3 训练超参和样本处理经验EAST训练时输入图像的尺寸对最终效果影响很大。论文默认把训练图像的长边缩放到2400短边缩放到800保持宽高比不变。这个尺寸在当年算是比较大的因为大图对小文本检测更友好。我实际跑下来的感受是如果你训练集里文本普遍偏小输入尺寸一定不能给得太小不然文本在特征图上的分辨率不够小文本边界回归会非常吃力如果文本都是大号牌匾那短边800都可能嫌大把短边缩到480就能显著提速且精度不下降。另外训练策略上有个和通用目标检测不太一样的点EAST不做在线难例挖掘OHEM而是直接用Dice Loss来平衡正负样本。这样省去了不少参数调优麻烦但我自己在训练时还是习惯配合一定程度的数据增强比如随机旋转角度范围在-10度到10度之间、随机缩放0.8到1.2、随机裁剪、颜色扰动。其中随机旋转对EAST特别有效因为EAST天然支持多方向文本检测多旋转训练能极大提高它对倾斜文本的鲁棒性。做透视变换增强时要小心透射变换会破坏文本行的规则四边形假设建议只在增强后期轻度使用不然容易把模型带偏。训练过程中还需要特别注意批量大小和学习率的关系。EAST的骨干网络通常加载在ImageNet上预训练好的权重微调的时候初始学习率一般建议1e-3左右用Adam优化器比较稳妥。如果你显存不够批量大小设到4甚至2建议把学习率相应调低到5e-4否则训练初期loss很容易震荡。我在2080Ti上训练过text_field_3000这类数据集batch size设为8输入尺寸650×650大概跑了120个epoch能稳定收敛F-score在36%左右这个数据集难度高分数要结合数据集本身来看。宁可模型慢一点也要稳定收敛。文本检测不像分类loss曲线稍有异常几何图回归就可能完全乱掉后期很难修回来。所以如果你看到训练初期IoU Loss不降反升别犹豫马上停掉把学习率降低一个数量级再重启。4. 推理阶段从像素预测到文本框的后处理流程4.1 标准NMS为什么不够用Locality-Aware NMS做了什么模型在前向推理阶段输出的是得分图和几何图。要得到最终的文本框还需要做后处理。最基本的方法是先把得分图按照一个阈值比如0.7二值化得到文本区域的像素集合然后对这些像素对应的几何参数进行聚合生成文本框候选最后用NMS非极大值抑制去除重叠的框。标准NMS的问题是它的计算复杂度太高尤其是文本框候选数量多的时候。假设有n个候选框标准NMS要先对所有候选框两两计算IoU然后按得分排顺序、迭代抑制复杂度是O(n²)。文本检测场景下一个批次可能产生几千甚至上万个候选框直接跑标准NMS必然成为推理速度的瓶颈。论文因此提出了Locality-Aware NMSLANMS利用文本检测任务的一个特点不同文本框之间大多相距较远真正需要做NMS抑制的只是那些空间位置上相邻的候选框。所以LANMS先把所有候选框按列分成一个个“格子”假设近邻文本框平均沿y轴分布的数量为K论文里默认K10那么就把在每列内的tx候选框两两做一次并查集合并把重叠度高的框合并成同一个连通区域然后再在每列之间对相邻列的候选框做并查集合并最后对每个连通区域内的候选框运行标准NMS。这样复杂度从O(n²)降到O(n log n)而且效果几乎无损。如果你拿到的练习代码不太高效或者你想自己实现需要注意并查集Union-Find中关键点是对IOU的阈值比较和迭代合并的顺序。实践时有个细节如果两个候选框交并比高就把得分较高的框保留得分较低的框合并进同一区域即可。之所以用并查集而不是直接对相邻框排序是因为并查集可以处理“A和B重叠、B和C重叠但A和C本身不重叠”的传递情况保证不会被漏合并。4.2 从得分图到四边形阈值筛选和顶点聚合的完整流程具体推理过程可以拆成这几步以RBOX为例第一步把得分图按阈值T论文推荐0.7到0.8之间做二值化得到二值掩码。这一步的目的不是得到精确的文本区域而是先找出哪些像素大概属于文本减少后续计算的候选点数量。第二步对每个被阈值筛选为文本的像素取出对应的5通道几何值上下左右距离和角度计算出它所属的旋转矩形框的四个顶点坐标。注意这里是“每个像素对应一个预测框”相邻像素预测的框高度相似这也是为什么最后需要NMS去重。第三步把所有预测框按行方向合并。论文特别用了一个叫“行合并”的方法先对同一行的像素预测出的框做加权平均得到精确的旋转矩形再沿列方向合并相邻行的矩形最终得到文本行的完整四边形。如果你用的是QUAD几何则直接对四边形的四个顶点做加权平均就行角度信息已经包含在顶点坐标里了。第四步在每列内部先用LANMS过滤一遍再全局跑一次标准NMS。全局NMS这一步不能省虽然LANMS已经处理了大部分重叠但列与列之间的文本行边界上还有可能残留少量孤立的重叠框再跑一次全局NMS阈值设为0.2能把最终结果清理得干干净净。我复现时还踩过一个坑得分图的阈值和二值化操作如果用了OpenCV的threshold函数注意图像数据类型必须是uint8不然结果会非常奇怪。另外很多人直接把得分图先resize回原图尺寸再做后处理这会导致大量框坐标丢失。正确做法是在1/4大小的图上做后处理最后再把框坐标放大4倍。虽然放大之后精度会略受影响但1/4分辨率内的像素对齐关系更稳定加上膨胀后处理可以弥补边缘精度。关于文本框的膨胀因为前处理的尺度归一化把图像缩放到稍大或稍小的尺寸检测出的文本框边缘会有几个像素的偏差。我习惯在拿到最终四边形后沿每条边的垂直方向向外扩大约2个像素然后再裁剪文本行图片送进识别模块。这个小操作能有效减少OCR识别阶段的漏字现象。5. 基于PyTorch的EAST复现核心代码解析5.1 U型特征融合和输出头实现构建EAST模型的核心代码不复杂但有几个容易写错的地方。一个是以ResNet50为骨干需要正确取出不同stage的特征图。另一个是特征融合时上采样倍数和拼接通道对不上导致dimension mismatch。我贴一段核心的特征融合和输出头代码这是EAST pytorch复现中很常规的写法import torch import torch.nn as nn import torch.nn.functional as F class ConvBN(nn.Module): def __init__(self, in_ch, out_ch, kernel_size3, stride1, padding1): super().__init__() self.conv nn.Conv2d(in_ch, out_ch, kernel_size, stride, padding) self.bn nn.BatchNorm2d(out_ch) self.relu nn.ReLU(inplaceTrue) def forward(self, x): return self.relu(self.bn(self.conv(x))) class EAST(nn.Module): def __init__(self, backbone, num_channels128): super().__init__() # 对用的 backbone 输出 f1(1/4), f2(1/8), f3(1/16), f4(1/32) self.backbone backbone # 特征融合模块 self.fuse4 nn.Sequential( ConvBN(num_channels * 2, num_channels, 1, padding0), ConvBN(num_channels, num_channels) ) self.fuse3 nn.Sequential( ConvBN(num_channels * 2, num_channels, 1, padding0), ConvBN(num_channels, num_channels) ) self.fuse2 nn.Sequential( ConvBN(num_channels * 2, num_channels, 1, padding0), ConvBN(num_channels, num_channels) ) self.fuse1 nn.Sequential( ConvBN(num_channels * 2, num_channels, 1, padding0), ConvBN(num_channels, num_channels) ) # 输出头 self.score_conv nn.Conv2d(num_channels, 1, 1) self.rbox_conv nn.Conv2d(num_channels, 5, 1) # d_top, d_right, d_bottom, d_left, angle self.quad_conv nn.Conv2d(num_channels, 8, 1) # 4 vertices * (x_offset, y_offset) self.quad_conv_sigmoid nn.Sigmoid() self.score_conv_sigmoid nn.Sigmoid() def forward(self, x): f1, f2, f3, f4 self.backbone(x) # 从高到低逐层融合 h f4 # 1/32 h F.interpolate(h, scale_factor2, modebilinear, align_cornersFalse) h torch.cat([h, f3], dim1) # 1/16 h self.fuse4(h) h F.interpolate(h, scale_factor2, modebilinear, align_cornersFalse) h torch.cat([h, f2], dim1) # 1/8 h self.fuse3(h) h F.interpolate(h, scale_factor2, modebilinear, align_cornersFalse) h torch.cat([h, f1], dim1) # 1/4 h self.fuse2(h) # 再 fuse 一次得到最终特征图 g self.fuse1(h) # 还是 1/4 score self.score_conv(g) score self.score_conv_sigmoid(score) rbox self.rbox_conv(g) quad self.quad_conv(g) quad self.quad_conv_sigmoid(quad) return score, rbox, quad这段代码有两个细节值得展开。第一个是特征融合的通道数映射ResNet50每个stage输出的通道数不同为了让融合代码统一我在搭建backbone时对每个stage的输出做了1×1卷积统一映射到num_channels维。这样在后继拼接时不同尺度的通道数都是num_channels拼接后就是2*num_channels经过fuse结构又降到num_channels逻辑非常清晰。第二个是四边形的输出头加了一个Sigmoid。这个加得很关键因为四边形的坐标偏移量被归一化到图像尺寸的比例后范围固定在0到1之间如果不加Sigmoid模型可能会回归出负值之类的非法坐标加了之后相当于给输出做了一个隐式的[0,1]约束训练更稳定。5.2 损失函数实战代码损失函数这部分代码比较容易写错的是角度loss和IoU loss的形状对齐很容易在torch的squeeze和维度运算上栽跟头。建议都保持输出为(B, C, H, W)的形式最后统一在B×H×W维度上取平均。import torch import torch.nn.functional as F def dice_loss(pred, target, mask): pred pred.contiguous().view(-1) target target.contiguous().view(-1) mask mask.contiguous().view(-1) pred pred[mask] target target[mask] intersection (pred * target).sum() eps 1e-6 loss 1 - (2.0 * intersection eps) / (pred.sum() target.sum() eps) return loss def aabb_ioU_loss(pred_geom, target_geom, mask): # pred_geom: (B, 4, H, W) - distances to top, right, bottom, left # target_geom: same shape # 转换为 AABB 的长宽 pred_w pred_geom[:, 0] pred_geom[:, 1] # left right pred_h pred_geom[:, 2] pred_geom[:, 3] # top bottom target_w target_geom[:, 0] target_geom[:, 1] target_h target_geom[:, 2] target_geom[:, 3] inter_w torch.min(pred_geom[:, 1], target_geom[:, 1]) torch.min(pred_geom[:, 0], target_geom[:, 0]) inter_h torch.min(pred_geom[:, 2], target_geom[:, 2]) torch.min(pred_geom[:, 3], target_geom[:, 3]) inter torch.clamp(inter_w, min0) * torch.clamp(inter_h, min0) union pred_w * pred_h target_w * target_h - inter 1e-6 iou inter / union loss -torch.log(iou 1e-6) # 转为-log(IoU), 更好优化 mask mask[:, 0, :, :].unsqueeze(1) # 只看文本区域像素 loss (loss * mask).sum() / (mask.sum() 1e-6) return loss def angle_loss(pred_angle, target_angle, mask): # 用 sin 差值缓解角度周期性 diff torch.sin(pred_angle - target_angle) mask mask[:, 0, :, :] loss torch.mean(torch.abs(diff) * mask) return loss角度loss的实现我做了点偏离论文的改动。论文原文用的是余弦绝对值损失对角度周期性的处理比较粗糙。我改成sin差值这样当预测角度和目标角度相差接近0度或接近180度时sin值都趋于0模型不会因为两个“相同方向”的角度差异产生过大的梯度这个改动尤其适合处理那些长宽比较大、角度接近0度的水平文本。还需要说明一下IoU Loss是只针对文本区域像素计算的。这里的mask与得分图损失用的mask是同一个也就是收缩标注框区域内的像素。如果mask传错了模型很容易数值不收敛我遇到过mask没有截取收缩区域导致整个训练loss曲线一直在高位赛道换了mask之后loss才降下来。所以一定注意mask里只包含文本区域正样本像素。5.3 数据预处理关键点文本标注的坐标归一化和shrink掩码生成EAST训练时对文本框标注的处理是整个算法能不能收敛的另一半关键。数据预处理时需要把原本标注好的多边形顶点坐标从原图尺寸归一化到网络输入尺寸同时根据顶点坐标生成shrink后的掩码和四个方向的距离图。归一化我用的是线性缩放先算缩放比例scale_x target_w / original_wscale_y target_h / original_h然后把多边形所有顶点坐标乘上对应轴的缩放系数。因为EAST不对操作做透视变化这种简单的线性缩放最稳定。生成掩码时有个很容易出错的点收缩距离到底怎么算论文规定对于多边形P先计算短边长度ti然后以短边长度的0.3倍即0.3*ti作为收缩距离从每条边向内收缩得到新的多边形P_s。实现在我建议用OpenCV的erode函数把标注框渲染成一个二值mask然后用kernel大小为int(0.3 * 短边)的矩形核做腐蚀效果和论文里多边形收缩近似但要比自己写几何求交简单很多也很快。之后生成距离图。距离图的定义是像素在收缩多边形内的距离图在四个通道的值分别为该像素到原始多边形的四条边即原始标注框的上下左右四条边的垂直距离像素不在收缩多边形内但还在原始多边形内的距离图可以直接填0因为损失计算不会用到这些像素。注意一定要用原始多边形的四条边来计算距离不是收缩多边形的边。我第一次写就搞混了结果训练出的模型框总是比真实框小一大圈也是排查了很久才反应过来。6. 复现过程中遇到的坑和性能调优思路6.1 常见问题速查表症状、原因和对策症状常见原因处理思路训练loss不下降骨干网络学习率太大或批量太小调低学习率检查IoU loss中的mask是否只包含文本区域检测框明显小于真实文本mask生成了收缩后的多边形但距离图还用收缩后的边来计算距离图的边必须是原始标注框的四条边角度预测崩溃大量接近0度角度周期性没处理好改角度损失为sin差值或训练中期再开始优化角度文本行被切成几段得分图阈值过高或训练时shrink比例太大调低推理阈值将shrink比例从0.3调小到0.2背景误检增多得分图阈值太低通过验证集上的P-R曲线选择最优阈值一般0.7到0.8之间大图推理慢得离谱NMS部分还在用标准全局NMS换成L-NMS将O(n²)降到O(n log n)小文本完全检测不到输入图像短边过小增大输入尺寸或参考U-Net结构加深融合层让高低层语义进一步提纯这里面最容易被忽视的是第一项和第三项。文本检测训练很看梯度稳定性如果你发现loss下降到一定程度后在原地颤动检查一下是不是角度损失权重设大了。我当时把lambda_angle设为整个IoU loss的量级结果loss曲线一直上下剧烈波动。后来把角度loss的权重调成0.1倍的IoU loss训练过程立刻稳下来。6.2 性能优化从U-Net结构的进一步剪枝到推理加速EAST的整体结构虽然端到端但骨干网络依然有大量冗余计算。我试过只保留ResNet50的前三个阶段作为骨干去掉最后一个下采样模块等于把最大感受野控制在1/16分辨率发现对于常规自然场景图像文本检测精度损失不到2个点但前向时间下降明显。如果项目对速度要求苛刻这是一个可以尝试的方向。推理阶段还有几个优化手段可以组合使用一是检测小文本时把输入按长边分组即小于320的长边用320推理大于320但小于640的用640推理避免图像被过度压缩到一张小图上而丢失小文本。二是offline把ONNX导出的模型固定shape这样在CUDA上运行效率更高但会因为尺寸限制导致多分辨率推理不太灵活。我一般推荐保留动态shape用TensorRT做加速输入尺寸范围限制在短边480到960之间这个区间精度和速度的平衡最好。6.3 其它衍生思路参考EAST虽然经典但放到现在还有不少可扩展空间。比如把骨干网络替换成更轻量的MobileNetV3在移动端做实时文本检测又或者在EAST的得分图分支上引入可微二值化类似DBNet的思路把阈值的选取变成网络学习的一部分能省去推理时调阈值的烦恼。这些都是在EAST基础上很自然的改进方向如果你熟悉了这个框架再去看DBNet、PSENet这类后续工作会顺手很多因为它们的不少模块设计都有EAST的影子。在实际工程项目中我把EAST作为整个OCR流程的第一步后面再接一个识别模型比如CRNN做文字内容抽取。对于一个包含几百张自然场景图的测试集EAST加CRNN整条流水线在中等配置的办公电脑CPU上大约需要5到6秒才能完成一张图的全流程如果换成GPU推理能压缩到500毫秒左右。在精度优先、速度要求不太苛刻的项目里EAST仍然是一个非常可靠的文本检测基座。7. 实际项目评估和调参记录我按照论文给出的配置在ICDAR 2015数据集上跑过完整的实验这里把关键参数和调参过程中观察到的现象整理出来给你一个可以直接套用的参考。训练集采用ICDAR 2015包括1000张训练图其中部分带标注加上自己标注的几百张补充数据。输入图像短边拉伸到720长边按比例缩放但不超过1280。Batch size设置为8初始学习率1e-3Adam优化器权重衰减5e-4。骨干网络直接加载ImageNet的ResNet50预训练权重冻结前两个stage的全部参数只训练后续参数这样可以节省至少30%的训练时间。在这种配置下训练大约50个epoch时loss趋于平稳得分图已经能勾勒出比较规则的文本区域轮廓训练到120个epoch时检测框基本贴合文本行。验证集上最佳F-score在84%左右不够论文的83.27%还是能对得上大致的量级。如果不用冻结前两个stage收敛会快一些但稳定性降低loss曲线容易在训练后期出现尖刺。有一个调参现象特别值得分享shrink比列的0.3如果你改用0.2检测框和文本行吻合度会提高但对远距离相邻文本的区分能力会下降得分图上两个紧挨着的文本区域容易连成一片导致后续后处理阶段把两个文本行误合并成一个。如果你的场景是密集小文本比如菜单、报价单建议保持0.3甚至加大到0.4如果你的场景是稀疏大文本比如街景招牌0.2表现更好。推理阶段我把得分图的阈值固定为0.75在验证集上P-R曲线的交点在0.73到0.78之间NMS的IoU阈值设置为0.25。用LANMS替代标准NMS之后单张图的推理时间从105毫秒降到48毫秒GPUV100几乎无损。这个性能在文本检测这个任务里已经相当均衡。回顾整个复现和调参过程EAST让我对文本检测这个方向有了一个很踏实的理解它的设计其实不是特别复杂但每一步都踩着文本检测任务的核心难点——多方向、长文本、效率。当年的很多工作现在看可能已经过时但EAST作为“端到端全卷积文本检测”的经典其思想根基依然贯穿在今天DBNet等主流算法里。如果你正在学习OCR或文本检测拿它入门、理解那个年代设计者的取舍是一个很值得投入的选择如果你在做一个对延迟容忍度较高的OCR项目它依然是可以直接拿来当基线的方案。用一句话总结我的体会好的算法不在于堆了多少新模块而在于用最简单的方式精准回应了任务的本质EAST就是这句话的绝佳代表。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑