
简介本资源是一套基于深度学习自主训练开发的手写文字OCR识别系统面向金融、教育、医疗等需要处理非结构化手写信息的行业开发者与算法学习者。系统覆盖通用场景手写文字识别、银行支票OCR、银行进账单OCR并支持机打与手写文字混合识别提供文字检测、文字识别与结构化处理完整链路。压缩包共46个文件约157.13MB以pyd编译模块、py源码、pb模型文件、jpg测试图片、md说明文档及xlsx配置表为主另含ttc字体与docx附赠资料目录按detect、rec、structure、service等模块划分便于按功能检索。已有99人学习下载。读者可获取从模型推理到字段结构化落地的完整工程代码理解支票、进账单等场景的字段定位与分类逻辑并借助测试样例与配置表快速复现和二次开发。1. 手写文字OCR识别系统从通用场景到银行票据一套模型怎么吃下三种活银行柜面每天要处理大量进账单和支票手写金额、日期、账号混着机打文字传统OCR模板匹配一遇到手写体就崩。这个标题讲的就是用深度学习自主训练一套手写文字OCR识别系统覆盖通用场景手写识别、银行支票OCR、银行进账单OCR支持机打和手写混合识别并且把文字检测、文字识别、结构化处理串成完整链路。适合谁做金融票据自动化的后端工程师、想用OCR识别python方案落地毕设的学生、以及需要本地化部署OCR识别软件的技术团队。核心痛点不是“能不能识别”而是“手写体机打混排固定模板票据”三件事同时发生时检测框和识别结果怎么对齐、结构化字段怎么稳定抽取。下面按我实际做过的路径从数据构造到模型训练再到结构化输出把可复现的步骤和参数讲清楚。2. 文字检测与识别模型选型为什么我最终选了DBNetCRNN而不是端到端2.1 检测和识别拆开做的三个现实理由常见做法是直接上端到端模型比如TrOCR或者Donut输入图像直接输出文本。但在银行票据场景里我踩过的坑是端到端模型对长文本行和密集小字的手写体召回率不够稳定而且一旦识别错你没法定位是检测框偏了还是识别模型认错了。拆成检测识别两阶段检测用DBNet识别用CRNNCTC好处是每个阶段可以独立调参、独立换模型、独立做数据增强。DBNet的核心是可微分二值化它把分割概率图经过一个可学习的阈值图得到二值化结果。相比EAST和PSENetDBNet在弯曲文本和手写连笔上的鲁棒性更好推理速度也够用。CRNN是CNNRNNCTC的经典结构对不定长文本序列友好训练数据不需要字符级标注只需要文本行图像和对应字符串。选型时还要考虑部署环境。如果目标机器是NPU电脑部署深度学习环境DBNet和CRNN都有成熟的ONNX导出路径量化到INT8后单张票据推理能压到200ms以内。如果直接用PyTorch服务端跑batch size设4到8GPU利用率比较均衡。2.2 用PaddleOCR快速搭出检测识别基线我一般会先用PaddleOCR跑一个基线确认数据质量和标注格式没问题再决定要不要自己从头训。下面这段代码是在本地装好PaddleOCR后对一张银行进账单做检测识别的完整调用。from paddleocr import PaddleOCR # 初始化det_model_dir和rec_model_dir可以换成自己训练的模型 ocr PaddleOCR( use_angle_clsTrue, # 方向分类器票据可能倒置 langch, # 中英文混合 det_model_dir./models/det_db, rec_model_dir./models/rec_crnn, cls_model_dir./models/cls, use_gpuTrue, drop_score0.5 # 低于0.5的识别结果丢弃 ) img_path bank_statement_001.jpg result ocr.ocr(img_path, clsTrue) # result结构[[[box, (text, score)], ...]] for line in result[0]: box line[0] # 四点坐标 text, score line[1] print(f坐标:{box} 文本:{text} 置信度:{score:.3f})逻辑说明use_angle_clsTrue对银行票据很重要柜面扫描件经常有180度倒置。drop_score0.5是经验值低于这个分数的结果大概率是手写连笔被误检宁可漏也不要错。det_model_dir和rec_model_dir如果不指定会用PaddleOCR自带的通用模型但通用模型对银行票据的手写金额识别率大概只有60%到70%必须用自己的数据微调。参数怎么改如果票据图像分辨率高比如300dpi扫描件把det_limit_side_len设到960或1280默认是960。如果显存不够降到640但小字检测会掉点。rec_batch_num控制识别阶段的batchGPU上设6到10CPU上设1。2.3 自己训练检测模型时的数据标注格式DBNet训练需要标注文件每行格式是图像路径\t[{transcription: 文本, points: [[x1,y1],[x2,y2],[x3,y3],[x4,y4]]}]。手写文字识别场景下标注框要贴紧文字边缘不要留太多空白否则检测框会偏大影响后续CRNN的输入裁剪。我一般用LabelImg或者PPOCRLabel做标注导出后写一个转换脚本统一成上述格式。注意银行支票OCR识别里金额栏的手写数字经常有连笔和粘连标注时要把每个数字单独框出来还是整行框我的经验是整行框让CRNN去学序列关系单独框反而丢失上下文。import json def convert_to_dbnet_format(label_lines, output_path): with open(output_path, w, encodingutf-8) as f: for img_path, annotations in label_lines: # annotations: list of dict with transcription and points line f{img_path}\t{json.dumps(annotations, ensure_asciiFalse)} f.write(line \n) # 示例单张图两个文本框 sample [ (imgs/check_001.jpg, [ {transcription: 20240115, points: [[100,200],[300,200],[300,240],[100,240]]}, {transcription: 壹万贰仟元整, points: [[100,300],[500,300],[500,350],[100,350]]} ]) ] convert_to_dbnet_format(sample, train_label.txt)这段脚本把标注转成DBNet可读的格式。points是四个点顺序是左上、右上、右下、左下。transcription是文本内容机打和手写混排时同一个框里可能既有印刷体又有手写体标注时按实际内容写不要分开。3. 银行支票与进账单的结构化处理从文本行到字段的映射3.1 固定模板票据的字段定位策略银行支票OCR识别和银行进账单OCR识别都属于固定模板票据但“固定”是相对的。不同银行的进账单布局有差异同一银行不同年份的版本也可能微调。我一般用两种策略结合先做模板匹配定位大区域再用文字检测框的相对位置做字段归类。具体做法对票据图像做透视校正后用预先定义的ROI区域裁剪出“日期”、“金额”、“账号”、“收款人”等区域。每个区域内的检测框按x坐标排序拼接成该字段的文本。如果某个区域检测不到框回退到全图检测再用最近邻规则匹配到字段。import cv2 import numpy as np def perspective_correct(img, src_points): # src_points: 票据四个角点顺序左上、右上、右下、左下 dst_points np.float32([[0,0],[800,0],[800,400],[0,400]]) M cv2.getPerspectiveTransform(np.float32(src_points), dst_points) return cv2.warpPerspective(img, M, (800, 400)) # 定义字段ROI基于校正后800x400画布 field_rois { date: (50, 30, 250, 70), amount: (300, 30, 750, 70), payee: (50, 100, 400, 140), account: (450, 100, 750, 140), } def extract_fields(ocr_result, rois): fields {} for name, (x1, y1, x2, y2) in rois.items(): texts [] for line in ocr_result[0]: box line[0] cx sum(p[0] for p in box) / 4 cy sum(p[1] for p in box) / 4 if x1 cx x2 and y1 cy y2: texts.append(line[1][0]) fields[name] .join(texts) return fields逻辑说明perspective_correct把票据拉正避免扫描倾斜导致ROI偏移。extract_fields用检测框中心点判断属于哪个字段比用框的左上角更稳因为手写文字框可能大小不一。field_rois的坐标需要根据实际票据调整我一般会拿20张不同来源的票据做统计取字段区域的最小外接矩形再留10%余量。参数注意如果票据是机打和手写混合识别同一个字段里可能有机打前缀和手写内容比如“金额壹万”拼接时不要加空格后续用正则清洗。3.2 手写金额的中文大写转换与校验银行支票OCR识别里手写金额经常是中文大写比如“壹万贰仟叁佰元整”。识别出来之后要做两件事转成数字金额和进账单上的小写金额做交叉校验。如果两者不一致标记为异常件转人工。import re CN_NUM {零:0,壹:1,贰:2,叁:3,肆:4,伍:5,陆:6,柒:7,捌:8,玖:9} CN_UNIT {拾:10,佰:100,仟:1000,万:10000,亿:100000000} def cn_amount_to_number(cn_str): cn_str cn_str.replace(元整,).replace(元,).replace(整,) total 0 section 0 number 0 for ch in cn_str: if ch in CN_NUM: number CN_NUM[ch] elif ch in CN_UNIT: unit CN_UNIT[ch] if unit 10000: section (section number) * unit total section section 0 else: section number * unit number 0 return total section number # 测试 print(cn_amount_to_number(壹万贰仟叁佰元整)) # 12300这段代码处理了“万”和“亿”的节权位。注意手写体识别结果可能有错字比如“贰”识别成“貳”需要在预处理里做异体字归一化。我一般维护一个映射表把常见异体字转成标准字。校验逻辑小写金额从进账单的机打区域提取用正则\d\.?\d*匹配。如果大写转数字和小写金额差值超过0.01标记异常。这个规则在银行进账单OCR识别里能拦下大部分手写识别错误。3.3 结构化输出的JSON Schema设计结构化处理最后要输出统一JSON方便下游系统消费。我一般定义这样的schema{ doc_type: bank_statement, image_id: stmt_001.jpg, fields: { date: {value: 2024-01-15, confidence: 0.98}, amount_cn: {value: 壹万贰仟叁佰元整, confidence: 0.95}, amount_num: {value: 12300.00, confidence: 0.99}, payee: {value: 张三, confidence: 0.92}, account: {value: 6222020200112233445, confidence: 0.97} }, raw_lines: [ {text: 日期 2024年1月15日, box: [[50,30],[250,30],[250,70],[50,70]], score: 0.98} ], anomalies: [] }confidence取该字段所有检测框识别置信度的最小值这样保守一些。anomalies记录校验失败的字段比如大小写金额不一致。raw_lines保留原始检测结果方便排查。4. 避坑与排查手写OCR训练和部署中翻车的五个场景4.1 损失不下降检测框全糊在一起现象DBNet训练几个epoch后loss在0.5左右震荡推理时检测框把整行文字框成一个大框。原因学习率太大或者标注框之间重叠严重。解决把初始学习率从0.01降到0.001用warmup前500步。检查标注文件如果两个框的IoU超过0.3合并或重新标注。4.2 手写数字“1”和“7”混淆识别结果不稳定现象CRNN对银行支票上的手写数字识别1和7经常互换0和6也偶尔错。原因训练数据里这两个数字的样本不均衡或者图像分辨率太低。解决对1和7做针对性数据增强加随机旋转±5度、随机缩放0.9到1.1。把输入高度从32提到48宽度保持320。如果还不行在CTC解码后加一个规则后处理金额字段里如果出现“7”但上下文是日期优先判为“1”。4.3 机打和手写混排时识别结果串行现象一行里前半段是机打“金额”后半段是手写“壹万”识别出来变成“金额壹万”或者“金额壹万”丢字。原因检测阶段把机打和手写分成两个框但识别阶段按框裁剪后CRNN对短文本的上下文建模不足。解决检测后处理时如果两个框的垂直重叠超过70%且水平间距小于20像素合并成一个框再送识别。合并后的文本行让CRNN一次识别CTC能学到“”和手写数字的边界。4.4 部署到NPU电脑后推理速度慢十倍现象PyTorch GPU上单张200ms转到NPU电脑部署深度学习环境后变成2秒。原因模型没有量化或者NPU只支持特定算子。解决用ONNX导出后做INT8量化校准集用500张票据图像。检查DBNet的可变形卷积是否被NPU支持如果不支持换成普通卷积精度掉1%到2%但速度能回来。CRNN的LSTM层如果NPU不支持换成GRU或者用CNN替代。4.5 结构化字段错位日期跑到金额栏现象进账单OCR识别结果里日期字段提取到了金额数字。原因ROI区域定义时没有考虑票据版本差异或者透视校正的角点检测偏了。解决不要硬编码ROI改成相对坐标。先检测票据的表格线用表格线交点做锚点再按比例定位字段。如果表格线检测不到用文字检测框的聚类结果做动态ROI把所有框按y坐标聚类取最上面一行做日期第二行做金额。5. 进阶技巧用少量标注数据微调出可用的手写识别模型如果你手头只有几百张标注票据从头训DBNetCRNN不现实。我一般用预训练模型做微调检测用PaddleOCR的ch_PP-OCRv4_det预训练权重识别用ch_PP-OCRv4_rec冻结骨干网络只训neck和head。学习率设0.0001batch size设8训20到30个epoch。数据增强用RandAugment但不要用Cutout会把手写连笔切断。验证方法留出50张票据做测试集算字段级准确率不是字符级。字段级准确率完全正确的字段数/总字段数。我做过的一个进账单项目微调前字段准确率62%微调后到89%。提升主要来自手写金额和日期字段。还有一个技巧用LLM做后处理校验。把OCR识别出的文本行和字段值拼成prompt让LLM判断“金额大写和小写是否一致”、“日期格式是否合法”。但注意LLM是否属于深度学习这个讨论不影响落地它就是个规则增强。我一般用本地部署的小模型做校验延迟控制在50ms以内。最后说个血泪经验不要指望一个模型吃下所有银行票据。我一般按银行分模型每个银行训一个检测识别模型共享骨干网络只微调head。这样单银行数据量要求降到200张准确率还能到92%以上。部署时用模型路由先分类票据来源再走对应模型。这个方案比追求一个通用大模型现实得多。希望帮到你。本文还有配套的精品资源点击获取