基于Keras的EAST+CRNN自然场景倾斜文字检测识别实战
2026/9/15 2:33:48 网站建设 项目流程

简介:这是一套基于Python与Keras(后端TensorFlow)实现的图像文字检测与识别项目,面向希望入门OCR领域、或需要完成毕设/课程设计的学习者。项目主要包含EAST/AdvancedEAST文字检测模型与CRNN+CTC文字识别模型,EAST支持90°至-90°任意角度文字定位,能框出中文、英文、数字、符号等目标;CRNN负责不定长序列识别并通过CTC进行解码,整体覆盖从检测到识别的完整OCR流程。压缩包共32个文件,以Python源码(19个py文件)为主,另含8张用于效果验证的jpg图片、3份md说明文档以及2个txt配置文件,整体体积仅957KB,结构紧凑适合快速查看与学习。目前已有164人学习浏览。资源提供了environment.txt等环境依赖说明,方便搭建Python3.6+TensorFlow1.14+Keras2.1.6运行环境;代码模块划分清晰,可作为图像文字识别方向的入门参考,也适合在此基础上扩展训练或改进模型。

1. 基于Keras的EAST+CRNN:自然场景倾斜文字检测识别的开源实现

路边随手拍下的广告牌,识别结果往往不是乱码就是漏字,原因在于场景文本不像扫描件那样横平竖直,可能出现30度、60度甚至接近垂直的旋转。单纯套用传统OCR接口,检测框先就把文字砍掉了一半。你从开源社区拿到的这套ocr_chinese-master,其实拆开看是两条清晰的流水线:EAST模型负责把文字区域当目标检测来做,直接回归出四个角点坐标,从而覆盖90°到-90°的任意角度;CRNN接手识别,利用CTC解决不定长序列对齐问题,输出中文、英文、数字和符号的混合文本。整套代码基于Keras+TensorFlow,目录结构里eastcrnndatanetmodeltest划分得比较规整,适合想在国内做图像文字检测毕业设计的同学,也适合已有深度学习基础、希望动手串一遍检测加识别全流程的工程师。

2. EAST/AdvancedEAST文本检测:网络的输出通道与旋转框回归实现

2.1 特征融合与四边形回归:EAST的算法底色

EAST的核心思路是去掉传统文本检测中候选区域、分类、细化等中间步骤,直接用全卷积网络输出文本与非文本的得分图以及文本框的几何参数。在east/neteast/model目录下,代码先通过主干网络提取特征图,再仿照FPN把不同尺度的特征图逐级上采样并拼接,让浅层空间细节和深层语义信息同时出现在最后的预测图上。这样做的原因很直接:自然场景里的中文招牌可能很大,路面上的小字又很小,单一尺度的特征图很难兼顾两个极端。

经过特征融合之后,网络最后接卷积层输出两类信息:一个是score_map,表示每个像素属于文本区域的概率;另一个是geo_map,在EAST原版里是四个通道,表示当前像素到旋转矩形上、右、下、左边界的距离。AdvancedEAST则在这个基础上做了改动,把几何输出拆成更细的像素级分割和偏移量,同时保留多角度信息,所以在处理倾斜文本时召回率更高。原项目描述里提到文字方向支持90°到-90°任意角度,实际上就是依赖这一类四边形回归输出,而不是先做图像旋转再送入识别模型。检测阶段最终把回归出的距离转换成四个点坐标,再执行Locality-Aware NMS合并重叠框,返回给调用方。

下表是我整理出的常见输出通道含义,你在阅读east包内代码时可以用它对照每一层张量的shape。

输出通道典型shape含义
score_map1 × H/4 × W/4每个像素属于文本区域的置信度,范围0~1
geo_map4 × H/4 × W/4像素到旋转框上、右、下、左四边距离(EAST)
angle_map1 × H/4 × W/4旋转框角度信息,部分实现中并入geo_map
advanced_geo5 × H/4 × W/4文本内部分数与四个顶点偏移,用于生成任意四边形

2.2 加载模型并调用predict_east.py:参数的含义和调整

在实际运行predict_east.py时,我一般会先看一眼脚本对图片的预处理。代码通常会先对输入图做等比例缩放,把短边控制在640左右,长边控制在2560附近,然后归一化到[-1,1]区间。缩放的原因是EAST主干网络下采样4倍,输入尺寸太小会直接丢失小字文本特征,太大则占用过多显存,训练和推理时间都会显著上升。

下面这段代码可以看作predict_east.py的核心流程,按注释可以把它装到自己的推理脚本里:

import cv2 import numpy as np from east.net.model import east_network from east.data.preprocess import resize_image, normalize def run_east_detector(image_path, score_thresh=0.9, nms_thresh=0.2): original = cv2.imread(image_path) img, ratio = resize_image(original, short_size=640, long_size=2560) # 构建网络并加载权重,这里用east/model中的结构 score_map, geo_map = east_network(training=False) model.load_weights('east/weights/east_model.h5') # 归一化后增加batch维度,输入网络 logits = model.predict( np.expand_dims(normalize(img), axis=0) ) # 后处理:阈值过滤背景,NMS合并重叠框 boxes = lanms( score_map[0], geo_map[0], score_thresh=score_thresh, nms_thresh=nms_thresh ) # 还原到原图坐标 return boxes / ratio

这段代码里的score_threshnms_thresh是我调参时最关心的两个值。score_thresh控制文本区域置信度门槛,设置太高容易把低对比度文字框漏掉,设置太低又会把水泥纹理、树叶阴影误判成文本;nms_thresh则控制重叠旋转框的合并力度,值偏大时相邻的小框会被激进合并,可能把两行文字框成一个框,值偏小时同一行文字会被切碎。处理横幅或路牌这类大目标时,我一般会把score_thresh放在0.85到0.92之间,而处理密集小字时降到0.75左右,这个取舍要结合后续识别效果来回调。

3. CRNN+CTC文字识别:从卷积特征到不定长文本序列

3.1 CNN特征序列化和双向RNN:为什么CRNN能处理不定长

检测模型只负责把文字区域框出来,真正的文字内容还要交给识别模型。这套项目里的CRNN,不是普通的CNN分类网络,而是把卷积特征、循环网络和CTC转录组合在一起,形成一个能输出变长结果的序列识别器。第一层仍是卷积层,作用是提取图像中的视觉特征,得到一张高度固定、宽度可变的特征图。接着把特征图按列切分,每一列作为一个时间步送入双向LSTM,这样网络就能学习文字之间的上下文依赖,比如“北京”后面的“市”会比单独识别时更可靠。

CTC在这里解决的是对齐问题。一张包含5个字符的图片,经过卷积和LSTM后可能有15到20个时间步,比真实字符数多得多。CTC会为每个时间步输出一个字符概率分布,然后通过引入空白符号,把连续重复的字符折叠成一个,并去掉空白位置,最终得到一条字符序列。因为这个转录过程允许网络输出序列长度大于标签长度,所以识别模型不需要提前切割单字,也不需要固定输入宽度,这正是这套CRNN实现能够处理不定长中文文本的原因。

crnn目录下同样有netmodeltest子目录,predict_crnn.py的推理逻辑可以压缩成下面这段:

from crnn.net.model import crnn_network from crnn.utils import decode_ctc # 识别前把检测到的文本框裁剪出来,并缩放到固定高度 image = cv2.imread('crop.jpg') h, w = 32, int(image.shape[1] * (32 / image.shape[0])) # 宽度对齐到4的整数倍,避免卷积下采样后出现奇偶错位 w = (w // 4) * 4 image = cv2.resize(image, (w, h)) # 将输入转成(W,H,C)布局,对应CRNN的序列化输入 input_seq = np.transpose( image[np.newaxis, :, :, :], (0, 2, 1, 3) ) model = crnn_network(training=False) model.load_weights('crnn/weights/crnn_ctc.h5') logits = model.predict(input_seq) # CTC解码,beam_width=1表示贪心,更大值用beam search text = decode_ctc(logits, alphabet, beam_width=1) print(text)

逻辑上这段代码把输入图像的高度固定为32,宽度跟随原始宽高比变化并取整到4的倍数,之后送入CRNN网络得到每个时间步的字符概率。最后一步的decode_ctc会根据字符表alphabet把概率序列转换成最终文本。如果你要在自己的数据集上重新训练,alphabet必须重新生成并和训练时保持一致,否则解码出来会是乱码。

3.2 CTC解码与工程调用:predict_crnn.py中的参数

decode_ctc里的参数直接影响识别耗时和准确率。下表是几个我实际调过的基础参数:

参数默认值实际建议
beam_width1句长较短时设为1即可;长文本设5~10提升准确率,但耗时线性增加
blank_index0必须和模型训练时blank字符索引一致,常见为0
alphabet自定义字符表需要覆盖中文常用字、英文大小写、数字,以及空格等特殊符号
merge_repeatedTrueCTC默认合并重复字符,关闭后连续相同字会被拆开

工程上最容易踩的坑是alphabet的顺序变动。很多人在模型训完后才追加新字符,结果测试时decode出来的索引全部错位。另一个坑是输入宽度没有对齐,导致CNN特征序列长度与RNN期望长度不一致,报错维度不匹配。看到这类报错时,优先检查w = (w // 4) * 4这步是否生效,因为卷积下采样倍数必须整除输入宽度。

4. 端到端串联:predict.py流程梳理与Python环境配置实测

4.1 从检测框到识别图:图像裁剪与透视变换

如果你打开项目根目录下的predict.py,会看到它把检测和识别两个阶段串成了同一条流水线。流程并不复杂:读图后先交给EAST拿到文本框四个角点,然后根据角点做透视变换,把旋转、倾斜的四边形区域拉正成水平方向,最后把矫正后的截图送给CRNN识别。关键步骤是透视变换矩阵的计算,常见做法是用cv2.getPerspectiveTransform取四个顶点映射到正矩形,再执行cv2.warpPerspective。这里要注意四个角点的顺序必须一致,通常按左上、右上、右下、左下的顺序排列,否则矫正出来的图像是旋转了90度的,CRNN会把它当成竖排文字处理,识别准确率会大幅下降。

asset目录下放了两张测试图23_re.jpg51_re.jpg,对应检测识别后的结果图23_0_.jpg51_0_.jpg。你可以直接在根目录执行:

python predict.py --image asset/23_re.jpg

脚本将依次输出检测框数量和识别文本。如果只向控制台输出文本,我建议自己在predict.py里加一行cv2.imwrite,把透视矫正后的图像存下来,这样能快速确认检测框是否把文字完整包住。

4.2 environment.txt依赖安装与版本兼容性

项目开发环境写的是Python 3.6、TensorFlow 1.14.0、Keras 2.1.6,environment.txt里也给出了可复现的依赖清单。我实际安装时习惯先创建独立虚拟环境,防止把系统里的TensorFlow 2.x环境搅乱。

python -m venv ocr_env source ocr_env/bin/activate pip install -r environment.txt

如果你的机器已经装过OpenCV,注意它可能会自动升级numpy,而Keras 2.1.6对numpy版本有隐性要求。下面是我整理出的依赖兼容性要点:

依赖版本建议踩坑点
Python3.6.x3.8以上与TensorFlow 1.14的whl包不兼容
TensorFlow1.14.0不要换成2.x,Keras 2.1.6调用tf接口会报错
Keras2.1.6需要设置KERAS_BACKEND=tensorflow
opencv-python4.1.x新版大量依赖可能与numpy版本冲突
pillow6.x过高版本在Python 3.6下可能安装失败

环境配置是最消耗耐心的部分。如果启动时出现AttributeError: module 'tensorflow' has no attribute 'placeholder',多半是Keras调用到了TensorFlow 2.x的API,检查一下虚拟环境里实际安装的TensorFlow版本,必要时卸载重装为1.14.0。如果加载h5权重时报结构不匹配,先确认Keras版本,因为不同版本对权重键名记录方式不一样。

5. 进阶:角度文本调参、验证方法与常见坑点

5.1 score阈值与NMS参数对旋转框的影响

之前提到score_threshnms_thresh是检测阶段两个关键参数,实际测试中它们对最终结果的影响往往是牵一发动全身。score_thresh太高时,低对比度的白色字体会被直接过滤掉;太低时,复杂背景会产生大量虚框。我先用score_thresh=0.9跑一遍测试图,如果某张图掉了文字,再以0.05的步长往下调,同时观察NMS是否把相邻文本块合并。nms_thresh则负责控制框的重叠度,对弯折的灯箱文字,我一般从0.2起步,调低到0.1会让同一行文字被切分成多个框,调高到0.3以上又可能把上下两行并成一个误检框,所以最好结合检测框的可视化结果去判断。

5.2 用自带测试图验证全流程

项目自带的asset目录里有多张测试图,其中res.jpg可能是一张已经合并检测和识别结果的可视化图,23_re.jpg51_re.jpg则是输入。验证时我建议按这个顺序做:先只跑predict_east.py,把检测框画到图上保存下来,确认每个文本框的角点是否紧贴文字边缘;再切出其中一个框,单独跑predict_crnn.py,确认识别结果和字符表没有错位;最后跑完整predict.py,对比输出文本和原图内容。如果检测框正确但识别结果是空的,多半是透视矫正时高度设置不合理,CRNN要求高度固定为32,矫正后图像宽高比极端时会丢失横向特征。遇到这类问题,可以在送入识别前对矫正图做一次高保持缩放,而不是直接把原裁剪图塞进模型。另外,environment.txt没有列出的lanms编译依赖也需要提前装好,否则EAST后处理阶段会报缺少Cython扩展,这个错误经常让初学者误以为是TensorFlow版本问题。

本文还有配套的精品资源,点击获取

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

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

立即咨询