尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
基于rapidOCR的图片转HTML:CPU上实现带排版的OCR识别
简介这套基于 rapidOCR 改造的完整 OCR 图片识别 Python 项目源码面向需要快速落地图片转文字服务的开发者可直接将识别能力嵌入 Web 应用或本地工具。项目已实现 OCR 识别并生成结构化的 HTML 标记结果内置 /engine/ocr 接口可接受 http 图片、本地文件、文件流等多种输入方式返回的带标记 HTML 能直接嵌入其他页面或复制。资源共 46 个文件以 23 个 Python 源码文件为主另有 17 个 pyc 编译文件、3 个 onnx 推理模型、配置文件、示例页面与依赖清单压缩包整体约 14.26MB。目录结构清晰包含 rapid_ocr 核心模块、web_service 服务层和 common 公共工具方便按模块阅读与二次开发。目前已有 1739 人学习下载适合有一定 Python 基础、希望理解 OCR 服务搭建或复用现成识别方案的开发者。1. 先把话说清楚一个把图片变成带排版HTML的OCR项目整理资料的时候最烦的就是对着截图手打文字。尤其那种票据、聊天记录、扫描件一页几十行敲完还得回头核对半天时间就没了。这个OCR图片识别项目解决的问题很直接把图片丢给一个Python脚本几秒钟后拿到一个HTML文件浏览器一打开图片上的文字按原来的位置整整齐齐排好哪句话在哪一行、置信度是多少一目了然。核心是基于rapidOCR改造的识别引擎把PaddleOCR的模型能力搬到了onnxruntime上纯CPU机器就能跑不需要GPU。适合做图片转文字、批量整理扫描件、给报表截图生成可检索版本的人新手照着命令能跑通熟手可以直接改它的HTML输出结构接自己的业务。2. 技术选型与项目结构rapidOCR省掉了什么、HTML结构留下了什么2.1 为什么是rapidOCR而不是直接上PaddleOCRPaddleOCR的识别效果确实好但它的安装方式是全家桶式的paddlepaddle框架本身就好几百兆再叠加CUDA、cuDNN的环境匹配在纯CPU的服务器或者一台配置普通的Windows开发机上光装环境就能劝退一批人。rapidOCR的做法是把PaddleOCR训练好的模型——文本检测det、方向分类cls、文字识别rec——全部转成onnx格式用一个轻量的onnxruntime做推理封装。安装就是一个pip包模型文件首次运行自动拉到本地。对常见的中文、英文、数字混合场景识别效果足够用。选择它的核心原因有三个。第一轻整个推理链路只依赖onnxruntime不绑定任何深度学习框架。第二CPU友好一张1080p的截图识别耗时大概在1到2秒批量处理完全能接受。第三模型文件是纯onnx部署到内网、离线环境都方便。这个项目讲的是「改造」改造点不在识别算法本身而在识别结果怎么用——把坐标、文本、置信度组织成一个和原图同坐标系的HTML结构这才是它对二次开发最有价值的地方。2.2 目录结构每个文件负责什么整个项目的结构非常简单核心就四个Python文件加一个模型目录。拿到源码包后你会看到这样的布局ocr_project/ ├── main.py # 命令行入口参数解析调度整个流程 ├── ocr_engine.py # RapidOCR的封装负责模型加载和推理 ├── html_builder.py # 识别结果渲染成HTML坐标绝对定位 ├── utils.py # 图片读取、base64编码等公共方法 ├── requirements.txt # 依赖清单 └── models/ # 本地模型文件目录离线部署时使用main.py是入口你所有命令行参数都在这里解析。ocr_engine.py把RapidOCR的初始化、推理、结果格式化包了一层业务代码不需要直接接触onnxruntime的细节。html_builder.py是这项目区别于普通OCR脚本的关键——它把engine返回的坐标框和文字组织成一个个绝对定位的div叠在base64内嵌的原图上。utils.py里放着图片读取这类公共方法。这个分层是刻意的。engine层只负责「认出文字和位置」builder层只负责「把结果画出来」中间靠一个统一的识结果结构box、text、score衔接。后面你想把输出改成JSON、XML或者直接写进数据库只动builder层就够了这也是我后面第6章要演示的改造思路。2.3 为什么输出HTML而不是TXT或JSON很多OCR脚本输出的是纯文本把所有识别到的文字按顺序拼起来。对纯文本阅读场景够用但一旦遇到发票、合同、表格这类版式敏感的资料纯文本会把排版信息全丢光。比如一张报销单「项目名称」和「金额」的对应关系在图片里是靠横排位置表达的纯文本输出后你根本分不清哪个数字对应哪个字段。这个项目输出HTML的思路是把每个识别框映射成一个绝对定位divdiv的left、top、width、height直接取识别框的坐标div内容就是识别文本鼠标悬停能看到置信度。图片本身用base64内嵌进HTML整个文件自包含拷到哪都能打开不需要附带原图。对于「给截图做标注」「生成可检索的资料存档」这类场景这种HTML比纯文本实用得多。3. 核心模块拆解从图片到HTML的完整识别链路3.1 图片加载与预处理OpenCV读图、灰度化、缩放图片加载是整个流程的第一关。这里有一个容易翻车的细节OpenCV的imread对中文路径不友好Windows下图片路径带中文时它会静默返回None。所以utils.py里封装了一层先用numpy的fromfile读字节再交给imdecode解码import cv2 import numpy as np def load_image(path: str) - np.ndarray: # 先用numpy读字节流再解码避免中文路径导致imread返回None data np.fromfile(path, dtypenp.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR) if img is None: raise ValueError(f图片读取失败: {path}) return cv2.cvtColor(img, cv2.COLOR_BGR2RGB) def preprocess(img: np.ndarray, max_side: int 1280) - np.ndarray: h, w img.shape[:2] scale min(1.0, max_side / max(h, w)) if scale 1.0: img cv2.resize(img, (int(w * scale), int(h * scale)), interpolationcv2.INTER_AREA) return img这段代码的逻辑说明load_image解决的是路径编码问题np.fromfile读的是原始字节imdecode能正确解析GBK编码路径下的文件比直接imread稳定得多。preprocess做的是等比例缩放max_side是长边的上限超过1280就缩下来。为什么要缩onnxruntime在CPU上对输入尺寸敏感长边超过2000像素时推理耗时成倍上涨而识别精度不会明显提升。INTER_AREA插值在缩小图片时保留边缘信息效果最好OCR场景下比默认的双线性插值更稳。实际调用时main.py里是这样衔接的img load_image(args.image) img preprocess(img, max_sideargs.max_side)3.2 调用RapidOCR推理拿到带坐标的识别结果识别引擎封装在ocr_engine.py里。RapidOCR的初始化支持传入model_dir参数指向本地模型目录这样在内网无外网环境下也能运行。推理方法接收numpy数组返回的result列表每个元素包含三个部分四个角点坐标、识别文本、置信度。from rapidocr_onnxruntime import RapidOCR class OCREngine: def __init__(self, model_dir: str | None None): if model_dir: # 显式指定本地模型目录适合离线环境 self.engine RapidOCR(model_dirmodel_dir) else: self.engine RapidOCR() def recognize(self, img: np.ndarray) - list: # 返回结构: [[box, text, score], ...] # box 是四个角点: [[x1,y1],[x2,y2],[x3,y3],[x4,y4]] result, elapse self.engine(img) if not result: return [] return [(item[0], item[1], item[2]) for item in result]这里要重点说明result的结构。每个item是一个长度为3的列表item[0]是box角点坐标是关键中的关键——它决定了后续HTML定位的精度。item[1]是识别出的文本字符串item[2]是置信度分数范围0到1越接近1说明识别越可靠。elapse是RapidOCR返回的性能信息包含检测、方向分类、识别各阶段的耗时通常只用来打日志。我一般会把它打出来批量处理时能看出哪张图推理慢方便定位问题。models目录的优先级高于默认行为传了model_dir就完全走本地文件不再联网下载。3.3 行合并与排序把散落的文字框整理成段落这是整个项目里最容易「翻车」的地方。RapidOCR返回的检测框顺序是模型推理的原始顺序不是阅读顺序。如果不做重排直接拼HTML文字会乱七八糟地叠在一起。常见的现象是同一行的文字被拆成七八个框上下两行的框交替出现。解决办法是按坐标做行合并。先算出每个框的中心点按y坐标聚类成行同一行内再按x坐标排序def group_into_lines(ocr_items, y_tol10): records [] for box, text, score in ocr_items: xs [p[0] for p in box] ys [p[1] for p in box] # 用外接矩形的坐标代替角点后续定位更稳定 records.append({ x: min(xs), y: min(ys), w: max(xs) - min(xs), h: max(ys) - min(ys), cx: sum(xs) / 4, cy: sum(ys) / 4, text: text, score: score, }) # 先按中心y排再按中心x排 records.sort(keylambda r: (r[cy], r[cx])) lines [] for r in records: if lines and abs(lines[-1][cy_avg] - r[cy]) y_tol: lines[-1][items].append(r) lines[-1][cy_avg] sum( i[cy] for i in lines[-1][items] ) / len(lines[-1][items]) else: lines.append({cy_avg: r[cy], items: [r]}) for line in lines: line[items].sort(keylambda i: i[cx]) return lines这段代码里y_tol是行合并的容差默认10像素。它的含义是两个文字框中心点y坐标差距在10像素以内就视为同一行。这个值不是玄学字号28px的正文行间距通常在10到15px之间10px能分出大部分正常排版的行。排序逻辑分两步走先按cy和cx全局排一遍再按照行合并的结果重排。注意cy_avg是动态更新的每加入一个新框就重新计算这一行所有框的中心y均值避免一行里第一个框的y偏得离谱导致后续框被错误分流。这种动态均值比固定阈值更抗噪声。3.4 HTML生成器把识别结果映射成带定位的网页html_builder.py的核心任务是将排序后的行数据渲染成HTML。设计思路是原图以base64形式内嵌到HTML里作为定位基准识别文本用绝对定位的div覆盖在对应位置。这样原图和文字天然对齐浏览器缩放时文字框跟着图一起缩放不会错位。import base64 def build_html(img_path, lines, scale1.0): with open(img_path, rb) as f: b64 base64.b64encode(f.read()).decode(utf-8) divs [] for line in lines: for it in line[items]: divs.append( fdiv classocr-box fstyleleft:{it[x]}px;top:{it[y]}px; fwidth:{it[w]}px;height:{it[h]}px; ftitle置信度:{it[score]:.2f} f{it[text]}/div ) html f!DOCTYPE html html langzh headmeta charsetutf-8titleOCR识别结果/title style body {{ position: relative; margin: 0; background: #f5f5f5; }} img {{ display: block; max-width: 100%; height: auto; }} .ocr-box {{ position: absolute; border: 1px solid rgba(0, 150, 255, .7); background: rgba(255, 255, 0, .15); font-size: 14px; line-height: 1.4; color: #333; overflow: hidden; cursor: pointer; }} .ocr-box:hover {{ background: rgba(255, 255, 0, .4); }} /style /head body img srcdata:image/png;base64,{b64} alt原图 {.join(divs)} /body /html return html注意坐标体系的细节。div的left、top直接取了识别框在像素坐标系里的值而图片是通过CSS设置了max-width: 100%实际渲染宽度不等于原始像素宽度。所以文字框也是绝对定位在同一个body坐标系里图片缩放时div跟着缩放比例天然一致。这里有个隐含假设div的定位基准是body左上角图片恰好也从body左上角开始。实际使用时如果图片跟body之间出现了边距需要在body样式里把margin清零这个代码里已经处理了。4. 本地跑通环境准备、命令行调用与效果验证4.1 环境准备虚拟环境与依赖安装先把环境隔离好。Python版本建议3.8到3.103.11以上对onnxruntime的兼容性需要踩些坑。创建虚拟环境然后安装依赖python -m venv venv source venv/bin/activate # Windows下执行 venv\Scripts\activate pip install -r requirements.txtrequirements.txt内容如下版本号给的是下限保证拉到的版本能用rapidocr-onnxruntime1.3.10 opencv-python4.5.0 numpy1.21.0安装完成后先做一个快速验证确认RapidOCR能正常初始化python -c from rapidocr_onnxruntime import RapidOCR; e RapidOCR(); print(engine ok)如果这一步能打印出engine ok说明模型加载没问题。首次执行这个命令时RapidOCR会把三个onnx模型下到本地缓存目录这步可能需要等一会儿。如果实在等不到用后面第5.1节的方法做离线部署。4.2 命令行调用参数说明与常见用法环境就绪后一张最基本的识别命令就一句话python main.py -i sample.png -o output.html执行完以后当前目录下会生成output.html浏览器打开就能看到原图和叠在上面的识别文字框。除了-i和-omain.py还提供两个高频参数python main.py -i sample.png -o output.html --threshold 0.6 --max-side 1600参数含义如下表参数默认值作用-i / --image必填待识别图片路径支持png/jpg/bmp-o / --outputoutput.html输出HTML文件路径--threshold0.5置信度过滤阈值低于该值的识别结果被丢弃--max-side1280图片长边缩放上限超过则等比缩小threshold这个参数建议先保持0.5跑一遍然后打开HTML看看有没有明显识别错的框——比如把水印、背景纹理识别成文字的框。如果发现误识别的框比较多把threshold提到0.6到0.7之间会立竿见影地干净很多。如果发现漏掉了一些字迹较淡的文字说明阈值高了往0.4降。4.3 效果验证如何判断识别结果是否可靠打开生成的HTML先做三件事。第一看整体布局是否对齐文字框和图片上的原始文字位置是否重合。如果框整体偏左或偏下说明HTML的坐标系和图片渲染坐标系有偏移去检查body的margin和padding。第二抽查几个置信度低于0.7的框看文字内容是否准确。第三确认行顺序是否符合阅读习惯——从左上到右下同一行的文字是否合并成了一行。RapidOCR识别一个混合中文和数字的表格截图准确率通常在90%以上。置信度低于0.5的框大概率是误检比如把横线识别成文字、把带纹理的背景识别成字符。HTML里的title属性已经写入了置信度鼠标悬停即可查看不需要额外操作。5. 常见问题与避坑记录跑通这个项目最容易踩的四个坑5.1 模型文件加载卡住rapidOCR首次运行离线没法初始化现象执行验证命令或跑main.py时程序卡在模型加载阶段状态停住不动等很久也没结束。有些环境直接报网络超时错误。原因RapidOCR首次运行时需要下载det、cls、rec三个onnx模型文件到本地缓存这一步会走网络。我在内网环境部署时遇到过一次整个build在无外网机器上跑卡了十分钟才发现一直在重试拉取。解决在一台能联网的机器上先执行一次初始化让它把模型缓存到位然后找到缓存目录一般在用户目录的.rapidocr文件夹下把模型文件复制到项目models目录代码里改成RapidOCR(model_dirmodels)完全断网也能跑。从那以后我再也不让生产环境的OCR脚本做首次自动下载一律预置模型文件。5.2 中文路径图片读不出来OpenCV的imread直接返回None现象在Windows下图片路径或者文件名带中文时脚本报「图片读取失败」或者直接空结果。原因OpenCV的imread内部使用的是系统编码处理路径而Python传递给它的是UTF-8字符串两者不一致时它不报错只是返回None。想要定位这个问题很迷惑因为图片路径明明是对的。解决办法就是用3.1节封装的load_image用np.fromfile读字节流再imdecode解码。同理输出HTML时如果原图路径带中文open()不会出问题但建议输出文件名也用英文避免浏览器端和文件系统之间编码不一致带来的奇怪行为。5.3 识别结果顺序全乱不重排坐标直接拼HTML现象生成的HTML里文字位置是对的但阅读顺序七零八落——左下角的文字排在右上角前面同一行里右边的框排在左边前面。原因RapidOCR返回的检测框顺序是按模型内部逻辑排列的它按「检测置信度从高到低」输出和阅读顺序无关。如果不做行合并和排序HTML里的文字顺序就是乱序的。解决强制走一遍group_into_lines。如果用了这个函数还是乱多半是y_tol设得太小把同一行但因为字号波动导致y坐标偏差超过阈值的框拆成了两行调大到15或20试试。5.4 低清晰度图片识别率低预处理参数调整现象手机随手拍的工牌、名片或者老旧的扫描件识别框明显变少或者识别出一些毫无逻辑的文字。原因图片分辨率低、光照不均、文字边缘模糊检测模型找不到足够的文本区域特征。这属于模型能力的物理上限但预处理能挽回一部分。我一般会在preprocess里加一步自适应阈值增强转成灰度图后对局部区域计算阈值并二值化文本线条和背景的反差会被拉大检测框能捞回一些边缘文字。这对于背景是浅色、文字颜色深的图片效果比较明显。如果图片本身文字就糊成一团预处理也救不了只能换更高清的源图。6. 二次开发把输出从HTML换成JSON只需要动一个文件这个项目分层最合理的地方在于识别逻辑和渲染逻辑完全解耦。ocr_engine.py只管输出box、text、score的结构化数据html_builder.py只管消费这些数据。所以你要给项目换一个输出格式根本不用碰识别引擎。给main.py加一个--format参数支持html和json两种输出。先看参数解析部分怎么改parser.add_argument(--format, choices[html, json], defaulthtml, help输出格式默认html可选json)然后在主流程里识别和排序完成之后按format分发到不同的渲染函数if args.format json: payload [] for line in lines: for it in line[items]: payload.append({ text: it[text], bbox: [it[x], it[y], it[w], it[h]], score: round(it[score], 4) }) with open(args.output, w, encodingutf-8) as f: json.dump(payload, f, ensure_asciiFalse, indent2) else: html build_html(args.image, lines) with open(args.output, w, encodingutf-8) as f: f.write(html)改造的核心思路是json输出把识别结果转成纯数据每条记录包含text、bbox、score三个字段。bbox用的是外接矩形的x、y、宽、高比原始四角点更容易被下游业务消费——比如前端要画高亮框时直接拿这四个字段设置CSS就够了。注意score做了四位小数截断避免浮点数尾数把JSON文件撑大。验证方法很简单拿同一张测试图分别跑一遍html和json两种格式对比两条记录里的bbox和text是否一致再对比stdout日志里识别出的行数是否相同。正常情况两种格式来自同一份lines数据结果必然一致如果不一致说明模板渲染层有副作用得排查build_html里是否松散地改动了lines结构。从那以后我每次接到「加个导出格式」这类需求第一反应是先看输出层接口而不是去翻识别逻辑。核心识别其实早就稳定了真正天天在变的是渲染端。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

长江流域shp数据从能用走向好用:坐标系、投影与水文处理全攻略

长江流域shp数据从能用走向好用:坐标系、投影与水文处理全攻略

简介:长江流域shp数据包是面向GIS研究者、城市规划与环保人员的矢量数据集,用于流域空间可视化、水资源管理及灾害风险分析。压缩包共44个文件,大小约2.09MB,以shp、shx、dbf、prj等格式为主,涵盖省级边界、地级市、湖…

📅 2026/10/16 3:21:34
如意报表V3安装部署与插件开发实战指南

如意报表V3安装部署与插件开发实战指南

简介:如意报表(Ming-WebReport-V3)安装程序面向Web开发、系统集成与企业报表管理等人员,用于在Web环境中快速部署报表工具,实现数据可视化、报表设计、插件集成和性能优化。压缩包共408个文件,大小26.45MB&…

📅 2026/10/16 3:21:34
小O物流通:计算地址点位是否在多边形范围(配送区域)内(地理围栏)

小O物流通:计算地址点位是否在多边形范围(配送区域)内(地理围栏)

一、摘要小O地图物流通是基于互联网地图实现物流数据管理的工具软件,提供地址管理、片区划分、里程计算、配送规划等功能,兼容EXCEL和WPS软件。具有易用、高效、稳定的特点。可提供个性化定制开发服务。效果很直接——配送点分布一目了然,信息…

📅 2026/10/16 3:16:34
MORE NEWS

更多资讯

📰

MFC ListCtrl 导出 Excel 实战:COM 初始化、编码与进程管理

简介:这份资源面向使用 MFC 进行桌面开发的程序员,聚焦于将 CListCtrl 控件中的列表数据导出到 Excel 表格这一常见需求。内容围绕 COM 自动化接口展开,涵盖创建 Excel 应用程序对象、遍历 ListCtrl 行列写入单元格、保存工作簿以及释放 COM …

📰

PHP动态读取MySQL列名与数据:mysqli与PDO实践

“Displaying MySQL Column Names and Values via PHP”这句话,看上去只是一行作业题或者需求标题,但真在项目里碰到“我要把这张表的字段名和内容一起展示出来”的时候,你会发现它远没有一句SQL那么简单。尤其是做内部数据查询工具、报表导出…

📰

Cursor AI编辑器入门:从安装到实战的完整指南

最近两个月我身边越来越多朋友在问同一个问题:“AI 写代码到底怎么上手?”他们不是专业程序员,有的是运营,有的是产品,有的刚学 Python 没多久。刷了一圈视频,看到别人在编辑器里敲几个字就生成了一整个项目…

📰

Gradle核心机制与多模块构建实战:从原理到性能优化指南

有人问过我一个挺实际的问题:既然已经有了那么多构建工具,为什么每次聊到现代项目的工程化,绕不开的还是 Gradle?我的回答是,它不只是把一个编译打包的过程自动化了,而是把“构建”这件事本身重新设计了一遍…

📰

同城代驾平台开发实战:从源码到企业级系统的完整落地

代驾这个行业,看着简单,做起来其实坑很深。用户打开App下单,司机骑辆折叠车赶过来,把人送回家,订单结束、扣费、评价,逻辑上好像并不复杂。但真正把一套“代驾系统”从一个能演示的Demo,做成一个…

📰

文件时间属性修改全攻略:批量操作、工具选型与避坑指南

提到“文件时间属性修改神器”,我第一反应就是笑——因为上周我刚用它救回一批老照片的拍摄时间,家里母上大人差点把相机时钟错乱的历史照片全删了。这玩意儿听起来冷门,但真到用的时候,谁用谁知道:整理文件归档、修复…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬