
1. 从“找字”到“找框”为什么我们需要EAST这样的文本检测器做图像处理或者OCR光学字符识别的朋友肯定都经历过一个阶段面对一张复杂的图片比如一张海报、一个路牌或者一个网页截图第一步不是直接识别上面的文字而是得先知道“字在哪里”。这个“找字”的过程就是文本检测。早期的方法比如基于滑动窗口的、基于连通域分析的要么慢得让人抓狂要么在背景复杂、文字方向多变、字体大小不一的情况下准确率惨不忍睹。你试过用传统方法去检测一张倾斜的商品标签吗那感觉就像用渔网去捞水里的针费力不讨好。所以当深度学习开始席卷计算机视觉领域时文本检测也迎来了它的“工业革命”。EASTEfficient and Accurate Scene Text detector就是这场革命中的一个明星选手。我第一次接触EAST是在处理一批电商平台的商品主图需要自动提取价格和商品名。传统方法在那些带有艺术字体、阴影和复杂装饰的图片上基本失灵而EAST的表现让我眼前一亮。它不像一些早期深度学习方法那样分多步进行比如先提议文本区域再精修而是用一个端到端的网络直接预测出文本的几何形状速度快精度高尤其擅长处理自然场景中的文本。简单来说EAST的核心价值在于它把文本检测这个任务从“像素级分类”或“区域提议”转变成了“几何形状回归”。它不再纠结于一个像素是不是文字的一部分而是直接告诉你文字所在的完整区域是一个什么样的形状旋转矩形或四边形。这种思路的转变带来了效率和精度的双重提升。对于需要处理大量图片、对实时性有要求或者场景文本千变万化的项目来说EAST是一个非常值得投入精力去研究和实践的模型。2. EAST模型的核心思想拆解一张图直接出文本框要理解EAST为什么高效得先看看它之前的方法是怎么做的。很多模型采用“两步走”策略例如CTPNConnectionist Text Proposal Network先检测文本行片段再把这些片段连起来还有一些方法基于Faster R-CNN之类的通用目标检测框架把文本当作一种特殊目标来检测。这些方法要么步骤繁琐后处理复杂要么对任意方向的文本支持不好。EAST的设计哲学非常直接摒弃中间步骤一步到位。它的网络结构主要分为两部分特征提取主干网络Backbone和特征融合输出头Head。2.1 主干网络Backbone的选择与特征金字塔EAST原文中使用了PVANet和ImageNet预训练的VGG16作为主干网络。在实际应用中为了平衡速度和精度我们更常见的是使用ResNet或轻量级的MobileNet。主干网络的作用是像人眼一样从原始图片中提取出多层次的特征。浅层特征包含丰富的边缘、角落细节利于定位文字边界深层特征则包含更抽象的语义信息利于判断这是不是文字区域。EAST巧妙的地方在于它构建了一个特征金字塔Feature Pyramid Network FPN的简化思想。它会将主干网络中不同深度的特征图进行上采样和合并最终形成一个融合了多尺度信息的单一特征图。这样做的好处是无论图片中的文字是大还是小模型都能在这个融合特征图上找到对应的响应。你想想看一张街景图里远处的路牌字小近处的广告牌字大如果没有多尺度特征融合模型很可能就顾此失彼了。2.2 输出头Head的双重任务得分与几何形状这是EAST最精髓的部分。对于融合特征图上的每一个像素点更准确说是每一个“像素单元”EAST的Head同时预测两样东西文本得分Score Map一个0到1之间的值代表这个像素位置是文本区域中心的可能性有多大。注意这里预测的是“文本区域”的中心区域而不是文字笔画本身。这相当于模型先对图片做了一个粗粒度的“文字热力图”。几何形状Geometry Map描述以该像素为中心的文本区域的形状。EAST支持两种几何表示旋转矩形RBOX用5个值表示分别是该像素点到文本框上、右、下、左四个边的距离4个d_i以及整个文本框的旋转角度θ。这种方式计算方便后处理简单。四边形QUAD用8个值表示即文本框四个顶点相对于该中心像素点的坐标偏移量Δx_i, Δy_i, i1,2,3,4。这种方式可以表示任意方向的四边形更灵活但后处理稍复杂。注意在实际训练时模型并不是对图片中每一个像素都做预测而是有一个“感受野”的概念。我们通常说的“每个点”是指下采样后特征图上的点。例如输入图片是512x512经过主干网络下采样32倍后特征图是16x16那么模型就是在预测这16x16256个“位置”的得分和几何形状。这种设计使得模型在推理时极其高效。前向传播一次网络直接输出了密集的预测图。剩下的工作就是通过后处理把这些密集的预测“聚合”成一个个独立的文本框。3. 实战部署从零开始搭建EAST文本检测环境理论说得再多不如动手跑一遍。这里我以Python和TensorFlow 1.xEAST原版代码基于此为例带你走通从环境准备到模型推理的全过程。虽然现在TF1.x有点“复古”但理解这个流程对掌握任何版本的实现都至关重要。3.1 环境搭建与依赖安装首先确保你的机器有Python3.6或3.7与TF1.x兼容性好以及必要的编译环境。然后安装核心依赖# 创建虚拟环境是个好习惯 conda create -n east_env python3.7 conda activate east_env # 安装TensorFlow 1.x。注意EAST原版代码通常需要1.12到1.15之间的版本。 pip install tensorflow-gpu1.15.0 # 如果你有GPU # 或者 pip install tensorflow1.15.0 # CPU版本 # 安装其他必要库 pip install opencv-python pip install shapely pip install pyclipper pip install matplotlib pip install numpy这里有几个坑点我踩过TensorFlow版本务必匹配。原版EAST代码中可能使用了tf.contrib等已在TF2.x中移除的模块直接使用TF2.x需要大量代码修改。Shapely和Pyclipper这两个库用于后处理中的几何操作比如多边形合并NMS的非极大值抑制变种。安装时如果遇到问题可以尝试先安装geosShapely的底层依赖。在Ubuntu上可以sudo apt-get install libgeos-dev在Mac上可以brew install geos。OpenCV确保安装的是opencv-python它包含了主要功能。如果需要更多功能如CUDA加速可以安装opencv-contrib-python但通常基础版就够了。3.2 获取模型与代码EAST没有官方的pip包我们需要从开源仓库获取代码。最经典的是 argman/EAST 这个GitHub仓库。git clone https://github.com/argman/EAST.git cd EAST下载预训练模型权重。原仓库可能提供了在ICDAR 2015数据集上训练的模型权重.ckpt文件你需要下载并放到项目目录的east_icdar2015_resnet_v1_50_rbox之类的文件夹中。如果链接失效可以在论文作者的项目页或一些AI模型平台如Model Zoo上搜索“EAST pretrained model ICDAR 2015”。3.3 核心代码结构与推理脚本解读克隆下来的代码库结构通常如下EAST/ ├── icdar.py # 数据加载和预处理工具ICDAR格式 ├── model.py # EAST模型网络结构定义 ├── lanms.py # 局部感知NMS算法后处理核心 ├── eval.py # 评估脚本 └── multigpu_train.py # 多GPU训练脚本如果有兴趣自己训练对于我们只想做推理检测的用户最关键的是理解model.py和如何调用lanms。但通常仓库会提供一个简单的推理脚本或者我们需要自己写一个。下面是一个高度精简和注释的推理流程核心代码import cv2 import numpy as np import tensorflow as tf from lanms import merge_quadrangle_n9 as nms_locality def load_graph(model_path): 加载训练好的模型计算图 detection_graph tf.Graph() with detection_graph.as_default(): od_graph_def tf.GraphDef() with tf.gfile.GFile(model_path, rb) as fid: serialized_graph fid.read() od_graph_def.ParseFromString(serialized_graph) tf.import_graph_def(od_graph_def, name) return detection_graph def resize_image(im, max_side_len2400): 将图像resize使得长边不超过max_side_len并保持比例 h, w, _ im.shape resize_w w resize_h h # 计算缩放比例 ratio float(max_side_len) / float(max(h, w)) if ratio 1: resize_h int(h * ratio) resize_w int(w * ratio) # 确保长宽是32的倍数网络下采样倍数为32 resize_h resize_h if resize_h % 32 0 else (resize_h // 32 - 1) * 32 resize_w resize_w if resize_w % 32 0 else (resize_w // 32 - 1) * 32 resize_h max(32, resize_h) resize_w max(32, resize_w) im cv2.resize(im, (int(resize_w), int(resize_h))) # 图像归一化并调整通道顺序为HWC - HWCM添加Batch维度 im (im / 127.5) - 1.0 im np.expand_dims(im, axis0) # 形状变为 [1, H, W, 3] return im, (resize_h, resize_w), (h, w) def detect(score_map, geo_map, score_thresh0.8, nms_thresh0.2): 从网络输出的score_map和geo_map中解析出文本框 # 1. 根据得分阈值筛选出可能是文本中心的像素位置 xy_text np.argwhere(score_map score_thresh) # [n, 2] 格式每一行是(y, x) if xy_text.size 0: return [] # 2. 获取这些位置的几何信息这里以RBOX为例 # geo_map 形状可能是 [H, W, 5] (5: d_top, d_right, d_bottom, d_left, theta) # 根据xy_text中的坐标取出对应的几何参数 # ... (此处涉及几何参数解码根据模型输出格式而定代码较长略) # 3. 解码后得到一系列候选框四边形顶点坐标 # boxes decode_by_anchor(xy_text, geo_map) # 伪代码实际解码函数需实现 # 4. 使用局部感知NMS合并重叠框 boxes nms_locality(boxes, nms_thresh) return boxes def main(): # 0. 参数配置 model_path ./east_icdar2015_resnet_v1_50_rbox/model.ckpt-49491 test_image_path ./test_img.jpg output_image_path ./result.jpg # 1. 加载模型计算图 graph load_graph(model_path) # 2. 准备输入张量根据model.py中定义的输入节点名 input_images graph.get_tensor_by_name(input_images:0) # 准备输出张量得分图和几何图 f_score graph.get_tensor_by_name(feature_fusion/score_map:0) f_geometry graph.get_tensor_by_name(feature_fusion/geometry_map:0) # 3. 读取并预处理图像 im_orig cv2.imread(test_image_path) im_resized, (rh, rw), (orig_h, orig_w) resize_image(im_orig) # 4. 运行会话进行推理 with tf.Session(graphgraph) as sess: score_map, geometry_map sess.run([f_score, f_geometry], feed_dict{input_images: im_resized}) # score_map, geometry_map 形状为 [1, H, W, C]需要 squeeze 掉 batch 维度 score_map np.squeeze(score_map) geometry_map np.squeeze(geometry_map) # 5. 后处理从网络输出中解析文本框 text_boxes detect(score_map, geometry_map) # 6. 将检测到的框坐标映射回原图尺寸并绘制 scale_h orig_h / rh scale_w orig_w / rw for box in text_boxes: # box 是四边形顶点坐标需要根据缩放比例还原 box[:, 0] * scale_w box[:, 1] * scale_h # 将浮点数转换为整数用于绘图 box box.astype(np.int32).reshape((-1, 1, 2)) cv2.polylines(im_orig, [box], True, (0, 255, 0), 2) # 7. 保存结果 cv2.imwrite(output_image_path, im_orig) print(f检测完成结果已保存至 {output_image_path}) if __name__ __main__: main()这段代码勾勒出了EAST推理的核心骨架。在实际操作中你需要根据具体下载的模型文件调整输入输出张量的名称get_tensor_by_name中的字符串并完整实现decode_by_anchor或类似的几何解码函数。原仓库的eval.py通常包含了完整的解码和后处理逻辑是最好的参考。4. 后处理详解从密集预测到干净文本框的魔法网络前向传播结束后我们得到的是两张“图”一张是每个位置是文本中心的概率图Score Map另一张是每个位置对应的几何参数图Geometry Map。如何把这些密集的、重叠的预测变成一个个独立的文本框这就是后处理的任务也是EAST实现中技术含量很高的部分。4.1 几何解码从偏移量到四边形顶点以RBOX旋转矩形为例网络对于特征图上位置(i, j)预测了5个值[d_t, d_r, d_b, d_l, θ]。这里的(i, j)是下采样后特征图上的坐标需要先映射回输入图像的坐标。假设下采样倍数是32这是VGG/ResNet常见的设置那么该点在输入图像上的粗略中心坐标为(x0, y0) (j*32, i*32)。但这还不够精确因为特征图上的一个点对应输入图像上一个32x32的区域。EAST在训练时采用了“像素投票”的思想在推理时我们需要根据预测的四个距离d_*还原出以(x0, y0)为中心的矩形的四个边。然后再根据角度θ对这个矩形进行旋转最终得到一个旋转矩形。这个旋转矩形通常用其外接四边形四个顶点来表示以便于后续的NMS处理和可视化。4.2 局部感知NMSLocality-Aware NMS这是EAST论文中的一个重要创新点。传统的NMS非极大值抑制在处理密集文本时有个致命问题它逐个比较框之间的IoU交并比如果高于阈值就抑制得分低的那个。但对于两个靠得非常近的文本行比如报纸上的两行小字它们可能IoU很高但实际上是两个独立的文本实例传统NMS会错误地抑制掉一个。局部感知NMS的聪明之处在于它先对所有的候选框进行合并然后再进行抑制。具体步骤简化如下按行合并将所有预测的四边形此时还很多很密集按照其中心点的y坐标进行排序和分组。在同一水平行内的四边形会被优先考虑合并。加权合并对于位置非常接近、方向也相似的四边形LA-NMS不是简单地丢弃一个而是根据它们的得分进行加权平均合并成一个新的、更准确的四边形。合并的权重就是它们的预测得分。迭代进行这个过程会迭代多次直到没有可以合并的四边形为止。最终阈值筛选对合并后剩下的四边形再使用一个传统的IoU阈值进行筛选去除那些仍然重叠度过高的框。这个过程就像先让“可能属于同一个文字块”的小框抱团合并形成一个大致的文本区域然后再在不同的大区域之间进行竞争淘汰。这极大地提升了对于紧凑文本、弯曲文本的检测效果。原仓库中的lanms.py就是该算法的实现虽然代码是C扩展但接口是Python的我们直接调用merge_quadrangle_n9函数即可。4.3 得分阈值与NMS阈值的调参经验在detect函数中有两个关键参数score_thresh和nms_thresh。score_thresh得分阈值控制哪些预测点被保留。值越高保留的预测点越少结果越“干净”但可能漏检一些模糊或小文字。值越低保留的预测点越多召回率可能更高但也会引入更多噪声把背景误检为文字。我的经验是对于场景干净、文字清晰的图片如扫描文档可以设高一点0.9-0.95对于自然场景复杂图片建议从0.8开始调试如果发现很多文字没检出来可以降到0.7甚至0.6试试。nms_threshNMS阈值控制框合并的激进程度。在LA-NMS中这个阈值用于判断两个框是否应该被合并。值越大如0.3合并条件越宽松更容易将邻近的文本行合并成一个框可能导致两行文字被框在一起。值越小如0.1合并条件越严格更倾向于保留独立的框。通常设置在0.2是一个不错的起点。如果你发现长文本行被断成了好几截可以适当调高如果发现独立的单词被合并成了一团就需要调低。5. 实战中的挑战与调优策略把官方Demo跑通只是第一步真正把EAST用到自己的项目里会遇到各种各样的问题。下面是我在多个实际项目中总结出的常见挑战和应对策略。5.1 小文字检测效果不佳EAST虽然有多尺度特征融合但对于图像中极小的文字高度小于20像素检测效果依然可能不理想。这是因为在多次下采样后小文字在特征图上可能只剩下一个点甚至消失。解决方案调整输入图像尺寸不要盲目地将所有图片缩放到固定大小。可以尝试先检测图片中文字的大致尺度如果小文字多就采用更大的max_side_len比如3200甚至4800减少下采样带来的信息损失。当然这会增加计算量。使用更优的主干网络尝试使用特征提取能力更强的Backbone如ResNet101或者在特征金字塔部分做文章引入更精细的特征融合策略如FPN的横向连接。数据增强在训练阶段如果你自己训练加入随机缩放、放大等增强让模型更好地学习小文字特征。5.2 复杂背景下的误检EAST在纹理复杂的背景如树叶、砖墙、织物纹理上有时会将这些纹理误判为文字。这是因为这些纹理具有与文字类似的局部规律性和边缘特征。后处理滤波可以根据检测框的一些特征进行过滤。例如过滤掉宽高比异常的框比如特别细长或特别扁平的可能是竖线或横线纹理、面积过小的框可能是噪声、内部像素平均梯度较低的框文字区域内部通常有较强的边缘变化。提升得分阈值这是最直接的方法但会牺牲召回率。使用更专业的模型如果场景固定比如都是街景可以考虑收集该场景的数据对EAST进行微调Fine-tuning让模型学习区分该场景下的文字和背景。5.3 弯曲文本与不规则文本EAST最初是为检测近似矩形的文本设计的RBOX或QUAD。对于明显的弯曲文本如圆形商标上的文字其检测框的四个顶点连成的四边形会包含大量非文字区域或者无法完整覆盖文字。使用QUAD模式QUAD模式比RBOX更灵活能更好地贴合不规则四边形的文本区域。后处理修正对于检测出的四边形可以进一步利用文本区域内的得分图Score Map或边缘信息通过分割或轮廓提取的方法得到更精确的、可能是弯曲的文本区域掩膜。但这已经超出了EAST本身的范围进入了“文本检测分割”的混合领域。考虑升级模型如果项目中弯曲文本是主要挑战应该直接考虑更先进的模型如Mask TextSpotter、PSENet或DBDifferentiable Binarization等专门为任意形状文本设计的检测器。5.4 速度与精度的权衡EAST号称高效但在高分辨率图片上速度可能仍达不到实时要求。模型轻量化将Backbone从ResNet50替换为MobileNetV2或ShuffleNet可以大幅提升速度精度略有损失。推理引擎优化将训练好的模型转换为TensorRT、OpenVINO或ONNX Runtime等优化后的推理引擎格式利用其层融合、量化等技术可以获得数倍的加速比。动态分辨率根据图片内容动态选择输入分辨率。例如先用一个轻量级网络判断图片中文字的尺度如果都是大文字就用较低分辨率推理。6. 超越原生EAST与现代OCR pipeline集成EAST本身只是一个检测器它输出的是文本框。在一个完整的OCR系统中检测之后还需要进行文本识别。现在的趋势是使用端到端的OCR模型但检测识别的两阶段pipeline因其灵活性和模块化依然被广泛使用。6.1 检测框的矫正与裁剪EAST给出的可能是旋转的四边形框而大多数识别模型如CRNN、Attention-based期望输入是水平排列的文本行图像。因此我们需要进行透视变换将四边形区域“拉直”成水平的矩形。import cv2 import numpy as np def four_point_transform(image, pts): 对图像中的四边形区域进行透视变换将其矫正为矩形。 :param image: 原始图像 :param pts: 四边形四个顶点的坐标形状为(4, 2)顺序为[左上 右上 右下 左下] :return: 矫正后的矩形图像 # 解包顶点 (tl, tr, br, bl) pts # 计算新矩形的宽度取上边和下边的最大长度 widthA np.sqrt(((br[0] - bl[0]) ** 2) ((br[1] - bl[1]) ** 2)) widthB np.sqrt(((tr[0] - tl[0]) ** 2) ((tr[1] - tl[1]) ** 2)) maxWidth max(int(widthA), int(widthB)) # 计算新矩形的高度取左边和右边的最大长度 heightA np.sqrt(((tr[0] - br[0]) ** 2) ((tr[1] - br[1]) ** 2)) heightB np.sqrt(((tl[0] - bl[0]) ** 2) ((tl[1] - bl[1]) ** 2)) maxHeight max(int(heightA), int(heightB)) # 定义目标点 dst np.array([ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtypefloat32) # 计算透视变换矩阵并应用 M cv2.getPerspectiveTransform(pts.astype(np.float32), dst) warped cv2.warpPerspective(image, M, (maxWidth, maxHeight)) return warped将EAST检测到的每个四边形box形状为[4, 2]和原始图像im_orig传入这个函数就能得到一张张摆正了的文本行小图这些图片可以直接喂给后续的识别模型。6.2 与识别模型串联一个典型的流程是EAST检测输入全图得到多个文本框坐标。框排序对于包含多个文本行的图像如文档需要按照阅读顺序通常是从左到右从上到下对检测框进行排序。可以根据框的中心点(cx, cy)坐标先按cy分组行再在每组内按cx排序。裁剪与矫正对每个排序后的框使用上面的透视变换函数裁剪出文本行图像。识别将每个文本行图像输入到CRNN、SVTR或SATRN等识别模型中得到文本内容。结果组装将识别出的文本按排序顺序组装成最终结果。6.3 使用PaddleOCR等现成工具链如果你不想从头搭建整个Pipeline强烈推荐使用百度开源的PaddleOCR。它集成了多种先进的检测模型包括DB 在弯曲文本上效果通常优于EAST和识别模型并且提供了Python API几行代码就能完成检测识别。from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch) # 使用中文模型开启方向分类 result ocr.ocr(your_image.jpg, clsTrue) for line in result: print(line)PaddleOCR的检测部分默认使用DBDifferentiable Binarization算法它在处理弯曲、遮挡、光照不均等复杂场景时鲁棒性更强且速度也很快。虽然本文聚焦EAST但在实际项目选型时DB往往是更推荐的选择。理解EAST的原理能帮助你更好地理解DB等后续模型是如何在它的基础上进行改进的。7. 模型训练与数据准备浅析虽然大多数时候我们使用预训练模型但了解如何训练EAST对于处理特定领域数据如古书籍、手写体、特殊字体至关重要。7.1 数据标注格式EAST训练需要的数据标注核心就是图片中每个文本区域的四边形顶点坐标和对应的文本内容内容用于评估训练时EAST本身不需要。通常使用ICDAR竞赛的格式ICDAR 2015格式每行8个数字表示四边形四个顶点的(x, y)坐标顺序任意最后是文本内容用引号括起来。例如x1,y1,x2,y2,x3,y3,x4,y4,text。多边形格式对于弯曲文本可能需要更多的点来描述。在训练前需要将这些顶点坐标转换为EAST需要的训练标签即对于图片中的每个文本区域在对应的特征图位置上生成Score Map的真值文本区域中心部分为1其他为0和Geometry Map的真值每个有效位置存储到文本框四边的距离和旋转角度。7.2 损失函数设计EAST的损失函数是得分损失和几何损失的加权和L L_s λ * L_g其中L_s得分损失使用Dice Loss或平衡交叉熵损失。Dice Loss特别适用于前景文本像素远少于背景像素的类别不平衡场景它直接优化预测区域和真实区域的重叠度。L_g几何损失对于RBOX使用IoU损失或平滑L1损失来回归距离和角度对于QUAD使用平滑L1损失回归顶点偏移。论文中为QUAD设计了一个尺度归一化的损失以减少大框对小框损失的主导。7.3 训练技巧与注意事项数据增强随机缩放0.5-3倍、随机裁剪、随机旋转、颜色抖动亮度、对比度、饱和度对于提升模型泛化能力至关重要。学习率策略使用带热启动Warmup的余弦退火或步进下降学习率。困难样本挖掘虽然EAST的损失函数一定程度上缓解了类别不平衡但在训练后期可以关注那些得分预测错误假阳性、假阴性的样本给它们更高的权重。预训练权重务必在ImageNet上预训练过的主干网络上进行初始化或者直接加载在SynthText等合成文本数据集上预训练好的EAST权重然后在自己的数据上进行微调。从实践角度看除非你有非常特殊且大量的标注数据否则从头训练一个EAST模型的性价比不高。更常见的做法是下载一个在大型通用文本数据集如MLT、ICDAR系列上预训练好的模型然后在自己的小规模数据上进行微调Fine-tuning这样可以快速让模型适应你的特定场景如某种特定的字体、背景或语言。