简介:一套面向自然场景文字检测与端到端中文OCR识别的毕业设计资源,基于TensorFlow与Keras、PyTorch框架实现,完整覆盖文字方向检测、文本区域检测和端到端文字识别三个核心网络。方向检测使用VGG16分类模型,利用八千张训练图片,达到约百分之八十八点二三的准确率;文本区域检测采用CTPN,支持CPU与GPU环境一键部署;端到端识别采用CRNN结构,通过GRU加CTC实现对不定长中文的识别,并提供Keras与PyTorch两个版本。资源包共包含两百三十七个文件,以Python脚本为主,涵盖模型定义、训练与推理逻辑,另有shell脚本用于Ubuntu环境构建,jpg与png图片作为测试样例,pth文件存放模型权重,以及少量C与CUDA源码用于加速模块编译,整体压缩包约六十二点七一兆字节。当前已有六百九十五人学习或下载,适合计算机视觉、OCR方向的学生作为毕业设计或项目实战参考。运行demo.py或pytorch_demo.py并设置测试图片路径,即可直观得到文本检测框与OCR识别结果,帮助理解从文本检测到端到端识别的完整流程。
1. 自然场景文字检测与端到端OCR:这个毕业设计题拆开看是什么、能解决什么、适合谁
你拿手机对着街边店铺拍一张照片,招牌、菜单、快递单上的字往往是歪的、反光的,背景还带着霓虹灯和绿化带。传统文档扫描软件遇到这种图,经常一个字符都认不出来。这个题目要解决的就是一件事:从自然场景照片里先找到文字区域,再把区域里的中文内容还原成计算机里的可编辑文本。所谓“端到端OCR”,指的不是某一个模型把所有事情包办,而是检测、识别、排序、输出这一整条链路能一次跑通。这套流程完全可以在本地离线运行,不需要把图片传去云端识别,数据也能留在自己手里。如果你准备做计算机视觉方向的毕业设计,或者想做一个本地文字识别工具,这个方向都很合适:它同时覆盖目标检测、序列标注、中文语言模型三个经典课题,工作量也刚好够一个学期。
2. 检测与识别分开做:为什么要拆成两步,以及三个框架怎么分工
自然场景里的文字不像文档那样规整地横在纸上,它可能旋转、弯曲、被栏杆挡住,也可能是一个竖排门店招牌。把所有问题交给一个模型去做,对训练数据量和模型容量要求都很高,普通毕业设计很难攒出足够的数据。我一般会把流程拆成两个子任务:先做文字检测,再做文字识别。检测负责回答问题“文字在哪”,输出一系列四边形框;识别负责回答问题“框里是什么字”,把裁剪出来的区域转成字符串。两个子任务分开训练、分开调参,排查问题也容易得多。
2.1 用DB或EAST做文字检测:模型选型与最小训练配置
自然场景文字检测常见的有两条路线:一条是EAST为代表的直接回归文本框顶点,另一条是DB(Differentiable Binarization)为代表的语义分割加阈值后处理。EAST思路直观,但处理长文本和弯曲文本时框不贴合;DB通过可微二值化把分割结果转成框,对倾斜和尺度变化更稳,所以现在多数开源项目都会选DB作为默认检测器。这个压缩包如果按“毕业设计”的常见做法整理,大概率也是把DB或EAST的PyTorch实现放在最前面。
我一般会从这种配置开始调:
model: name: db_resnet50 pretrained: true seg_channels: 256 data: input_size: [640, 640] batch_size: 8 num_workers: 4 train: epochs: 50 base_lr: 0.001 optimizer: adamw lr_schedule: warmup+cosine warmup_epochs: 3 post_process: thresh: 0.3 box_thresh: 0.5 max_candidates: 1000这里的input_size决定图像缩放后输入网络的尺寸,640x640是速度和精度比较平衡的点。thresh是分割图二值化阈值,box_thresh是最终候选框的置信度阈值。如果图里有大量小字招牌,我会把输入尺寸提高到736,同时降低box_thresh到0.4,让检测器不要漏框。训练轮次一般40到60轮就够了,再多容易在真实场景上过拟合。
2.2 用CRNN+CTC做中文识别:为什么中文场景不能照搬英文Pipeline
检测框拿到以后,下一个问题是识别框内文字。英文OCR类别只有26个字母加数字,中文却常用几千个汉字,加上标点和生僻字,输出类别轻松过万。如果直接把英文OCR那套“每帧预测一个字符”的模型搬过来,训练集里每个字的样本量都会变得很稀疏,模型很容易记住背景纹理而不是字形。
常用做法是CRNN+CTC:卷积层提取图像特征,循环层建模序列,CTC损失解决“每列字符没对齐”的问题。中文识别训练前需要先把字典文件准备好,按行放常用汉字、数字和标点,然后给每个字一个固定索引。我见过不少项目在训练集里只有两三百个不同汉字,测试一多就狂出乱码。正常的中文识别模型至少要覆盖一级常用字表,大约3500个字符,如果针对快递单、营业执照这类场景,再额外加入数字和单位字符。
CTC loss 里有一个关键参数叫blank,它表示“当前这一列没有字符”,通常被默认放在字典索引0的位置。很多中文识别复现把我常用的最后一类加在字典末尾,结果CTCDecode出来的结果整体错位,这就是初期最容易踩的坑。字典顺序一旦确定,整个训练过程就不要随意变更,否则之前训练的模型权重全部作废。
2.3 TensorFlow、Keras、PyTorch怎么选:训练、导出、推理的最佳分工
这个标题同时出现了TensorFlow、Keras和PyTorch,第一次接触的人容易以为必须选一个用到死。实际做这类项目,三个框架很少是全等替换关系,更多是各管一段。
| 环节 | 推荐框架 | 理由 |
|---|---|---|
| 检测模型训练 | PyTorch | 开源复现多,DB/EAST/PAN这些都先出PyTorch版 |
| 识别模型训练 | PyTorch 或 tf.keras | CRNN实现都成熟,主要看团队已有代码 |
| 模型导出部署 | TensorFlow/Keras + ONNX | 边缘设备和Web端对TFServing/ONNX Runtime支持好 |
| 快速验证脚本 | PyTorch | 模型定义和调试循环直观,断点下进去就能看张量 |
PyTorch 环境搭建是很多人刚开始最头疼的一步,尤其是要在 Windows WSL 里配 GPU 环境时。我一般会先用 Anaconda 建独立环境,把 PyTorch、CUDA、cuDNN 以及 OpenCV 全部装进同一个环境,避免污染系统Python。Keras侧则要特别注意:TensorFlow 2.x 里推荐用tf.keras,而旧的独立keras包和tensorflow.keras混用时,模型保存结构和反序列化经常对不上。如果一个项目源码里写的是from keras import ...,跑之前先确认它对应的后端是哪个TF版本,否则一训练就报AttributeError。
3. 把项目跑起来:环境搭建、数据准备和训练命令
拿到压缩包以后,第一件事不是马上打开训练脚本,而是先把环境彻底跑通。很多毕业设计代码写到一半没问题,一换机器就翻车,大部分原因都出在库版本冲突和数据集路径硬编码上。下面我会按“环境安装→数据准备→启动训练”的顺序给出一套能直接照抄的流程,同时也把坑的位置标出来。
3.1 用Anaconda搭PyTorch环境:CPU、GPU、WSL三种情况的安装边界
一个干净的环境能省掉后面90%的报错时间。用 Anaconda 创建独立环境时,我不建议直接pip install torch,而是先从官方渠道安装对应CUDA版本:
conda create -n ocr python=3.9 -y conda activate ocr # GPU环境:先确认自己的CUDA版本再选cudatoolkit conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia # CPU环境:机器没有NVIDIA显卡时用这条 # conda install pytorch torchvision torchaudio cpuonly -c pytorch # OCR依赖 pip install opencv-python shapely pyclipper polygon numpy scipy这段命令里,pytorch-cuda=11.8表示 PyTorch 使用的 CUDA 运行时版本,并不要求系统全局 CUDA 也是11.8,因为 PyTorch 会自动携带对应库。CPU版适合先跑通逻辑,但训练检测模型基本跑不动,50轮可能要几天。插一句,如果在 WSL2 里装环境,宿主 Windows 的 NVIDIA 驱动会让 WSL 直接共享 GPU,你只需要在 WSL 里装 CUDA toolkit,不需要再装一遍驱动。装完后用python -c "import torch; print(torch.cuda.is_available())"验证。
3.2 中文OCR数据集的准备与标注:不要只拿ICDAR15就开训
自然场景中文OCR最大的门槛不是模型,而是数据。ICDAR15 主要是英文场景,直接拿来训练中文识别,模型只会记住英文字母形状。常见做法是“真实数据+合成数据”混合:真实数据标注少量精修,合成数据用渲染工具批量生成。检测标注格式一般是这样的:每张图片对应一个txt文件,每行是一个四边形的四个点坐标和文字内容。
# 生成一批合成中文字符图,用来预训练识别模型 import os import random from PIL import Image, ImageDraw, ImageFont font_path = "./simhei.ttf" # 换成你本机中文字体路径 out_dir = "./synth_chars" os.makedirs(out_dir, exist_ok=True) char_set = "的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应放它甚外想只`此前" font = ImageFont.truetype(font_path, 48) for i, ch in enumerate(char_set): img = Image.new("RGB", (64, 64), (random.randint(200, 255),) * 3) draw = ImageDraw.Draw(img) draw.text((8, 4), ch, fill=(30, 30, 30), font=font) # 随机旋转小幅角度,让模型更接近自然场景 img = img.rotate(random.uniform(-15, 15), expand=True, fillcolor=(255, 255, 255)) img.save(os.path.join(out_dir, f"{i:05d}_{ch}.jpg"))这段代码的逻辑是:把每个常用汉字分别渲染成单字图片,随机加一点旋转和灰度背景。random.randint(200,255)控制的是背景亮度,img.rotate的expand=True会扩大画布,避免旋转后文字被裁掉。合成数据不需要很逼真,重点是把字形变体喂给识别模型,让模型先学会“这是一个字”,后面再用真实场景微调。
3.3 训练与验证的完整命令:从原生PyTorch到Keras适配
训练脚本的入口大同小异。如果是 PyTorch 原生实现,我通常会在项目目录下放一个train_det.py和train_rec.py,分别训练检测与识别。由于这类源码经常来自不同的开源仓库,路径和参数名都不完全一致,拿到压缩包后先执行以下命令检查结构:
# 查看项目是否有依赖文件缺失 python -c "import torch, cv2, numpy; print('ok')" # 运行官方自带的单张图片推理脚本,确认权重能加载 python demo.py --image ./test.jpg如果 demo 能跑通,说明整个框架和权重是配套的。接下来训练时,最重要的超参数无非是学习率、batch size和输入尺寸。batch size 受显存限制,8G卡建议检测模型设为4,识别模型可以到16;学习率用AdamW时从1e-4起步,每10轮乘以0.5。损失曲线如果前5轮不下降,先检查训练数据和标签是否对应,不要急于调学习率,这个经验我反复踩过好多次。
4. 端到端推理与效果验证:把检测框接进识别器
训练完检测和识别两个模型以后,最关键的是把两者串成一个完整脚本。这一步不能省,否则你交出去的还是一个一个孤立的模型。推理时要注意图片缩放、四边形归一化、识别器输入高度等细节,任何一个环节没对齐,最终输出的文字都会差得离谱。
4.1 推理Pipeline:检测框裁剪、角度矫正和分桶
检测模型输出的通常不是标准矩形,而是任意角度的四边形。识别模型大多接受水平方向的矩形图,所以拿到四边形后需要先做透视矫正。这里给一个推理Pipeline的简化版本:
import cv2 import numpy as np def detect_and_recognize(image_path, detector, recognizer): image = cv2.imread(image_path) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) boxes = detector.predict(image) # shape: (N, 4, 2) results = [] for box in boxes: box = np.array(box, dtype=np.float32) rect = cv2.minAreaRect(box) # 得到中心、宽高、旋转角 center, (w, h), angle = rect # 长边为宽:保证最终裁剪图是水平方向 if h > w: angle -= 90 w, h = h, w # 旋转图像让框水平 M = cv2.getRotationMatrix2D(center, angle, 1.0) rotated = cv2.warpAffine(image, M, (image.shape[1], image.shape[0])) # 从旋转图里把矩形区域抠出来 x = int(center[0] - w / 2) y = int(center[1] - h / 2) crop = rotated[max(0, y):max(0, y + int(h)), max(0, x):max(0, x + int(w))] # 统一高度后交给识别器,宽高比过大可再拆列 crop = cv2.resize(crop, (int(32 * crop.shape[1] / crop.shape[0]), 32)) text = recognizer.predict(crop) results.append((box, text)) return results这段代码核心在cv2.minAreaRect,它能把任意四边形包成最小外接矩形,然后通过旋转矩阵把矩形掰正。angle -= 90是对竖排文字的第一次矫正,后续还会按阅读顺序排布。需要注意,recognizer.predict一般要求输入固定高度,宽度按比例缩放,常见的CRNN高度是32。如果框太宽,可以按固定片宽切分,否则识别器会对长横幅招牌漏字符。
4.2 常用OCR指标:用编辑距离和检测IoU量化“毕业设计能不能过”
很多同学训练完只看一眼可视化结果,感觉“差不多了”就交差,答辩的时候一旦换测试图就崩。我建议至少用两个定量指标评估自己的系统:检测用 IoU,识别用编辑距离,最后合并成端到端准确率。IoU 计算检测框和标注框的重合程度;编辑距离计算识别字符串与标准答案之间需要多少次增删改。
def edit_distance(s1, s2): m, n = len(s1), len(s2) dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(m + 1): dp[i][0] = i for j in range(n + 1): dp[0][j] = j for i in range(1, m + 1): for j in range(1, n + 1): if s1[i - 1] == s2[j - 1]: dp[i][j] = dp[i - 1][j - 1] else: dp[i][j] = 1 + min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) return dp[m][n] def char_accuracy(pred, gt): return 1 - edit_distance(pred, gt) / max(len(gt), 1)这个算法是标准的动态规划,dp[i][j]表示第一个字符串前 i 个字符和第二个字符串前 j 个字符之间的编辑距离。毕业设计里常用的合格线是:检测IoU大于0.5的比例达到85%以上,端到端整行准确率达到70%以上。如果只报“识别准确率95%”,很可能是拿单字裁剪测出来的,这不能反映自然场景里的真实水平。
4.3 竖排文字与纵向阅读顺序:一个容易被忽视的调参点
中文场景和英文场景最明显的差别之一就是竖排。英文很少竖向排版,中文却常见门店竖招牌、菜单竖列、书脊书名。检测模型一般能把竖排框检测成“高大于宽”的矩形,但识别器默认按水平方向处理,这时就一定要把裁剪图转90度再识别。我在4.1里做了angle -= 90,这一步必须配套“转回来之后文字不是倒的”的判断,否则很容易把字识别成旋转180度后的乱码。
竖排的另一个问题是输出顺序。即使每块文字都识别对了,横幅和竖牌混合在同一张照片里时,按坐标排序也会乱。常见做法是先按文字块中心y坐标粗略聚类为行,然后再按x坐标排序。对于同时出现的横排与竖排文字,我一般会在检测结果里加一个字段记录框的宽高比,宽高比大于1的按行从左到右读,小于1的按列从上到下读。这个设计看似微小,却是影响端到端可读性的关键点。
5. 避坑指南:5个让检测识别全链路崩溃的常见问题
5.1 安装阶段:TensorFlow和Keras版本不匹配,import keras就报错
现象:运行训练脚本时,import keras直接抛出AttributeError: module 'tensorflow' has no attribute 'get_default_graph',或者模型保存后无法加载。原因:TensorFlow 2.x 以后,独立 Keras 和tf.keras是两个不同体系,很多旧源码还是from keras.layers import Conv2D,但又用 TensorFlow 2.x 作为后端。解决:统一改成from tensorflow.keras.layers import Conv2D;如果整个项目都是旧的 Keras 写法,就安装 TensorFlow 1.15 配合keras 2.2,千万不要装最新版 TensorFlow 去跑旧 Keras 代码。
5.2 数据标注阶段:中文标签文件编码不对,读出来全乱码
现象:检测训练损失正常下降,但验证时发现所有预测框的文字内容都是“锟斤拷”。原因:中文标签txt文件可能是 GBK 或 ANSI 编码,而 Python 用 UTF-8 读取,导致中文标签解析错位。解决:用文本编辑器批量转成 UTF-8 无 BOM 格式;在读取代码里显式指定encoding='utf-8'。更隐蔽的情况是字典文件里混入全角空格和换行符,会让识别结果多出奇怪的空白字,必须在构建字典时统一清洗。
5.3 训练阶段:检测后处理阈值一错,模型效果瞬间崩盘
现象:detect部分训练到20轮时,loss很低但可视化检测框歪七扭八,高度重叠或漏掉小字。原因:thresh和box_thresh两个阈值设置不合理。分割图输出是一个概率图,thresh太低会产生大量背景噪点,box_thresh太高又会滤掉真实的小文字框。解决:分阶段调参,先不动模型权重,只对同一张测试图调整thresh,观察分割热力图变化;然后用验证集跑一次IoU曲线,选取 IoU 最高的阈值组合。这件事很玄学,但一旦确定就不要频繁改动。
5.4 推理阶段:白色亮底图识别好,换深色底图就什么也识别不出来
现象:训练时很多自然场景样本都是白底黑字,测试时遇到黑底白字的招牌,检测框找得到,识别结果却全是空字符串。原因:识别模型会对灰度分布很敏感,相当于把背景和文字颜色当成了特征。解决:训练识别器时做像素反转增强,以50%概率255 - img;推理时也可以先检测图像平均亮度,如果深色背景占主导就做一次反转,再送进识别器。
5.5 显存与WSL环境坑:训练到一半OOM,或者GPU不可见
现象:明明装了GPU版PyTorch,但在 WSL 里运行torch.cuda.is_available()返回 False,或者在训练第3个epoch时触发CUDA out of memory。原因:WSL 里看到的是/dev/nvidia0设备,但缺少必要的 CUDA 用户态驱动;OOM 则通常是因为 batch size 设太大,或者验证阶段积累了很多计算图没有释放。解决:先用nvidia-smi确认驱动正常,再检查torch.cuda.get_device_name(0);显存不够就把检测模型输入尺寸从640降到512,batch size降到2。如果仍然OOM,检查代码里是否每个step都调用了loss.backward(),以及是否保存了不该保存的中间变量。
6. 给一个能直接用的验证技巧:用固定测试集和错误样本回放,告别黑匣子调参
最后一章我想分享一个让我少熬夜的习惯:把验证从“肉眼看好不好”变成“直接看失败样本”。很多模型看起来准确率还行,但一落地就发现是训练集过拟合。要保证测试稳定,我会单独建立一个test_fixed/文件夹,里面放大约20张来自不同场景的照片,比如路牌、菜单、快递单、手机截图、竖排门店牌。每次调完参数,对整个文件夹跑一遍,并自动把预测结果和错误的原图保存下来。
import glob import os error_dir = "./error_cases" os.makedirs(error_dir, exist_ok=True) for img_path in glob.glob("./test_fixed/*.jpg"): pred_text = run_ocr(img_path) gt_text = read_gt(img_path) if pred_text.strip() != gt_text.strip(): # 把预测结果和检测框一起画回原图 img = draw_result(img_path, pred_text) error_name = os.path.basename(img_path) + f"_{pred_text}.jpg" cv2.imwrite(os.path.join(error_dir, error_name), img)这段脚本的逻辑是把每个识别错误的样本连预测文本一起存档,这样你晚上休息前瞄一眼错误文件夹,就知道当前系统最怕什么。以前我调试OCR就只看loss曲线,觉得loss降了必然变好,后来发现检测框偏移和识别器输入尺寸不匹配这类问题根本不会反映在训练loss上。这个错误回放习惯帮我解决过好几个“全链路单独看都对、串起来就错”的老大难问题。
如果你准备把这个毕业设计继续做成一个能给别人用的工具,我建议再往前推一步:把检测模型导出成 ONNX,识别模型也导出成 ONNX,用onnxruntime去跑CPU推理。PyTorch 训练时用GPU没问题,但交付的 demo 不可能要求每个人都在GPU上运行。导出时注意固定输入尺寸,并把动态轴关掉,这样在普通笔记本上也能达到每张2秒以内的速度,离线OCR软件的体验才算成立。这个坑我当年花了两天才填平,提前写在这里,希望帮到你。
本文还有配套的精品资源,点击获取