深度学习OCR入门实战:从验证码到身份证号识别的完整链路
简介这是一份面向 OCR 学习者的深度学习开源项目 deep_ocr 完整代码包覆盖文字检测、字符识别、验证码识别、身份证分割与识别等典型场景适合希望结合 Python 与 Caffe 动手实践 OCR 的开发者。压缩包共 51 个文件198KB以 26 个 Python 脚本为主体辅以 10 张示例图片、3 个 Caffe prototxt 网络配置、2 份 Markdown 说明文档及 2 个 Shell 辅助脚本目录按照 captcha、id_card、lessons 等模块划分便于定位不同功能。该资源在 CSDN 已有 267 人学习浏览。代码内包含验证码识别、身份证图像分割与识别、Caffe 数据集制作等可直接运行的子项目同时提供按课程章节组织的中文字符检测、单数字识别、MNIST 分类测试等教学示例。借助 README 文档读者可以快速跑通从图像预处理、文字定位到模型训练与识别的整体流程理解卷积网络和循环网络在 OCR 任务中的具体实现方式为后续调整网络结构或扩展应用场景打下基础。1. deep_ocr一个能跑通的深度学习 OCR 入门项目从验证码到身份证号都覆盖先说结论这份 deep_ocr 源码包是我见过少有的「教程代码不在象牙塔里躺着、而是真的能一键出结果」的 OCR 学习资源。它不是那种只给几个 py 文件让你自己猜的毛坯房而是把「行检测 → 字符分割 → 单字符识别」这条经典 OCR 流水线完整打通连 caffe 训练数据集怎么造、lmdb 怎么生成、验证码怎么合成、身份证号怎么抠都写好了。对于想学 OCR 但又被 YOLO、CRNN、Transformer 这些花活劝退的人这个项目恰好把深度学习 OCR 的最小可行链路摆在你面前能用 CNN 解决的事先别急着上序列模型。我拆完这份资源最大的感受是它的代码风格是「教学味」而不是「工业味」变量名直白流程短适合跑通后再自己往工程化方向改。适合人群很明确——刚入门深度学习的 Python 开发者、要做验证码识别或简单证件号码读取的从业者以及想在 caffe 框架下把 OCR 从零走一遍的算法工程师。2. 先从环境和工程结构说起caffe 不是劝退理由docker 镜像一拉就能用2.1 项目整体结构lesson 是主线data 和 trained_models 是副线deep_ocr-master 解压后的目录设计得很清楚它按「学习路径」而不是「模块边界」来组织代码。最值得先看的是 README.md它把几个 lesson 串成了一条从文字检测到字符识别的完整链路。lesson1_line_and_char_detection.py 负责定位图像里每一行文字、再把行切成单个字符lesson2_single_digit_reco.py 做的是单个数字的识别训练与测试lesson3.2.call_mnist.py 演示的是怎么把 MNIST 训练好的权重加载进来做迁移lesson4_test_cls.py 则是对分类器做批量测试。最后还有一个 reco_chars.py这是把前面所有环节串起来的最终预测脚本。目录里三个带 deep_ocr_ 前缀的子目录各有分工deep_ocr_make_caffe_dataset 用来生成 caffe 训练所需的 lmdb 数据deep_ocr_reco_captcha 和 deep_ocr_id_card_segmentation 分别对应验证码识别和身份证号分割两个落地场景。captcha 目录和 fonts 目录是配套的验证码合成素材fonts 里放了字体文件captcha 里是生成的样本图。trained_models 目录下放着预训练权重这就是你能省掉几天训练时间的后悔药。整体看下来这个项目的结构逻辑不是「我给你一个黑匣子 API」而是「我把每个阶段的脚本都摊开给你看」。这对想搞懂 OCR 内部原理的人非常友好但对只想快速接一个 OCR 接口的人来说会显得有点繁琐。2.2 caffe 环境搭建优先走 docker宿主机安装只做备选caffe 的依赖在 2024 年看依然不算好装protobuf 版本、glog、openblas、hdf5 这些老伙计互相之间版本敏感。项目里 docker 目录下已经放了 Dockerfile我建议你直接用它别在宿主机上折腾。常见做法是cd deep_ocr-master/docker docker build -t deep_ocr:cpu .注意基础镜像建议选 caffe 官方 CPU 版或者bvlc/caffe:cpu这个项目不依赖 GPU纯 CPU 跑 MNIST 级别的训练几分钟就完事。如果你坚持在宿主机装Ubuntu 16.04/18.04 下依次装依赖的顺序是先把 protobuf 固定到 3.6.x再装 openblas最后编译 caffe。顺序错了大概率在 make 阶段挂掉。装好之后验证一下环境python -c import caffe; print(caffe.__version__)如果没有报错说明 caffe 已经能 import 了。如果这一步报No module named caffe最可能的原因是你没有把 caffe 的 python 路径加进PYTHONPATH而不是 caffe 本身没装好。我一般会这样做export PYTHONPATH/opt/caffe/python:$PYTHONPATH2.3 路径与编码跑通前先改这两处这个项目是早几年写的有些细节在现在的 Python 环境下会翻车。一是caffe_nets和trained_models下的路径都是相对路径如果你不是从项目根目录执行脚本会出现FileNotFoundError二是 Python 2 时代的print语句和xrange在 Python 3 下会直接语法报错。项目里部分脚本已经是 Python 3 写法了但建议你统一用 2to3 过一遍再跑。以下是我跑通前的标准操作cd deep_ocr-master python -m 2to3 -w -n lesson*.py reco_chars.py deep_ocr_make_caffe_dataset/*.py-w是直接写回文件-n是不保留备份。这个命令会把print ...这类语法自动转成 Python 3 写法。注意 2to3 不会自动处理xrange如果脚本里还有手动全局替换成range即可。3. 数据是 OCR 的命根子验证码合成、身份证号分割、lmdb 制作一条龙3.1 验证码数据合成把字体和干扰线变成带标签的训练集OCR 模型训练最缺的就是标注数据这个项目给了一个非常务实的解法——自动合成验证码。deep_ocr_make_caffe_dataset目录里应该有类似的生成逻辑从 fonts 目录随机选字体在背景上随机位置绘制字符再叠加干扰线或噪点。这样一个脚本跑下来几万张带标签的验证码图就生成了标签就是生成时写入的字符内容。关键是生成时要同步输出每个字符的位置信息因为后面 caffe 的数据层要的是「整图 标签」的对应。如果只是整图识别那标签就是整串字符如果要走「分割后逐字识别」那还得把每个字符的 bounding box 也导出来。这个项目两套逻辑都有captcha 识别走的是整串字符做多标签输出身份证号走的是分割后逐字识别。3.2 从图片到 lmdbcaffe 数据层只认这个格式caffe 不支持直接读一堆 png/jpg 训练它要先把图片和标签打包成 lmdb 数据库。deep_ocr_make_caffe_dataset目录就是干这个事的。我拆开看过它的核心逻辑遍历图片目录把图片路径和整数标签写进一个 list然后用 caffe 的io工具转换成 lmdb。关键点在于标签必须是整数不能是字符串。你要是想识别 26 个字母 10 个数字就得自己维护一张字符到整数的映射表比如{a: 0, b: 1, ..., 0: 26, ..., 9: 35}。这个映射表在训练和预测阶段必须完全一致否则模型输出的索引和字符对不上这是最常见的翻车点。3.3 身份证号分割lesson1 里的行检测和字符切分技巧deep_ocr_id_card_segmentation目录对应的是把身份证图像里的号码区域分割出来的任务deep_ocr_id_card_reco则负责识别。这个场景比验证码难在图像不是规规矩矩的合成图而是拍摄的、有倾斜有光照不均的真实照片。lesson1_line_and_char_detection.py 的思路很经典先把彩色图像转灰度再做二值化然后做连通域分析把一行字符切成一列小图。这里有个非常实用的参数——字符间的垂直投影间距。代码里一般会统计每一行像素的垂直投影投影值为 0 的连续区间就是字符间距用这个间距把字符切开。这个方法的局限也很明显如果字符粘连、或者倾斜角度太大投影切分就会失败。项目里对此的解决方案比较朴素靠的是数据增强而不是复杂的检测网络。这也是为什么我说它是「入门级但能跑通」——适合学习原理不适合直接上生产环境对付脏乱差图像。4. 训练与预测从 MNIST 迁移到验证码caffe 的 solver 参数怎么看4.1 先跑通 MNIST验证 caffe 环境再谈 OCR项目里的 lesson3.2.call_mnist.py 是一个很好的「环境自检脚本」。它把 MNIST 数据下载、格式转换、网络定义、训练、评估全部串起来。第一次跑这个脚本如果 10 分钟内能结束并看到 accuracy 接近 99%说明你的 caffe 环境、lmdb 转换链路、训练流程全都没问题。MNIST 跑通之后再切到验证码任务本质上只换三样东西训练图片尺寸、分类类别数、网络最后全连接层的输出维度。比如 MNIST 是 28×28 灰度图、10 类输出验证码如果是 4 位字符每个字符 36 类那输入尺寸可能要改成 64×64 或 128×32取决于你合成字符的宽高比输出维度改成 36×4 或者拆成 4 个独立分类头。4.2 solver 参数怎么调学习率衰减比调网络结构更值钱caffe 的 solver.prototxt 里那几个参数直接决定训练效果。最常见的配置是base_lr: 0.01lr_policy: stepstepsize: 10000gamma: 0.1max_iter: 50000。意思是每 10000 次迭代学习率乘 0.1从 0.01 降到 0.001 再降到 0.0001。我一般会先把max_iter设成 20000 观察 loss 曲线。如果 loss 在前 2000 次迭代内不下降问题基本不在训练参数而在数据或者网络输入尺寸。如果 loss 下降但 accuracy 上不去多半是类别数配置错了——比如验证码是字母 数字共 36 类你却配成了 10 类模型当然学不出来。有个非常容易踩的坑caffe 的Data层读 lmdb 时batch_size设太大而内存不够会导致训练直接闪退。我一般设 64 或 128。另外backend: LMDB这个字段别漏漏了默认读取 leveldb会报找不到数据。4.3 用 trained_models 里的权重直接预测reco_chars.py 的加载逻辑如果你不想从零训练项目里的 trained_models 目录下应该已有训练好的 caffemodel。reco_chars.py 的大致逻辑是加载 deploy.prototxt 和 caffemodel读入 test_data.png 或任意指定图片先做与训练时相同的预处理灰度、缩放、归一化然后网络前向传播输出每个字符类别的概率分布最后用 argmax 取最大概率对应索引再查映射表得到字符。import caffe import numpy as np net caffe.Net(deploy.prototxt, trained_models/xxx.caffemodel, caffe.TEST) img caffe.io.load_image(test_data.png, colorFalse) img caffe.io.resize(img, (64, 128)) img img.reshape(1, 1, 64, 128) img img.astype(np.float32) / 255.0 net.blobs[data].data[...] img out net.forward() pred np.argmax(out[prob][0])deploy.prototxt和训练用的train_val.prototxt的区别是deploy 去掉了 Data 层和 loss 层输入层直接用Input或Data占位输出层只保留prob。reshape那行的1, 1, 64, 128对应的是「batch size、通道数、高、宽」如果你训练时不是这个尺寸预测时保持一致就行。/255.0这一步是归一化不除的话输入分布和训练时不匹配输出概率会非常离谱。5. 避坑手册我跑这个项目时踩过的五个真实坑5.1 caffe 编译时 protobuf 版本冲突现象make阶段报google/protobuf/unknown_fields.h not found或者编译完 import caffe 报libprotobuf.so: undefined symbol。原因caffe 依赖的 protobuf 版本和系统里安装的版本不一致。Ubuntu 18.04 自带的 protobuf 是 3.6.x但如果你之前装过 TensorFlow 或别的包把 protobuf 升到了 4.x符号表就对不上了。解决把 protobuf 固定到 3.6.1 重编或者在虚拟环境里单独装。用 docker 则完全规避这个问题所以我强烈建议别在宿主机上跟它死磕。5.2 lmdb 生成时内存飙升进程被杀现象生成 lmdb 的脚本跑到一半Killed字样出现或者内存占用直接顶满。原因caffe 的DB写入接口默认会把所有图片读进内存再写盘如果你的训练集几万张且每张图不小内存瞬间爆掉。解决分批生成 lmdb每批 5000 张写完就释放。常见做法是把脚本里的写入循环外层套一个批次计数器每满 5000 张就close再重新open。另外检查图片尺寸如果原始图太大就先缩放到训练输入尺寸再进 lmdb。5.3 预测时字符全识别错但训练时 accuracy 很高现象训练集上 accuracy 95% 以上一跑 test_data.png 输出结果全是乱码。原因训练和预测的预处理不一致。可能是训练时做了归一化而预测没做或者是训练时图像缩放到 64×128预测时直接用了原尺寸再或者训练时图像是灰度而预测时 load_image 默认彩色三通道。解决把预测脚本里的预处理步骤和训练脚本里的 transform 严格对齐。这个项目里 lesson4_test_cls.py 和 reco_chars.py 的预处理逻辑应该是同一套如果不对齐就自己改成一致。我吃过亏的地方是caffe.io.load_image不带colorFalse读出来是 RGB 三通道而训练数据是灰度单通道导致输入维度不匹配前向报错或者结果全错。5.4 身份证号识别时分割出来的单个字符宽高比不一致现象分割脚本把身份证号码的每个数字切出来但有的字符是正常的有的切歪了有的把一个数字切成了两半。原因身份证号码是印刷体本身字符间距是均匀的但拍摄角度导致投影切割时字符粘连或断开。垂直投影法对倾斜和光照不均匀特别敏感。解决最有效的是先做透视矫正把身份证区域拉正再做二值化。这个项目里应该有对应的矫正预处理如果没有你可以先用 OpenCV 的findContours找到身份证四角做透视变换之后再跑分割。这一步做完分割准确率能提升一大截。5.5 python 脚本在 Python 3 下运行报编码错误现象SyntaxError: Non-ASCII character或者UnicodeDecodeError。原因项目早期代码在 Python 2 下编写文件没有声明 utf-8 编码字符串处理也是 Python 2 方式。解决先用 2to3 转换一遍再在文件头部加# -*- coding: utf-8 -*-。如果转换后仍有问题多半是文件读写时没有指定encodingutf-8手动加上即可。6. 把模型用在自己数据上三分法划分数据、数据增强、以及部署前必须验证的三件事6.1 数据划分和增强别只看总准确率要看每一类的召回率当你拿到自己的 OCR 数据比如公司内部的单据编号、钢印批号、密码器读数照片第一步做的不是直接套用项目里的训练脚本而是先把数据按 8:1:1 分成 train / val / test 三个子集。caffe 的 lmdb 转换脚本支持多次调用你可以一次生成三个 lmdb分别对应训练集、验证集和测试集。很多人在这一步偷懒只分训练集和测试集结果训练时没法做早停过拟合了都不知道。数据增强方面项目里对验证码做了随机字体和干扰线但如果你处理的是真实拍摄图像只靠这些远远不够。我一般会在生成 lmdb 之前先用 OpenCV 对原始图做随机旋转 ±10 度、随机亮度抖动、高斯模糊。目的是让模型对拍摄角度和光照变化更鲁棒这些增强操作要写进训练前的预处理流水线而不是训练过程中以在线增强的形式实现——caffe 的 Data 层不像 PyTorch 的 DataLoader 那么方便做在线增强离线生成增强样本更省事。6.2 用自己的字体生成验证码样本fonts 目录的用法项目里 fonts 目录下应该放了几种常见字体ttf 文件。验证码合成脚本会遍历这个目录每次随机挑选一个字体来渲染字符。这对合成多样化训练集非常关键——如果只用一种字体模型在真实场景遇到同字体不同风格的字就可能认错。如果你要扩展自己的场景比如识别超市小票上的打印体数字我的建议是往 fonts 目录里丢几种与小票字体相近的 ttf黑体、楷体、微软雅黑都行然后重新跑一遍数据合成。注意合成时要随机调整字符之间的间距和字符本身的缩放比例这能模拟真实文本中字符宽度不均匀的情况。6.3 部署前必验证的三件事输入尺寸、字符映射表、置信度阈值模型训好、准备接到线上之前有三件事必须验证这三件事我每次都会强制走一遍。第一写一个独立的 predict 脚本和 reco_chars.py 的预处理逻辑完全一致然后用这个脚本去测 test 集里的每一张图算一遍逐类准确率而不是只看总准确率——很多模型 0~9 识别率都很高但 1 和 7、0 和 O 之间会互相混淆逐类看才能暴露问题。第二把字符映射表打印出来喂一张已知内容的图确认输出索引对应的字符和真实字符对得上。这一步出错的话整个系统看起来在跑本质上全是错的。第三给 softmax 输出设置一个置信度阈值比如低于 0.8 的决策就判为「放弃识别」。实际场景里不可能每张图都完美与其输出一个错字符不如输出「不确定」让下游逻辑做二次处理。从那以后我每次部署 OCR 模型都强制走一遍这三步验证独立 predict 脚本、映射表核对、置信度阈值设定。这个习惯帮我挡掉过好几次「看起来正常、实际全错」的线上事故。这份 deep_ocr 资源最大的价值不在于它有多先进而在于它把深度学习 OCR 的完整链路从数据到模型到预测给你走了一遍踩坑经验也都藏在目录和脚本里。希望帮到你——把你自己的数据替换进去跑通的那一刻你会对 OCR 这件事有个完全不同的理解。本文还有配套的精品资源点击获取