简介:这份资源是面向高校计算机、软件工程等专业学生的毕业设计完整项目包,主题为基于OpenCV与Tesseract的中文扫描票据OCR识别,适合正在准备毕设、需要OCR实战案例或想学习图像预处理与文字识别流程的开发者参考。压缩包共143个文件,约105.26MB,以83个Python源码为核心,辅以xml配置、png示意图、pdf文档、md说明及少量C/C++、Cython与模型权重文件,覆盖从图像预处理、文本检测到识别输出的完整链路。项目已获导师认可并高分通过,代码经测试可正常运行,功能完整。资源中保留了Fast R-CNN相关的网络定义、NMS编译文件与VGGnet权重,便于读者理解票据区域检测与OCR串联的实现细节,也可直接作为二次开发或论文实验的基础工程。目前已有540人学习下载,适合需要完整可跑通毕设方案与排错参考的读者。
1. 从一张糊掉的增值税发票说起:这套 OpenCV + Tesseract 票据 OCR 源码到底能跑出什么
手里拿到一张手机拍的增值税发票,边角卷曲、光照不均、印章盖在金额上,直接丢给 Tesseract 识别,返回的是一堆乱码和空行——这是绝大多数人第一次做中文票据 OCR 的真实体验。这套毕业设计资源的核心价值,就是把「拍歪的票据照片」到「结构化字段文本」之间那条脏活链路完整走通了:OpenCV 负责图像预处理(灰度化、二值化、去噪、倾斜校正、ROI 裁剪),Tesseract 负责中文识别,中间还夹着一套基于 Cython 加速的 NMS 后处理模块和 VGG 网络权重文件,说明作者在检测阶段做了额外工作,不只是调个 API 交差。
它适合三类人:正在做计算机视觉方向毕业设计、需要一份能跑通全流程参考代码的学生;想快速搭一个票据识别 demo 验证业务可行性的工程师;以及手头有大量扫描件需要批量提取字段、又不想从零造轮子的从业者。资源里带了完整数据和训练权重,省掉了最耗时的数据标注和模型训练环节,这一点比网上那些只给推理脚本的仓库实在得多。
2. 拆开压缩包先看什么:目录结构、依赖版本与运行环境确认
2.1 从文件清单反推技术栈和模块职责
拿到压缩包别急着 pip install,先把目录树看清楚。从项目正文给出的文件清单能读出几个关键信息:cython_nms.c、bbox.c、gpu_nms.c、nms_kernel.cu这一组是目标检测的后处理加速模块,说明项目里跑过 Faster R-CNN 或类似的两阶段检测器;VGGnet_fast_rcnn_iter_150000.ckpt.data-00000-of-00001是 TensorFlow 格式的模型权重,迭代了 15 万次,说明训练是认真跑过的;checkpoint文件记录权重路径,.gitignore说明是从 Git 仓库导出的。
这意味着整个 pipeline 大概率是:OpenCV 做图像预处理 → 检测模型定位票据中的关键区域(如发票代码、金额、日期)→ 对每个 ROI 做裁剪和增强 → Tesseract 做中文识别 → 后处理规整输出。理解这条链路,后面调参才知道改哪里。
2.2 环境搭建:Python 版本、OpenCV 编译选项与 Tesseract 中文包
环境是这套代码最容易翻车的地方。我一般会先确认三件事:Python 版本、OpenCV 是否带 contrib 模块、Tesseract 有没有装中文语言包。
# 建议 Python 3.7~3.9,太新的版本 TensorFlow 1.x 装不上 conda create -n ocr_bill python=3.8 conda activate ocr_bill # OpenCV 用 pip 装主包即可,票据预处理用不到 contrib pip install opencv-python==4.5.5.64 pip install opencv-contrib-python==4.5.5.64 # Tesseract 本体走系统包管理,Windows 下用安装包 # Ubuntu/Debian: sudo apt-get install tesseract-ocr tesseract-ocr-chi-sim # 确认中文包已装 tesseract --list-langstesseract-ocr-chi-sim是简体中文语言包,没有它识别出来全是英文乱码。--list-langs输出里必须能看到chi_sim,否则后面所有识别都是白费。Windows 用户装完 Tesseract 后要把安装目录加进 PATH,否则pytesseract找不到可执行文件。
import pytesseract import cv2 import numpy as np # Windows 下需要手动指定路径,Linux 下通常不用 # pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' img = cv2.imread('bill_sample.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 自适应二值化,比全局阈值更适合光照不均的票据 binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 8) text = pytesseract.image_to_string(binary, lang='chi_sim') print(text)adaptiveThreshold的blockSize设 15、C设 8 是我在票据场景下试出来的经验值:blockSize 太小会把笔画断开,太大则失去局部适应性;C 值越大二值化越保守,噪点多的时候往上调。这段代码只是验证环境通不通,真正跑项目要走完整的预处理链路。
提示:如果
import cv2报ModuleNotFoundError,先确认 conda 环境激活了没有,再确认 pip 装到了当前环境而不是系统 Python。
3. 图像预处理链路:灰度、二值化、去噪、倾斜校正与 ROI 裁剪
3.1 为什么票据 OCR 的成败七成在预处理
Tesseract 对输入图像质量极其敏感。票据照片常见的四类问题——光照不均、透视畸变、印章遮挡、背景噪点——任何一个没处理好,识别率都会断崖式下跌。这套源码里 OpenCV 承担的就是把这些干扰逐一消掉的工作。我拆过不少 OCR 项目,凡是识别效果差的,八成不是模型不行,而是预处理偷懒了。
预处理的标准链路是:灰度化 → 去噪 → 二值化 → 形态学操作 → 倾斜校正 → ROI 裁剪。每一步都有参数要调,下面逐个说。
3.2 去噪与二值化:高斯滤波、自适应阈值与形态学闭运算
import cv2 import numpy as np def preprocess_bill(image_path): img = cv2.imread(image_path) # 1. 灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 高斯滤波去噪,ksize 取奇数,票据扫描件用 3 或 5 blurred = cv2.GaussianBlur(gray, (3, 3), 0) # 3. 自适应二值化 binary = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 25, 10) # 4. 形态学闭运算连接断开的笔画 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2)) closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return closedGaussianBlur的 ksize 用 (3,3) 而不是 (5,5),是因为票据上的小字笔画细,核太大会把笔画糊掉。adaptiveThreshold的 blockSize 从 15 调到 25,是因为票据文字区域比一般文档大,需要更大的邻域来估计局部阈值。闭运算的 kernel 用 (2,2) 是保守选择,太大反而会把相邻字符粘连。
3.3 倾斜校正:霍夫变换检测直线与仿射变换旋转
票据扫描时经常有几度的倾斜,Tesseract 对倾斜非常敏感,超过 2 度识别率就明显下降。
def deskew(binary_img): # 用霍夫变换找直线,取所有直线角度的中位数作为倾斜角 edges = cv2.Canny(binary_img, 50, 150, apertureSize=3) lines = cv2.HoughLines(edges, 1, np.pi / 180, threshold=200) if lines is None: return binary_img angles = [] for line in lines[:20]: # 只取前 20 条最明显的 theta = line[0][1] angle = np.degrees(theta) - 90 if -45 < angle < 45: # 过滤掉垂直方向的干扰线 angles.append(angle) if not angles: return binary_img median_angle = np.median(angles) # 仿射变换旋转校正 h, w = binary_img.shape[:2] center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center, median_angle, 1.0) rotated = cv2.warpAffine(binary_img, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) return rotatedHoughLines的 threshold 设 200 是经验值,太低会检出大量噪声线,太高则漏检。取中位数而不是平均值,是为了避免个别异常角度把整体拉偏。borderMode用BORDER_REPLICATE而不是默认的黑色填充,是因为黑边会被 Tesseract 当成字符区域产生误识别。
3.4 ROI 裁剪:轮廓检测定位票据区域并做透视变换
如果输入是拍照的票据,背景桌面会干扰识别,需要先把票据区域裁出来。
def crop_bill_region(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 边缘检测 + 膨胀连接断边 edged = cv2.Canny(gray, 75, 200) edged = cv2.dilate(edged, np.ones((3, 3), np.uint8), iterations=1) contours, _ = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 取面积最大的轮廓,假设票据是画面主体 contours = sorted(contours, key=cv2.contourArea, reverse=True) for c in contours[:5]: peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.02 * peri, True) if len(approx) == 4: # 找到四边形轮廓 return four_point_transform(img, approx.reshape(4, 2)) return img # 没找到就返回原图approxPolyDP的精度参数0.02 * peri控制多边形逼近程度,值越小越贴近原始轮廓但边数越多,0.02 是票据场景下比较稳的取值。four_point_transform是透视变换函数,把倾斜拍摄的四边形拉正成矩形,这个函数在源码里应该有实现,核心是计算变换矩阵后warpPerspective。
注意:ROI 裁剪不是万能的。如果票据占画面比例小于 30%,或者背景有类似矩形的干扰物,轮廓检测会失败。这种情况我一般会加一个面积阈值过滤,只保留面积大于画面 20% 的轮廓。
4. 检测模型与 NMS 加速:Cython 编译、权重加载与推理链路
4.1 为什么票据 OCR 里塞了一个目标检测模型
纯 Tesseract 是对整图做识别,它不知道哪里是发票代码、哪里是金额、哪里是日期。这套源码里带了VGGnet_fast_rcnn_iter_150000.ckpt权重和 NMS 相关源码,说明作者用 Faster R-CNN 先检测票据中的关键字段区域,再对每个区域单独做 OCR。这样做的好处是:字段定位准确,后续可以做结构化输出;坏处是链路长、依赖多、环境难配。
cython_nms.c、bbox.c、gpu_nms.c、nms_kernel.cu这一组文件是 Faster R-CNN 的标准后处理组件。NMS(非极大值抑制)用来合并重叠的检测框,CPU 版本用 Cython 编译加速,GPU 版本用 CUDA。如果你的机器没有 NVIDIA 显卡,gpu_nms编译会失败,需要改用 CPU 版本。
4.2 Cython 模块编译:setup.py 配置与常见编译错误
# setup.py 核心内容,编译 CPU 版 NMS from distutils.core import setup from Cython.Build import cythonize import numpy as np setup( ext_modules=cythonize([ "cython_nms.pyx", # NMS 的 Cython 实现 "bbox.pyx", # 边界框工具函数 ]), include_dirs=[np.get_include()] # 必须加,否则找不到 numpy 头文件 )编译命令是python setup.py build_ext --inplace。--inplace让生成的.so文件放在源码目录而不是 build 目录,方便直接 import。include_dirs里加np.get_include()是最容易漏的一步,漏了会报numpy/arrayobject.h: No such file or directory。
如果编译gpu_nms报错,先确认 CUDA 版本和 TensorFlow 版本匹配。TensorFlow 1.x 对 CUDA 版本很挑,CUDA 10.0 配 TF 1.15 是经典组合。没有 GPU 的话,直接在代码里把gpu_nms的 import 注释掉,改用cython_nms即可,速度慢一些但不影响功能。
4.3 权重加载与推理:TensorFlow 1.x 会话模式与输入尺寸
import tensorflow as tf # TF 1.x 的图模式,这套代码大概率是基于 TF 1.x 写的 tf.reset_default_graph() saver = tf.train.Saver() with tf.Session() as sess: # 加载 checkpoint,路径不带 .data-00000-of-00001 后缀 saver.restore(sess, "./checkpoint/VGGnet_fast_rcnn_iter_150000.ckpt") # 输入图像需要 resize 到网络固定尺寸,通常是 600x1000 或类似 # 具体尺寸看源码里的 cfg 配置 feed_dict = {input_tensor: resized_img} boxes, scores = sess.run([output_boxes, output_scores], feed_dict=feed_dict)saver.restore的路径只写到.ckpt,不要带.data-00000-of-00001后缀,TF 会自动找对应的 index 和 data 文件。输入尺寸必须和训练时一致,否则检测框会错位。这个尺寸一般在源码的配置文件里,找IMAGE_SIZE或类似的变量。
提示:如果 TF 1.x 装不上,可以试试
pip install tensorflow==1.15.5,这是最后一个支持 Python 3.7 的 1.x 版本。Python 3.8 以上只能跑 TF 2.x,需要改代码或用tf.compat.v1兼容模式。
5. 避坑与排查:中文识别乱码、Cython 编译失败、权重加载报错
5.1 识别结果全是乱码或空行
现象:Tesseract 返回的文本里中文全是问号或方块,或者整段为空。
原因:三种可能——没装chi_sim语言包、lang参数没传、图像预处理过度导致笔画丢失。
解决:先跑tesseract --list-langs确认chi_sim存在;代码里image_to_string必须带lang='chi_sim';如果前两步都对,把二值化结果存图看一眼,笔画断成虚线就是blockSize太小或C值太大,调回去。
5.2 Cython 编译报 numpy 头文件找不到
现象:fatal error: numpy/arrayobject.h: No such file or directory。
原因:setup.py里没加include_dirs=[np.get_include()],或者当前环境没装 numpy。
解决:确认import numpy能跑通,然后在setup()里补上include_dirs。如果用的是虚拟环境,确认编译时环境是激活的。
5.3 权重加载报 NotFoundError 或 DataLossError
现象:saver.restore时报NotFoundError: Key not found或DataLossError。
原因:路径写错、checkpoint 文件不完整、TF 版本不匹配导致图结构对不上。
解决:确认checkpoint文件里的路径和实际文件位置一致;确认.ckpt.index、.ckpt.data-00000-of-00001、.ckpt.meta三个文件都在;如果 TF 版本和训练时不一致,图结构可能对不上,这种情况只能找匹配的 TF 版本重跑。
5.4 OpenCV 读图返回 None 导致后续全崩
现象:cv2.imread返回None,后面cvtColor报Assertion failed。
原因:图片路径含中文、格式不支持、文件损坏。
解决:OpenCV 在 Windows 下对中文路径支持不好,用cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR)替代imread。另外确认图片确实是 jpg/png 格式,不是 webp 或 heic。
5.5 检测框坐标和原图对不上
现象:检测出来的框位置偏移,或者框的大小和预期不符。
原因:输入图像 resize 后没有记录缩放比例,检测框坐标是缩放后的,映射回原图时算错了。
解决:在 resize 前记录scale_x = original_w / input_w,检测框坐标乘以scale_x和scale_y再映射回原图。这个缩放逻辑在源码里应该有,如果自己改代码容易漏。
6. 把识别结果落成结构化字段:后处理正则与批量跑图技巧
单张票据识别出文本只是第一步,真正能用的是把文本里的发票代码、金额、日期抽成结构化数据。Tesseract 返回的是带换行的原始文本,字段之间没有分隔,需要靠正则和关键词定位。
import re def extract_fields(ocr_text): result = {} # 发票代码通常是 10 或 12 位数字 code_match = re.search(r'发票代码[::]\s*(\d{10,12})', ocr_text) if code_match: result['invoice_code'] = code_match.group(1) # 金额:匹配"价税合计"后面的数字,允许逗号和小写 amount_match = re.search(r'价税合计.*?[¥¥]?\s*([\d,]+\.?\d*)', ocr_text) if amount_match: result['total_amount'] = amount_match.group(1).replace(',', '') # 日期:匹配 2024年01月01日 或 2024-01-01 格式 date_match = re.search(r'(\d{4})[年\-/](\d{1,2})[月\-/](\d{1,2})', ocr_text) if date_match: result['date'] = '-'.join(date_match.groups()) return result正则里的[::]同时匹配中英文冒号,因为 OCR 有时会把中文冒号识别成英文冒号。金额匹配用.*?非贪婪匹配,避免跨字段匹配到后面的数字。日期格式兼容三种常见写法,实际项目里可以根据票据类型再细化。
批量跑图的时候,我习惯加一个异常捕获和日志,避免单张图失败导致整个批次中断:
import os import logging logging.basicConfig(filename='ocr_batch.log', level=logging.INFO) def batch_process(image_dir, output_csv): results = [] for fname in os.listdir(image_dir): if not fname.lower().endswith(('.jpg', '.png', '.jpeg')): continue try: path = os.path.join(image_dir, fname) text = full_pipeline(path) # 预处理 + 检测 + OCR fields = extract_fields(text) fields['filename'] = fname results.append(fields) logging.info(f'{fname} OK') except Exception as e: logging.error(f'{fname} FAILED: {e}') continue # 写 CSV import csv if results: keys = results[0].keys() with open(output_csv, 'w', newline='', encoding='utf-8-sig') as f: writer = csv.DictWriter(f, fieldnames=keys) writer.writeheader() writer.writerows(results)encoding='utf-8-sig'是为了 Excel 打开 CSV 不乱码,这个坑我踩过不止一次。continue而不是break,保证单张失败不影响后续。
验证识别效果不能只看一两张图。我一般会准备 20 张不同类型的票据(增值税发票、火车票、出租车票各若干),跑完后人工核对字段准确率。如果某个字段准确率低于 80%,先看是预处理问题还是正则问题——把 OCR 原始文本打出来看,如果文本里字段本身就没识别对,调预处理;如果文本对但正则没匹配上,改正则。
从那以后我每次拿到新的 OCR 项目,都强制先跑一遍「原图 → 预处理中间图 → OCR 原始文本 → 结构化字段」的全链路可视化,把每一步的中间结果存图或打日志,确认哪一步开始出问题再动手调。这套毕业设计资源的价值不在于代码多优雅,而在于它把这条链路完整跑通了,省掉了从零搭框架的时间。希望帮到你。
本文还有配套的精品资源,点击获取