☰
基于rapidOCR的本地OCR源码实战:检测、方向分类与识别全流程解析
2026/10/12 8:10:56 网站建设 项目流程

简介:这是一套基于 rapidOCR 改造的完整 OCR 图片识别源码项目,面向需要将图片文字识别能力集成到自有系统中的开发者与 Python 学习者。项目已实现 OCR 识别并返回带标记的 HTML 结构,内置 /engine/ocr 接口,可接收 HTTP 链接、本地文件、文件流等多种形式的图片输入,识别结果可直接嵌入其他页面或复制使用,适用于文档数字化、票据识别、网页内容提取等场景。资源包共 46 个文件,包含 23 个 py 源码、17 个 pyc 编译文件、3 个 onnx 模型文件,以及 txt、html、yaml 等配置与页面文件,压缩包约 14.26MB,目录涵盖 rapid_ocr 核心模块、ch_ppocr 检测识别分类模型、web_service 服务入口与前端页面等,结构清晰便于二次开发。目前已有 1739 人学习下载,读者可借此快速理解 OCR 工程化落地思路,掌握接口封装、模型调用与 HTML 结果渲染的完整链路。

1. 从一张发票截图说起:这套 OCR 源码到底能干什么

上周帮朋友处理一批报销单据,三百多张发票截图混着手机拍的纸质文档,他问我能不能直接批量转成文字。我第一反应是调云服务 API,但对方要求数据不能出内网,只能本地跑。翻了一圈开源方案,最后锁定了一套基于 rapidOCR 改造的 Python 源码包——它把检测、方向分类、文字识别三个模型串成完整流水线,输出结果直接带 HTML 结构,连版面信息都保留了。

这套东西解决的核心问题很具体:你有一堆图片,想拿到结构化的文字结果,又不想依赖外部接口。它适合两类人——一类是需要快速搭一个本地 OCR 服务的后端开发者,另一类是手里有大量扫描件、想批量提取文字做后续处理的数据工作者。源码本身是 Python 实现,模型文件独立存放,推理引擎默认用 ONNX Runtime,CPU 上也能跑出可接受的速度。下面我从实际拆包的角度,把这份资源的用法、参数和踩坑点讲清楚。

2. rapidOCR 改造版拆解:检测、方向、识别三段式怎么串

2.1 为什么是 rapidOCR 而不是 PaddleOCR 原生

PaddleOCR 功能全,但依赖链长,装完动辄几个 G,部署到一些边缘设备上很痛苦。rapidOCR 的思路是把 PaddleOCR 训练好的模型转成 ONNX 格式,用 ONNX Runtime 做推理,整个包体积能压到几十兆。这套源码在 rapidOCR 基础上做了两处关键改造:一是把三个模型的输出统一成内部数据结构,二是加了一层 HTML 渲染逻辑,让识别结果不只是纯文本,而是带段落和坐标的富文本。

常见做法是直接用 rapidOCR 的 pip 包,但这份源码把模型加载、预处理、后处理都拆成了独立模块,方便你替换其中某一段。比如你想换检测模型,只需要改detector.py里的模型路径和输入尺寸,不用动识别部分。

2.2 三个模型的分工与数据流

整个流水线分三步走。第一步是文本检测,输入原始图片,输出若干个四边形文本框坐标。第二步是方向分类,对每个检测框裁剪出来的小图判断是否旋转了 180 度,如果是就翻正。第三步是文字识别,把每个框内的图像送进 CRNN 模型,输出字符序列和置信度。

数据流在代码里是这样串的:

# pipeline.py 核心调度逻辑 from detector import TextDetector from classifier import AngleClassifier from recognizer import TextRecognizer class OCRPipeline: def __init__(self, det_model, cls_model, rec_model): self.detector = TextDetector(det_model) self.classifier = AngleClassifier(cls_model) self.recognizer = TextRecognizer(rec_model) def run(self, img_path): img = self._load_image(img_path) # 第一步:检测文本框 boxes = self.detector(img) results = [] for box in boxes: # 第二步:裁剪并判断方向 crop = self._crop(img, box) angle = self.classifier(crop) if angle == 180: crop = self._rotate(crop, 180) # 第三步:识别文字 text, score = self.recognizer(crop) results.append({ "box": box, "text": text, "score": score }) return results

这里_crop函数负责按四边形坐标做透视变换,把倾斜的文本行拉正。_rotate只在方向分类置信度超过阈值时才执行,避免误翻转。识别结果里的score是平均字符置信度,后面过滤低质量结果会用到。

2.3 HTML 结构输出的实现方式

纯文本结果丢给下游用没问题,但如果你想在浏览器里直接看识别效果,HTML 结构就很有用。源码里html_render.py做了这件事:把每个识别框的坐标映射到原图尺寸,生成绝对定位的div,文字内容放在里面,背景透明,鼠标悬停时高亮。

# html_render.py 片段 def render_html(image_path, ocr_results, output_path): img = Image.open(image_path) w, h = img.size html_parts = [f'<div style="position:relative;width:{w}px;height:{h}px;">'] html_parts.append(f'<img src="{image_path}" style="position:absolute;top:0;left:0;">') for item in ocr_results: x_min = min(p[0] for p in item["box"]) y_min = min(p[1] for p in item["box"]) html_parts.append( f'<div style="position:absolute;left:{x_min}px;top:{y_min}px;' f'background:rgba(255,255,0,0.3);" title="score:{item["score"]:.2f}">' f'{item["text"]}</div>' ) html_parts.append('</div>') with open(output_path, 'w', encoding='utf-8') as f: f.write('\n'.join(html_parts))

这段代码生成的 HTML 可以直接用浏览器打开,文字覆盖在原图上,位置基本对齐。注意title属性里放了置信度,方便快速判断哪些识别结果可能有问题。如果图片路径是相对路径,生成的 HTML 和图片要放在同一目录下才能正常显示。

3. 本地跑通全流程:环境、模型、批量推理三步走

3.1 环境准备与依赖安装

源码包解压后,根目录下有一个requirements.txt,核心依赖就四个:onnxruntime、opencv-python、numpy、Pillow。我一般会建一个干净的虚拟环境,避免和系统里已有的包冲突。

# 创建虚拟环境 python -m venv ocr_env source ocr_env/bin/activate # Windows 用 ocr_env\Scripts\activate # 安装依赖,指定 onnxruntime CPU 版本 pip install onnxruntime==1.16.0 opencv-python==4.8.0.74 numpy==1.24.3 Pillow==10.0.0

版本号不是随便写的。onnxruntime 1.16 之后的版本对 ONNX opset 支持更好,但如果你用的是比较老的 CPU,建议降到 1.14 试试。opencv 用 4.8 是因为透视变换的warpPerspective在这个版本上对边界处理更稳定。装完之后跑一个简单的 import 测试,确认没有报错。

3.2 模型文件放置与路径配置

模型文件一般放在models/目录下,三个 ONNX 文件分别对应检测、方向分类和识别。源码里config.yaml控制路径:

det_model_path: models/det_model.onnx cls_model_path: models/cls_model.onnx rec_model_path: models/rec_model.onnx rec_char_dict: models/keys.txt det_input_size: [640, 640] rec_input_size: [32, 320] use_gpu: false

det_input_size是检测模型的输入尺寸,设成 640x640 在精度和速度之间比较平衡。如果你处理的图片普遍偏小,可以降到 480x480 提速。rec_input_size的高度固定 32,宽度 320 是识别模型能处理的最大字符宽度,超过这个宽度的文本行会被压缩,长文本识别率会下降。use_gpu设成 true 需要装 onnxruntime-gpu 版本,并且 CUDA 环境要配好,否则会回退到 CPU 但速度反而更慢。

3.3 单张图片推理与结果解析

跑单张图片用infer_single.py:

python infer_single.py --image test_invoice.jpg --output result.html

脚本内部会依次调用检测、分类、识别,最后生成 HTML。控制台会打印每个识别框的文字和置信度。我一般会关注两个指标:识别框数量是否和肉眼看到的文本行数一致,以及平均置信度是否低于 0.8。如果框数偏少,可能是检测阈值设高了;如果置信度普遍偏低,检查图片是否模糊或者分辨率太低。

3.4 批量处理与结果导出

批量处理用infer_batch.py,支持传入文件夹路径:

# infer_batch.py 核心循环 import os from pipeline import OCRPipeline pipeline = OCRPipeline("models/det_model.onnx", "models/cls_model.onnx", "models/rec_model.onnx") input_dir = "input_images" output_dir = "output_results" os.makedirs(output_dir, exist_ok=True) for fname in os.listdir(input_dir): if not fname.lower().endswith(('.jpg', '.png', '.jpeg')): continue img_path = os.path.join(input_dir, fname) results = pipeline.run(img_path) # 导出纯文本 txt_path = os.path.join(output_dir, fname + ".txt") with open(txt_path, 'w', encoding='utf-8') as f: for item in results: if item["score"] > 0.7: # 过滤低置信度结果 f.write(item["text"] + "\n") # 导出 HTML html_path = os.path.join(output_dir, fname + ".html") render_html(img_path, results, html_path)

这里加了一个置信度过滤,低于 0.7 的结果不写入文本文件。这个阈值可以根据实际数据调,发票类文档一般 0.7 够用,手写体可能要降到 0.5。批量跑的时候注意内存占用,每处理完一张图最好手动释放一下中间变量,否则图片多了容易 OOM。

4. 避坑指南:模型加载、图片预处理、结果后处理里的五个坑

4.1 模型加载报错 "Invalid ONNX model"

现象:运行时报onnxruntime.capi.onnxruntime_pybind11_state.InvalidArgument,提示模型文件无效。

原因:模型文件下载不完整,或者 ONNX 版本和 onnxruntime 版本不匹配。有些模型是用 opset 12 导出的,但 onnxruntime 1.10 以下不支持。

解决:先检查模型文件大小是否和官方发布的一致,然后用onnxruntime自带的工具验证:

import onnxruntime as ort sess = ort.InferenceSession("models/det_model.onnx") print(sess.get_inputs()[0].shape)

如果这行代码报错,说明模型文件本身有问题,重新下载。如果只是警告 opset 版本,可以忽略,但建议把 onnxruntime 升级到 1.16 以上。

4.2 检测框偏移严重,文字对不上位置

现象:生成的 HTML 里文字框和原图文字位置错位,尤其是图片被缩放过的。

原因:检测模型输出的坐标是基于模型输入尺寸的,需要按比例映射回原图尺寸。源码里_scale_boxes函数如果比例算错,就会偏移。

解决:检查_scale_boxes里的缩放因子是不是用原图宽高除以模型输入宽高。注意 OpenCV 读图是 BGR,Pillow 是 RGB,如果混用会导致通道顺序错误,虽然不影响坐标但影响识别率。

4.3 长文本行识别不全,后半截丢失

现象:一行很长的文字,识别结果只有前半部分。

原因:识别模型的输入宽度固定 320,长文本被压缩后特征丢失。

解决:在检测阶段把长文本框按宽度切分成多个子框,分别识别再拼接。源码里split_long_box函数就是干这个的,默认阈值是宽度超过 300 像素就切。如果切分后还是丢字,把rec_input_size的宽度调到 480 试试,但速度会慢一些。

4.4 方向分类误判导致文字倒置

现象:正常横向文字被翻转 180 度,识别结果全是乱码。

原因:方向分类模型对某些字体或背景的置信度不高,误判为倒置。

解决:把方向分类的置信度阈值从默认的 0.9 提高到 0.95,只有非常确定才翻转。或者直接在配置里关掉方向分类,前提是你的图片方向都是正的。

4.5 批量处理时内存持续增长

现象:处理几十张图后程序变慢甚至崩溃。

原因:ONNX Runtime 的 session 没有复用,每次推理都新建 session 会累积内存。

解决:把InferenceSession在 pipeline 初始化时创建一次,所有图片共用。另外 OpenCV 的imread返回的 numpy 数组要及时del并调用gc.collect()。我一般每处理 50 张图手动触发一次垃圾回收。

5. 进阶技巧:用置信度过滤和坐标排序提升可用性

5.1 按坐标排序还原阅读顺序

检测模型输出的框是无序的,直接按检测顺序输出文字,读起来会跳来跳去。我一般会按y坐标排序,同一行的再按x排序:

def sort_results(results, y_threshold=10): # 先按 y 坐标粗排 results.sort(key=lambda r: min(p[1] for p in r["box"])) # 同一行内按 x 排序 sorted_results = [] current_line = [results[0]] for r in results[1:]: if abs(min(p[1] for p in r["box"]) - min(p[1] for p in current_line[-1]["box"])) < y_threshold: current_line.append(r) else: current_line.sort(key=lambda r: min(p[0] for p in r["box"])) sorted_results.extend(current_line) current_line = [r] current_line.sort(key=lambda r: min(p[0] for p in r["box"])) sorted_results.extend(current_line) return sorted_results

y_threshold控制行间距判断,一般设 10 到 20 像素。这个排序对发票、表格类文档效果很明显,输出文本基本能按人眼阅读顺序排列。

5.2 置信度阈值动态调整

固定阈值 0.7 不一定适合所有场景。我的习惯是先跑一批样本,统计置信度分布,然后取 5% 分位数作为阈值。如果分布集中在 0.9 以上,说明图片质量好,阈值可以提到 0.85;如果分布很散,阈值降到 0.6 并人工抽检。

5.3 输出格式扩展

源码默认输出 HTML 和纯文本,但你可以很容易加一个 JSON 导出:

import json with open("result.json", "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=2)

JSON 里保留了每个框的坐标和置信度,方便后续做版面分析或者导入数据库。如果下游是搜索引擎,可以把文字和坐标一起存进去,支持按位置检索。

5.4 一个实际验证方法

拿一张包含中英文混排、数字、标点的测试图,跑完 OCR 后对比三个指标:字符准确率、行数一致率、阅读顺序正确率。字符准确率用编辑距离算,行数一致率看检测框数量和实际行数的比值,阅读顺序正确率靠人工抽检。我一般会准备 20 张不同场景的图做回归测试,每次改完参数跑一遍,确保没有明显退化。

从那以后我每次换模型或者调参数,都强制走一遍这个回归测试,不然很容易在某个场景上翻车。希望帮到你。

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

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

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

立即咨询