资讯动态

Python实现EAST与AdvancedEAST文本检测及CRNN+CTC识别

发布时间:2026/9/15 2:33:47 来源:尧图企业网站定制
简介一套使用Keras与TensorFlow构建的自然场景图像文字检测与识别工程核心包含EAST/AdvancedEAST文字检测模型和CRNN加CTC不定长文字识别模型。EAST采用目标检测画框方式返回文本框四个坐标能支持中文、英文、数字、符号混排以及负90度至正90度任意角度文字方向CRNN负责不定长文字序列识别通过CTC算法完成对齐输出。项目整体压缩包共32个文件其中有19个Python脚本、8张测试样例图片、3份Markdown说明文档和2个TXT配置文件代码按east与crnn两个模块清晰拆分并附带环境依赖清单总大小仅957KB。该资源已有164人学习适合作为毕业设计、课程设计、大作业或工程实训的参考项目检测与识别脚本能够直接结合样例图运行调试方便快速理解模型调用流程。对于希望从工程层面掌握文字检测识别落地方法的学习者可借助模块化代码逐步拆解EAST的文本框回归策略与CRNN的CTC序列解码思路并在此基础上进行二次开发与功能扩展。1. 图像文字识别第一步是让模型知道“字在哪”拿到一张包含营业执照、收银小票或者路牌的照片直接让模型去识别“字”结果往往惨不忍睹。因为识别模型处理的是“裁剪后的文字区域”它天生不负责搜索文字在图像中的哪一块。所以完整的OCR流水线必须先有一个文本检测模型把“字所在的多边形或矩形”找出来再把裁剪后的图像交给识别模型。这个任务就是标题里的EAST和AdvancedEAST的职责而CRNNCTC则负责把裁剪区域变成字符串。这套方案的现实价值在于EAST推理速度快能处理任意四边形文本AdvancedEAST在它基础上解决了弧度文本和长文本的召回问题CRNNCTC则是工业界落地最广泛的中文识别方案。Python让这三个模型可以快速串联成一条可调试的管线。如果你已经在接触目标检测或OCR这篇文章会给你一套从原理到参数都能直接上手的路径。2. EAST与AdvancedEAST的检测原理和Python环境搭建2.1 EAST网络结构从全卷积到Locality-Aware NMSEASTEfficient and Accurate Scene Text Detector的核心思路是用一个类似U-Net的全卷积网络直接回归文本行的得分图和几何信息。它不依赖候选框没有RPN这类重计算所以单张图像推理时延很低。常见的实现基于PVANet或ResNet作为backbone后续通过FPN逐级融合特征最后输出两个分支一个分支是每个像素是否为文本区域的score map另一个分支预测每个像素到文本矩形四条边的距离RBOX或到四边形的四个角点的相对偏移QUAD。在Python工程里使用预先训练好的模型不需要从零训练也能验证效果。但如果你需要理解模型输出尺寸记住这样一个事实EAST的输出feature map通常只有输入图像的1/4大小。假设输入图像是batch为1的[1, 3, 512, 512]输出score map是[1, 1, 128, 128]几何图是[1, 5, 128, 128]RBOX。对每一个像素如果其score高于阈值就根据预测的偏移重建出一个旋转矩形。重建完毕后再执行一个叫做Locality-Aware NMS的合并过程。它与传统NMS的区别是最终生成的文本框数量通常远小于目标检测所以底层通过按行排序和相邻框传播的方式合并避免了平方级的IoU计算。2.2 AdvancedEAST在EAST上的改进类别平衡与边界回归AdvancedEAST解决了EAST在长文本和密集排列文本上的几个问题。首先是边界回归形式不同EAST直接回归距离而AdvancedEAST回归的是像素到顶边、底边、左边、右边以及角度偏移然后重建四边形。其次它引入了两个头一个负责文本区域分割一个负责边界回归并且在loss处理上加入了dice loss与boundary loss的权重配比。更重要的是AdvancedEAST融合了VGG和U-Net结构它的输出层会对每个像素预测7个值1个文本得分、2个顶底边相对位置、4个左右及角度参数。如果你要在Python里复现需要注意它训练时对正负样本的处理文本中心区域为正样本边缘像素会计算额外权重。这样做的直接好处是在弯曲文本和倾斜文本上召回率比原始EAST高。实际项目中如果遇到文本行本身是倾斜但排列规整的情况AdvancedEAST比EAST更容易得到一个可用的四边形坐标。2.3 Python依赖安装与模型权重准备在开始写代码前先把环境准备好。深度学习框架我一般会选PyTorch因为EAST和AdvancedEAST的开源实现多数基于它调试也更直观。需要安装的包至少包括torch、torchvision、opencv-python、numpy、shapely。shapely用于计算四边形和矩形的交集面积这在NMS和评估阶段都会用到。pip install torch torchvision opencv-python numpy shapely如果是纯CPU环境请从PyTorch官网选择对应的CPU版本否则默认安装的CUDA版会在没有GPU的机器上报错或回退到极慢的CPU运算。验证安装是否成功用下面一段脚本import torch import cv2 import shapely print(torch.__version__) print(cv2.__version__) print(shapely.__version__)输出能显示三个版本号就说明环境就绪。模型权重方面你可以从常见的开源项目里下载预训练文件或者使用论文作者公开的Google Drive备份。注意不同的后端对应不同的输入尺度和输出定义下载前务必看清楚源码里的图像归一化方式否则推理结果会偏差巨大。我自己通常的做法是把权重放在项目的weights/目录下并同时保存一份对应的配置文件记录模型输入尺寸、归一化均值和标准差。3. 用Python实现文本检测的完整流程3.1 图像预处理与缩放策略文本检测的输入尺度没有唯一正确答案但有一个经验性的选择范围。如果图像中文字占比较大直接缩放到[512, 512]即可。如果图像是长宽比很悬殊的截图比如网页长截图直接等比缩放到固定尺寸会让小字完全消失。我一般会先限制最长边为1024短边相应缩放不足的部分零填充。这样做既不会丢失太多细节也保证了batch内张量形状一致。预处理代码大致如下import cv2 import numpy as np def resize_with_pad(image, target_size1024): h, w image.shape[:2] scale target_size / max(h, w) new_w, new_h int(w * scale), int(h * scale) resized cv2.resize(image, (new_w, new_h)) padded np.zeros((target_size, target_size, 3), dtypenp.uint8) padded[:new_h, :new_w] resized return padded, scale这段代码返回两个值一个是零填充后的图像另一个是缩放比例。零填充的方向固定放在右侧和底部后续坐标映射时需要把偏移量减掉。这里有一个容易踩的坑cv2.resize默认使用双线性插值在缩小文本时会出现锯齿。检测模型对锯齿不敏感但后续识别阶段对清晰度有要求所以如果同时把预处理结果用于识别建议缩小高质量场景时改用INTER_AREAresized cv2.resize(image, (new_w, new_h), interpolationcv2.INTER_AREA)3.2 模型推理和文本框解码加载模型后的推理流程可以封装成一个函数。下面以AdvancedEAST风格的输出为例模型输出一个[batch, 7, H, W]张量索引0是文本得分索引1和2是顶边和底边的相对预测索引3和4是左右边的水平坐标预测索引5和6是角度预测。def detect_text(model, image, device, score_threshold0.8): # 图像已缩放到训练尺寸并归一化 tensor torch.from_numpy(image).permute(2, 0, 1).unsqueeze(0).float().to(device) with torch.no_grad(): out model(tensor)[0].cpu().numpy() score_map out[0] geometry out[1:] # 根据score_map的像素位置和geometry重建四边形 boxes [] for y in range(score_map.shape[0]): for x in range(score_map.shape[1]): if score_map[y, x] score_threshold: continue # 从geometry恢复四边形的四个角点 top geometry[0, y, x] bottom geometry[1, y, x] left geometry[2, y, x] right geometry[3, y, x] angle geometry[4, y, x] # 这里省略角度计算细节实际代码会用cos/sin计算偏移量 boxes.append(restore_box(x, y, top, bottom, left, right, angle)) # 合并重叠框 return merge_boxes(boxes)上面的代码省去了restore_box的内部计算但它清楚了表达了解码流程开一个循环遍历每个像素对大于阈值的像素恢复出一个矩形。针对这类密集循环我建议先做阈值二值化再用np.nonzero提取候选像素数组否则纯Python循环在128×128的feature map上会慢到让人怀疑人生。改写方式ys, xs np.where(score_map score_threshold) for x, y in zip(xs, ys): # 恢复四边形 pass这样做以后推理耗时可以从几百毫秒降低到几十毫秒。注意最终得到的文本框坐标是feature map尺度的需要先乘以4或对应的采样倍数补回原图尺度再除以之前传来的缩放比例scale才能映射回原始图像坐标。3.3 可视化检测结果与坐标输出检测结果不能只存在于内存里通常要保存为可视化的图片和JSON供调试。可视化时我会用OpenCV直接画多边形注意四边形角点顺序必须一致否则形状会扭曲。输出JSON时把四个角点的x、y都转为整数因为后续传给识别模型需要矩形裁剪而整数像素坐标才能被cv2.getPerspectiveTransform正确处理。def draw_polygons(image, boxes): result image.copy() for box in boxes: pts np.array(box, dtypenp.int32).reshape(-1, 2) cv2.polylines(result, [pts], isClosedTrue, color(0, 0, 255), thickness2) return result存储到JSON时需要定义一套可追溯的格式。我通常用{ image_name: sample.jpg, text_polygons: [ {points: [[x1, y1], [x2, y2], [x3, y3], [x4, y4]], score: 0.95} ] }这里的score就是模型输出的文本得分它在后续过滤低置信度结果时非常有用。如果你看到某些文本被漏检处理思路是降低score_threshold到0.3再做一次可视化对比观察漏检框的得分区间这种情况往往是模型本身对这个样本的置信度偏低而不是后处理逻辑出错。4. CRNNCTC文字识别模型的训练与推理配置4.1 CRNN骨干网络CNNRNNCTC的串联CRNN的核心思想是先用卷积网络提取图像特征再把特征图按宽度展开成序列送入双向LSTM学习上下文关系最后通过CTC损失计算序列和标签的对齐。它的名字已经说明了结构好像是CNN提取特征然后经过RNN双向LSTM最后用CTC解码。在Python中实现时你可以把CRNN看作三部分。第一部分是一个类似VGG的CNN对输入图像进行下采样得到[batch, channel, height, width]的特征图其中height通常压缩到很小水平比如1或2。第二部分是双向LSTM输入是按宽度排列的序列每个时刻对应图像的一部分列。第三部分是线性分类头将LSTM的输出映射到字符类别数上。整个模型最终的输出是一个[batch, seq_len, num_classes]的张量其中每个时刻的预测代表当前列可能属于哪个字符。训练时输入图像的宽度不是固定的但同一batch内必须相同。所以数据加载器里要按宽高比分组把宽度接近的图片放在一个batch里。我一般用collate_fn动态找到batch内的最大宽度其余图像填充随机噪声或零。要特别注意填充对CTC损失会产生影响如果填充区域对应的目标是空白符模型会学到输出blank如果填充目标设置为其他字符会导致错误惩罚。正确做法是把填充位置的目标设为-1表示忽略该位置的loss。4.2 CTC损失函数的对齐逻辑与超参数CTC解决的核心问题是没有逐帧对齐标注只有字符串级别的转录。假设识别“hello”这几个字符LSTM输出了20个时间步CTC允许每个字符重复合并同时允许插入blank分隔相同字符。比如h h - e e - l - l - o o会先合并重复字符得到h e l l o再删除blank得到“hello”因此一条真实标签对应了很多条不同的路径。PyTorch里的torch.nn.CTCLoss要求输入是[seq_len, batch, num_classes]的log softmax输出目标序列的长度不能超过输入序列长度。关键超参数有两个blank索引和零无穷大。blank索引默认为0所以字符映射表应该把blank放在索引0之后的索引对应实际字符。zero_infinity参数设为True可以避免某些样本只有空白时产生NaN损失这对嘈杂场景的鲁棒性很有帮助。另一个容易出错的点是log_softmax。CTC损失内部需要概率的对数但torch.nn.CTCLoss并不自己做log_softmax所以你在网络输出后必须手动加这个操作import torch import torch.nn.functional as F log_probs F.log_softmax(model_out, dim-1) # [seq_len, batch, num_classes] loss ctcloss(log_probs, targets, input_lengths, target_lengths)4.3 字符集构建与数据集标注格式字符集是识别模型的输出维度基础。常见中文识别项目会构建一个包含常用汉字、数字、标点、大写英文字母的字典字符总数在5000到8000之间。字符集过大会增加模型参数量和训练难度过小则导致识别结果中频繁出现未知字符。我的做法是先从语料库中统计字频截取出现次数排前5000的汉字再加上完整ASCII可见字符最终形成dict.txt每行一个字符行号就是该字符的索引。对应地数据集标注格式通常是一行一个样本路径和标签用制表符分隔images/001.png 银行回执单 images/002.png NO. 000234训练时首先读取标签并把每个字符映射为索引列表。然后把索引列表转成torch.LongTensor。一个需要注意的问题是某些标注里含有空格。如果字符集里没有空格字符训练时这一帧的预测永远得不到正确标签会导致loss居高不下。所以我强烈建议在字符集中包含一个空格或全角空格符号这能显著改善中文文本中自然空隙带来的序列对齐问题。4.4 训练脚本关键参数与学习率调整训练CRNN的batch size受限于GPU显存。对于[1, 32, 320]的图像在12GB显存下batch size可以设为64但这是指PyTorch默认的固定尺寸情况。序列长度越长LSTM中间状态占用的显存也越多因此动态batch里对最大宽度应有上限。我一般会限制图像宽高比在10:1以内超过该比例的样本直接缩放变形或者用无缝拼接的合成数据替代。下面给出训练循环的核心片段optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones[10, 20], gamma0.1) for epoch in range(total_epochs): for batch in train_loader: inputs, labels, input_lengths, target_lengths batch logits model(inputs) # [seq_len, batch, num_classes] log_probs F.log_softmax(logits, dim-1) loss ctcloss(log_probs, labels, input_lengths, target_lengths) loss.backward() optimizer.step() scheduler.step()这里的learning_rate初始值设为1e-3在训练到10个和20个epoch时分别除以10。如果发现loss曲线在刚开始就震荡很可能是学习率太大回退到3e-4。另一个常见问题是训练到后期loss不再下降但精度也上不去。这时查看一下模型对训练集的预测结果如果训练集准确率已经很高而验证集低这就是过拟合应该增加数据增强或降低模型容量。如果训练集准确率不高那问题出在序列对齐上需要检查字符映射表和宽度归一化。5. 检测与识别串联的工程优化技巧5.1 检测框到识别区域的裁剪与透视校正检测模型输出的是四边形识别模型需要的是矩形或高度归一化的图像。直接把四边形的外接矩形裁出来会让倾斜文本中包含大量背景干扰识别。更好的是做透视校正用四边形的四个角点作为目标矩形的四个角点然后计算变换矩阵把区域拉正。def crop_and_warp(image, poly, dst_size(320, 32)): src np.array(poly, dtypenp.float32) dst np.array([[0, 0], [dst_size[0], 0], [dst_size[0], dst_size[1]], [0, dst_size[1]]], dtypenp.float32) matrix cv2.getPerspectiveTransform(src, dst) return cv2.warpPerspective(image, matrix, dst_size)dst_size的宽度和高度不一定是320和32但高度建议恒定宽度按文本串长度比例缩放。如果文本长度超过宽度字符会被压扁。解决办法是按文本框原始宽高比设定输出宽度其中高度固定为32宽度等于32 * (poly_width / poly_height)左右但还要限制最小宽度和最大宽度。5.2 提升长文本识别速度的批处理技巧OCR服务往往要处理一批图片而每张图片会有多个检测框。简单的做法是循环逐个识别但这样没有利用GPU并行能力。更快的方式是把所有检测框裁剪、缩放到统一尺寸后作为一个batch输入识别模型。由于广义文本长度不相同统一尺寸会导致很多空白填充。我通常按文本的宽度分组把宽度相近的裁剪图像放进同一个batch每组使用独立的预测宽度。这样的动态batch可以让GPU利用率保持在60%以上。在识别阶段解码使用贪心搜索或前缀束搜索。贪心直接取每个时刻最大概率字符速度最快。束搜索保持多个候选序列最后选择概率最高的。对于中文文本束宽度在10左右就能明显减少单字错误率继续增加作用不大反而拖慢速度。5.3 模型量化与导出到ONNX的验证方法生产环境里如果不想依赖PyTorch运行时可以把CRNN模型导出到ONNX。导出时有一个条件模型的输入尺寸必须固定或使用动态轴。推荐使用动态轴这样同一份模型可以适配不同宽度的文本图像。import torch.onnx dummy_input torch.randn(1, 3, 32, 320) torch.onnx.export( model, dummy_input, crnn.onnx, input_names[input], output_names[logits], dynamic_axes{input: {0: batch, 3: width}, logits: {0: batch, 1: seq_len}} )导出后用ONNX Runtime加载并比对输出误差。误差通常来自于torch.onnx.export对LSTM内部的循环解析如果误差超过1e-4优先检查是否使用了torch.nn.utils.rnn.pack_padded_sequence该操作在ONNX导出时会发生图结构变化。替代方案是直接把输入padding到固定长度并手动输入sequence length列表避免使用pack操作。验证完成后可以尝试量化对于CRNN这类模型权重从FP32降到INT8后精度损失通常小于1%但推理速度能提升2倍以上。量化适应度测试需要拿真实文本图片跑一遍不能只看loss曲线。以上从检测到识别再到串联合并的路径覆盖了工程落地的多数关键点。在你自己的项目里优先把握检测框质量和识别宽度这两个核心因素剩下的参数可以按实际数据微调。本文还有配套的精品资源点击获取

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

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

免费获取报价