
简介这是一套基于rapidOCR改造的完整OCR图片识别Python源码面向需要快速实现图片转文字、文本识别并生成可嵌入页面结构的开发者。项目已封装为Web服务内置/engine/ocr接口支持HTTP链接、本地文件、文件流等任意形式图片输入返回标记好的HTML片段可直接嵌入其他页面或复制使用。压缩包内共46个文件整体约14.26MB。其中23个py为Python源码含OCR引擎、Web服务与工具模块17个pyc为编译缓存3个onnx为推理模型文件另有txt依赖说明、html演示页面和yaml配置文件目录结构清晰便于二次开发与部署。目前已有1739人学习下载。通过这份资源可快速搭建具备完整OCR能力的服务启动run_web.py即可运行内部按rapid_ocr识别、web_service接口、common通用工具分层组织同时提供配置项与模型文件适合用于文档扫描、截图识别、验证码处理等场景也方便研究者阅读核心逻辑或迁移到自身项目中。1. 一套能直接改的 OCR 图片识别源码RapidOCR 改造版值不值得用做图像处理的人迟早会卡在同一个需求上给一张带表格、带标题、带页码的图片要的不是一坨识别出来的纯文本而是还能看出来这段文字原来排在哪、属于哪个块、是不是标题。这套 OCR 图片识别源码解决的就是这个事。它用 Python 实现完整识别流程核心引擎基于 RapidOCR 改造——不做服务端请求、不依赖外部接口模型和推理都在本地跑输出是带 HTML 结构的嵌套结果标题、段落、坐标框都给你标好。适合两类人一类是要把扫描件、截图批量转成可检索文本的工具开发者另一类是正在搭文档解析管道、想让 OCR 结果直接对接前端展示的工程实践者。这套代码把 RapidOCR 原始识别结果重新组织成 DOM 结构后续接 CSS、接 Json 导出都很顺。下面把它拆开讲哪些可以照抄哪些地方要按你的输入格式再改。2. 从 RapidOCR 改造说起识别引擎的选型逻辑与六个处理模块2.1 为什么是 RapidOCR而不是 Tesseract 或云接口选 OCR 引擎这件事上我在此之前有过几次翻车。Tesseract 的中文识别对复杂排版几乎是玄学轻量场景下准确率勉强能用一旦图片里出现表格线、倾斜文本或者低对比度水印输出就乱到没法收拾。云接口识别率高但牵扯到传图、鉴权、计费批处理几百张图的时候既慢又贵还要担心数据出网的合规问题。RapidOCR 恰好卡在一个舒服的位置基于 PaddleOCR 的模型转成 ONNX 格式后推理模型和推理代码全在本地CPU 就能跑单张图秒级出结果不依赖任何外部服务。实际对比下来同场景下它对印刷体的中文识别率明显比 Tesseract 稳。这个源码的定位不是从零写一个 OCR而是把 RapidOCR 当识别内核在它之上做了一层结构化改造。理解这一点很重要你拿到的代码里核心的文本框检测、文字方向分类、文字识别三个模型都来自 RapidOCR 的预训练权重改造的部分集中在结果重组——把 RapidOCR 输出的文本框坐标序列重新聚合成行、块、版面元素再生成对应的 HTML 结构。所以这套源码的能力上限取决于 RapidOCR 的模型质量而下限取决于改造层对坐标和上下文关系的处理是否合理。2.2 识别流程的六个处理模块整个识别管道被拆成六个模块每个模块做的事情相对单一调试时也容易定位问题模块职责输入输出图像预处理灰度化、缩放、方向校正原始图片字节或路径标准化的推理图片文本检测定位文字所在区域框预处理后的图片若干矩形坐标 (x1,y1,x2,y2)方向分类判断文本框内文字朝向检测结果裁剪图旋转角度0/90/180/270文字识别对校正后的文本框做字符识别方向校正后的裁剪图文本字符串与置信度版面重组按坐标关系聚合框为行和块所有文本框及识别结果有序的块结构HTML 输出把块结构映射为嵌套标签重组后的块结构HTML 文本或文件源码里对这六个模块的耦合做得比较干净每个模块都有独立的入口函数。我一般会在改代码时从「版面重组」和「HTML 输出」这两层入手——因为文本检测和文字识别是黑匣子改不动也不会去改真正影响输出结构质量的是后面两层。2.3 改造层解决了 RapidOCR 原生输出的什么问题RapidOCR 原始的识别结果是一组文本框的集合每个框带坐标和文字但彼此是平铺的。直接拿这种结果去建索引或者做前端展示会碰到三个麻烦一是阅读顺序丢失文字框按检测顺序输出跟人眼从上到下、从左到右的阅读习惯不一致二是段落的归属关系不明确一个段落被切成几个框之后没有信息告诉你它们是同一段三是标题和正文在语义上没有区分所有文字都是同一等级。这套改造版针对这三个问题分别做了处理按坐标排序重建阅读顺序按垂直重叠度和水平间距做行合并按字号和位置特征标记标题与正文。HTML 结构输出就是这个改造层的直接产物。3. 把识别结果变成 HTML核心代码拆解与结构设计3.1 项目文件结构与入口逻辑拿到源码包之后先看目录结构再动手。常见做法是按照采集、识别、输出三个层级来组织代码这套源码也遵循了类似的风格。核心文件大致是这样分布ocr_project/ ├── main.py # 命令行入口支持单张图片和批量目录 ├── ocr_engine.py # RapidOCR 引擎封装加载模型与推理 ├── layout_analyzer.py # 版面重组模块坐标聚合与阅读顺序 ├── html_builder.py # HTML 结构生成模块 ├── utils/ │ ├── image_utils.py # 图像预处理工具 │ └── text_utils.py # 文本清洗、置信度过滤 ├── models/ # ONNX 模型文件存放目录 └── output/ # 识别结果输出目录main.py 是整个流程的入口用一个简单的参数解析器接收输入路径和输出格式。改代码时我习惯先在这个文件里把逻辑通读一遍确认默认参数是什么再决定调哪个模块。这套源码的入口支持单图识别和目录批量识别两种模式批量模式下会自动遍历目录下的所有图片文件并把每个文件的 HTML 结果写到 output 目录下。3.2 OCR 引擎封装模型加载与推理参数ocr_engine.py 是跟 RapidOCR 交互的唯一入口。原始 RapidOCR 库提供的接口比较簿一次推理返回一组结果但批量场景下你需要控制它的资源占用和线程行为。封装层做的事情是加载 ONNX 模型、设置推理会话参数、暴露一个recognize(image)方法给上层调用。# ocr_engine.py 核心片段 import numpy as np from rapidocr_onnxruntime import RapidOCR class OcrEngine: 封装 RapidOCR 推理过程统一输入输出格式 def __init__(self, model_dir: str models, use_cuda: bool False): self.engine RapidOCR( det_model_pathf{model_dir}/det.onnx, cls_model_pathf{model_dir}/cls.onnx, rec_model_pathf{model_dir}/rec.onnx, use_cudause_cuda, # CPU 环境下保持 False ) # 置信度阈值低于此值的识别结果将被丢弃 self.conf_threshold 0.45 def recognize(self, image) - list: 参数说明 image: 可以是图片路径也可以是解码后的 numpy 数组 返回[ [坐标框, 文本, 置信度], ... ] 的列表 result, _ self.engine(image) if not result: return [] # 过滤低置信度结果减少后续版面重组的干扰 filtered [] for box, text, score in result: if score self.conf_threshold: filtered.append({ box: np.array(box, dtypenp.float64), text: text.strip(), score: float(score) }) return filtered这里有两个值得注意的细节。第一个是use_cuda参数默认设 False确保在只有 CPU 的环境里也能跑通。如果部署在多机 GPU 集群上可以打开但单机 CPU 场景打开反而会因为显存搬运增加延迟。第二个是置信度阈值conf_threshold我一般会先按默认值 0.45 跑一批样图观察被过滤掉的框里是否有需要的内容。如果发现漏识别严重可以往下降到 0.3如果发现大量误识别噪声框就往上升。3.3 版面重组从文本框序列到行与段落拿到 RapidOCR 返回的文本框集合之后最烦的就是把这些框按视觉习惯重新排成段落。layout_analyzer.py 里的核心逻辑是两遍遍历第一遍把所有框按 y 坐标排序第二遍检查每个框与已归组框的重叠关系决定是合并到同一行、同一块还是新建一个块。# layout_analyzer.py 核心片段 def group_boxes_into_lines(boxes, overlap_threshold0.35): 把检测出的文本框按垂直坐标重叠度聚合成视觉行。 参数说明 boxes: ocr_engine.recognize() 返回的框列表 overlap_threshold: 垂直重叠率阈值两个框垂直投影重叠率超过 该值时认为是同一行太小容易拆散太大会误合并 # 按 y 坐标升序排列保证处理顺序基本符合阅读方向 sorted_boxes sorted(boxes, keylambda b: b[box][0][1]) lines [] for box in sorted_boxes: placed False for line in lines: if _vertical_overlap(box[box], line) overlap_threshold: # 与已有行合并按 x 坐标插入对应位置 line[boxes].append(box) placed True break if not placed: # 当前框与任何已有行都没重叠新建一个行 lines.append({boxes: [box]}) # 合并后对每个行内部的框按 x 坐标排序保证从左到右 for line in lines: line[boxes].sort(keylambda b: b[box][0][0]) return lines这个模块是整个源码里最值得花时间调的地方。overlap_threshold 的取值直接决定行合并的效果取大了两栏文本会被当成一行左右栏内容交错取小了同一行内被误切成两段的文本框合并不起来。针对不同的输入图片类型这个参数值得单独做成配置文件而不是写死在代码里。我手里的场景里扫描书籍用 0.35手机拍摄的歪斜图片要降到 0.25 才能稳定分行。3.4 HTML 结构生成坐标信息如何映射为嵌套标签html_builder.py 是整个改造层的最终输出端。它把 layout_analyzer 生成的块结构映射为 HTML 节点核心策略是每个块对应一个p标签块内每行对应一个span识别出的标题用h2或h3标记图片中的题注用em。坐标信息写进标签的># html_builder.py 核心片段 def build_html(lines, image_width, image_height): 把版面重组结果输出为带坐标信息的 HTML 结构。 参数说明 lines: group_boxes_into_lines() 返回的行列表 image_width/height: 原图尺寸用于归一化坐标计算 import html body_parts [] for line in lines: # 行内第一个框的 y 坐标作为块级定位基准 first_box line[boxes][0][box] y_top int(first_box[0][1]) # 判断该行是否可能是标题字号较大或位于图片顶部区域 box_height int(first_box[2][1] - first_box[0][1]) is_heading box_height height_threshold(image_height) tag h2 if is_heading else span # 拼接行内文本保留单行顺序 row_text .join(b[text] for b in line[boxes]) escaped html.escape(row_text) #># 创建独立虚拟环境避免污染全局 Python python -m venv ocr_env source ocr_env/bin/activate # Windows 下用 ocr_env\Scripts\activate # 优先安装 ONNX 运行时依赖再装 RapidOCR pip install onnxruntime opencv-python numpy pip install rapidocr-onnxruntime1.3.24rapidocr-onnxruntime的版本要特别注意。它跟onnxruntime的主版本号有对应关系RapidOCR 1.3.x 系列要求 onnxruntime 1.7但某些新版本 onnxruntime 会调整推理接口的默认行为导致模型加载报错或结果异常。我踩过的实际案例是装完最新 onnxruntime 之后RapidOCR 推理结果全部为空回退到 1.16.3 之后恢复正常。如果你装完跑出来 result 是空的先检查 onnxruntime 版本不要急着怀疑模型文件损坏。4.2 单张图片识别与批处理命令源码入口支持两种模式用--input指定输入图片或目录用--output指定输出目录。默认输出是 HTML 文件文件名保持原图名称加.html后缀# 单张图片识别结果输出到 output/ 目录 python main.py --input sample.png --output output/ # 批量识别目录下所有 jpg/png 图片 python main.py --input imgs_dir/ --output output/ --batch批量模式下程序会自动排序文件按顺序处理并把每张图的结果写入独立的 HTML 文件控制台会打印每张图的处理耗时和识别出的行数。跑大量图时我习惯加--max-side 1280参数把图片最长边压缩到 1280 以内推理速度能提高 2 到 3 倍识别率的损失在小字号文本上才比较明显。4.3 四个关键参数与推荐区间调参集中在三个文件里参数相互影响改的时候要做好记录不然很容易调乱。整理成参数表放在手边比每次翻代码强多了参数所在位置作用推荐区间我的常用值conf_thresholdocr_engine.py识别置信度过滤阈值0.3 ~ 0.60.45overlap_thresholdlayout_analyzer.py行合并垂直重叠率下限0.25 ~ 0.450.35max_sideutils/image_utils.py图像最长边缩放上限960 ~ 19201280height_threshold 系数html_builder.py标题判定的行高阈值系数0.03 ~ 0.060.04这几个参数中height_threshold的系数是相对图片高度的比例。它的作用是计算「行高超过图片高度百分之几就当成标题」印刷体文档设 0.04 即可PPT 截图这类大字内容可以调高到 0.08 减少误判。调参时每次只动一个参数跑同一组测试图对比 HTML 输出找感觉。4.4 识别效果验证结构化输出怎么看判断改造效果好不好不要只看识别出的文字对不对还要看 HTML 结构是否还原了原始版面。我的验证方法是准备三张不同类型的测试图一张纯文字扫描页、一张带标题和表格的文档页、一张多栏排版截图。跑完之后依次检查三件事读取顺序是否从左到右、从上到下标题标签是否落在了正确的位置同一个段落是否被合并进同一个块里。第三点是这套源码与裸 RapidOCR 的差异所在如果识别出的 HTML 里一个自然段被拆成五六个span但没有包在同一个块内优先缩小 overlap_threshold 值让行合并更宽松。5. OCR 避坑手册五个反复踩中的坑和对应修法5.1 识别结果为空模型路径拼错还是 onnxruntime 版本问题现象运行时self.engine(image)返回空结果控制台看不到任何报错。原因有两类第一类发生在 Windows 环境模型路径写法用了绝对路径但目录层级和源码里models/预期不一致第二类是 onnxruntime 升级到 1.17 以上后推理会话默认行为有变化导致输出解析不到有效数据。解决方式先在 ocr_engine.py 里打印模型路径确认文件真实存在然后回退 onnxruntime 版本到 1.16.3重新跑。这个过程帮我省了不少时间——第一次遇到时我甚至在怀疑模型文件下载不完整反复重新解压都没用。5.2 多栏排版被从左到右合并成一条线现象两栏排版的文档页左栏第一行和右栏第一行被合并到同一个视觉行文字内容左右交错HTML 里span的文本完全是乱的。原因overlap_threshold 设置过高导致左右两栏中垂直方向有交集的文本框被误判成同一行。解决把 overlap_threshold 从 0.4 降到 0.3 以下再观察行分组结果。如果页面里还有跨栏的标题和页眉需要额外增加一个判断两个框的水平距离超过图片宽度的 20% 时即使垂直重叠也不属于同一行。5.3 图片带 EXIF 旋转信息导致方向分类失效现象手机拍的竖版照片识别出来全是横着的或者错乱但同一个文件在 Windows 自带的看图工具里显示是正的。原因图片的 EXIF 元数据里存了方向标记OpenCV 的imread读取时不解析 EXIF 方向RapidOCR 拿到的是未校正方向的原始像素数组方向分类模型在某些角度下工作不稳定。解决在 image_utils.py 的预处理里显式读取 EXIF 方向并旋转图像或者用 OpenCV 的imread加cv2.IMREAD_IGNORE_ORIENTATION参数配合自定义旋转逻辑。加完这个处理后手机图识别的稳定度明显提升。5.4 低对比度水印图片识别率骤降现象白底浅灰水印字、印章背景上的文字识别率从 90% 掉到 50% 以下很多关键数字漏识别。原因预处理只做了灰度化和缩放没有做对比度增强浅色文字在前景和背景之间没有足够的像素梯度差文本框检测在第一步就漏掉了大量文字区域。解决在图像预处理阶段加入自适应对比度拉伸OpenCV 里用createCLAHE把灰度图的局部对比度增强后再送进引擎。注意参数clipLimit不宜设太高2.0 到 3.0 之间比较合适设高了会让纸张纹理也被增强成噪声框。5.5 HTML 输出编码乱码写入文件时没用 utf-8现象生成的 HTML 文件用浏览器打开中文显示为乱码。原因html_builder.py返回的是字符串但写入文件的语句没指定编码Windows 下 Python 默认用区域语言编码GBK写入浏览器读取时按 UTF-8 解析就对不上。解决写入文件时显式传encodingutf-8并且在 HTML 头部加上meta charsetutf-8。这是个极其不起眼的小问题但排查起来很烦——识别结果看起来完全正常问题出在输出环节。6. 一个进阶技巧用 HTML 结构做坐标级版式还原前面讲的都是 HTML 结构作为「半成品」输出落到真实业务里还差一步把这个结构变成能直接展示、可检索、或者能导出为规范文档的形式。我常用的一招是利用代码里已经输出的>// 前端对照层渲染示例 document.querySelectorAll(.ocr-result [data-x]).forEach(el { const x parseFloat(el.dataset.x); const y parseFloat(el.dataset.y); const w parseFloat(el.dataset.w); const h parseFloat(el.dataset.h); // 生成半透明标记框叠加在原图对应位置 const marker document.createElement(div); marker.style.left x px; marker.style.top y px; marker.style.width w px; marker.style.height h px; marker.className ocr-marker; container.appendChild(marker); });同一个坐标数据还能做另一件事把 HTML 结构转换成 JSON 对象直接灌给全文索引。做法也不复杂——遍历 DOM 树提取标签名、文本内容和坐标组成一个扁平的记录数组每行一条记录。这样后续接 Elasticsearch 建索引时就能实现「按文档名搜全文、再按坐标定位回原图」的检索链路。我后来做的一个文档管理系统就是这么干的OCR 识别出的 HTML 存一份JSON 索引存一份前端展示用对照层结构复用了三处没改动代码。从那以后我每次拿到带坐标信息的 OCR 输出都强制自己先想「这个坐标除了画框还能干什么」再决定怎么存、怎么传、怎么展。这套源码给你的是起点后面接什么业务是你自己的事——但凡是带坐标的结构化输出都值得往前多想一步希望帮到你。本文还有配套的精品资源点击获取