简介基于 RapidOCR 改造的完整 Python OCR 图片识别源码面向需要将图片文字快速转为可编辑文本的开发者和技术爱好者。项目已完整实现 OCR 功能内置 /engine/ocr 接口可接收 HTTP、本地文件、文件流等多种图片输入并返回标注好的 HTML 结构能直接嵌入其他页面或复制使用极大提升图片转文字的落地效率。压缩包共 46 个文件包含 23 个 Python 源码、17 个 pyc 编译文件、3 个 ONNX 模型文件以及配置 YAML、HTML 模板和依赖清单等整体仅 14.26MB目录结构清晰便于二次开发。目前已有 1739 人浏览学习社区关注度良好。读者可获得一套完整的 Web OCR 服务实现涵盖模型加载、图片预处理、文本检测识别、结果标注等环节并附有工具脚本与依赖说明适合中文场景下图片转文字、文档扫描识别、自动化信息采集等任务也是学习 RapidOCR 改造与接口封装的实用范例。1. OCR图片识别源码rapidOCR改造版到底能省多少事做图片转文字的时候我一开始想的是直接用现成OCR库返回纯文本但真正放到业务里才发现只有识别文字远远不够。表格、票据、截图里的每一行文字都需要知道它在原图里的位置否则后续根本没法对齐、没法编辑。这套源码的核心是把rapidOCR的检测、识别结果改造成了带坐标、带置信度的HTML结构前端拿到手就能直接渲染成可编辑的文字层。适合刚接触OCR、又不想从零搭模型的开发者也适合准备把OCR能力嵌入自己系统的后端工程师。这里不会要求你先了解深度学习训练流程所有模型都是现成的源码把复杂度封在了两个类里下面从原理到落地把这条链路完整拆一遍。2. rapidOCR推理链路拆解检测、方向分类、识别三段式与HTML输出设计很多人拿到源码第一反应是问“怎么用”但我建议先看懂它为什么这样设计。rapidOCR本身不是单一模型而是一条三段式流水线文本检测、方向分类、文本识别。改造后的源码把这条链路的输出重新组装成HTML本质上是在做“信息再组织”。只有把三段式里每一段输出的字段都搞清楚后续调参、排查HTML错位问题才有据可依。2.1 文本检测阶段定位文字区域并保留坐标rapidOCR先用检测模型扫全图输出一系列候选区域。每个区域不是简单的矩形框而是四角坐标因为印刷文字可能有倾斜四点坐标能还原任意四边形。源码里保存的是box字段四个点按左上、右上、右下、左下排序。做HTML输出时如果你直接用四点坐标去生成div会遇到一个天然矛盾CSS里的div只能矩形不支持任意四边形。常见做法是取外接矩形也就是min_x, min_y, max_x, max_y或者用transform: rotate近似。源码默认取外接矩形因为大多数截图场景文字是水平排列的这样处理不会造成明显误差。这里有个参数值得单独说检测阈值det_box_thresh默认通常在0.5左右。值调低浅色、模糊的弱文本更容易被召回但噪点也会变多值调高只保留高置信度区域漏检率上升。在票据、截图这类背景干净的场景我一般不动它如果是相机拍照、复杂背景推荐先从0.4试起。如果发现HTML里出现大量非文字方块多半是阈值偏低。各阶段模型的关键参数可以汇总成一张表排查时对照着找问题阶段模型输入模型输出关键参数文本检测整图检测框四点坐标det_box_thresh、det_limit_side_len方向分类检测框裁剪图角度类别use_angle_cls文本识别矫正后文本行图文本序列置信度rec_img_shape、rec_batch_num检测阶段输出的顺序并不一定是阅读顺序。不同版本里返回顺序可能随图片内文本排布变化改造版会在后处理阶段按坐标的y值从上到下、x值从左到右重新排序这样才能让HTML文本层读起来和人类阅读习惯一致。很多新手拿到结果直接渲染发现文字顺序乱掉就是忽略了排序这一步。2.2 方向分类阶段把倒置、旋转90度的图先“摆正”检测完成后rapidOCR会做一次方向分类判断每个文本区域里的文字是正向还是旋转过。分类输出是角度类别0度、90度、180度、270度。模型的输入是检测框裁剪出来的小图分类结果会传递给识别模型。源码里这一环节对用户是透明的但它直接影响到坐标映射和文本内容。如果一张图整体旋转180度检测框的相对坐标其实不变但文字方向是倒的识别出来的文本就是反的。HTML输出时如果你只渲染文本而不关心方向很可能在批量识别手机拍摄的图片时发现文字顺序全反。方向分类的作用就是在这个环节把图“摆正”再送进识别器。这里有一个容易被忽略的坑方向分类模型只是“建议”识别器该用哪个方向它不保证结果正确。所以源码里最终结果中带angle字段但一般只用于调试。我在处理多语言混合图片时会主动禁用方向分类因为分类模型本身有置信度也会消耗额外CPU时间。改造版里提供了一个开关use_angle_cls默认是开的如果图片来源都是系统截图方向一定是正的关掉这个开关可以节省一次推理。2.3 识别阶段把图像序列解码成可读文字识别模型负责把文本框内的图像变成字符序列。rapidOCR里使用的是基于CTC解码的识别结构输出的是概率序列再通过解码器去掉重复字符和空白标签得到最终文本。这一阶段与HTML输出的关系最密切因为识别结果不是简单的一行字符串它包含每个文本行的置信度。源码的score字段就是每个文本行的平均置信度范围0到1。HTML生成时我习惯用置信度来决定节点样式低于一定阈值时加一个low_confidence的CSS类方便前端高亮可疑区域。比如0.75以下视为需要人工核验0.5以下基本是误识别。这个阈值不是固定的截图场景可以放到0.6照片场景收到0.8没有绝对标准。识别模型的选型也很重要。rapidOCR提供多种识别模型默认的中文模型覆盖简体、繁体、英文和常用标点如果你的场景是纯数字比如物流面单可以换成数字专用模型识别率和速度都会更好。改造源码里可配置rec_model_path直接指向自己准备的其他onnx模型模型输入输出shape一致就能替换。这也是这套源码比封装死的OCR服务更灵活的地方。2.4 HTML结构化设计坐标归一化、文本转义与DOM节点现在把三段式结果拼成HTML。改造版的核心思想是每个识别文本框对应一个div用绝对定位放在原图上。输出HTML时需要把像素坐标转换成百分比坐标防止在不同缩放级别下错位。计算公式left box_left / image_width * 100% top box_top / image_height * 100% width (box_right - box_left) / image_width * 100% height (box_bottom - box_top) / image_height * 100%对应的CSS基础样式如下.ocr-box { position: absolute; white-space: pre-wrap; border: 1px solid rgba(0, 0, 0, 0.2); }这里的image_width/image_height是原始图片尺寸。如果直接在浏览器里预览常常会遇到一个问题图片显示时被CSS压缩或拉伸而坐标却没有同步缩放。解决办法是给容器设position: relative;图片用width:100%; height:auto;所有文本节点用百分比定位这样容器自适应时文本层也跟着走。文本内容直接嵌入HTML必须做转义否则出现、、会破坏DOM结构。源码里专门有一段html.escape的处理这一点很容易被自己重写的脚本忽略。另外识别结果里可能包含换行符必须给节点设white-space: pre-wrap;否则换行信息就丢了。常见翻车是HTML结构都出来了但换行和空格全部缩成一团就是因为忘了这个CSS属性。还有一个阅读细节HTML输出的节点顺序要按阅读顺序排列而不是按识别顺序。改造版会先按 y 坐标分组再按 x 坐标排序最终让 HTML 里 div 的顺序就是视觉上的阅读顺序。如果不做这步输出虽然不缺内容但读起来是乱的。2.5 改造选型理由为什么是rapidOCR而不是云端接口有人会问既然要输出HTML直接用云端OCR再拼HTML不行吗当然可以但有几个现实问题。首先图片识别对很多公司来说是敏感数据不能全部传到外部接口。rapidOCR完全本地推理断网也能用。其次云端接口每次返回的是不可控的JSON字段结构不一定带四点坐标。即使带坐标也可能要按次数计费批量跑起来成本不低。最后rapidOCR的模型是可替换的你可以根据业务不断调整识别模型而不是被一个固定接口锁死。当然rapidOCR也不是万能。它没有训练能力如果业务票据是特殊字体你可能需要单独训练模型再替换它也没有自动旋转大图的能力依赖方向分类做局部矫正。但作为“将图片转成带位置HTML”的底座它足够干净、足够快这也是源码选择它的原因。3. 源码复现跑通改造后的OCR接口并让识别结果变HTML接下来直接落地不绕弯子。源码包拿到手先别急着看每个文件按下面的顺序跑一遍先让输出HTML生成出来再回过来理解代码会顺手很多。3.1 环境准备依赖安装与模型文件确认项目基于Python依赖主要是rapidocr-onnxruntime。安装命令pip install rapidocr-onnxruntime如果机器上没有OpenCV还需要装轻量版pip install opencv-python-headless说明rapidocr-onnxruntime依赖 onnxruntime 和 numpy安装时通常会自动带上。opencv-python-headless是给不需要GUI的服务器环境用的如果你是在本地跑装opencv-python也可以但服务器上建议 headless 以减少依赖冲突。安装后先验证一下引擎能不能正常创建from rapidocr_onnxruntime import RapidOCR ocr RapidOCR() print(引擎初始化成功)如果输出成功说明环境没问题。这时第一次运行会下载模型文件下载位置一般在用户目录下的缓存文件夹里。要注意的是公司内网环境经常下载失败报错看起来像“连接超时”。遇到这种情况不要反复重试应该手动把模型文件下载好然后显式指定路径。源码里的初始化逻辑大致是这样ocr RapidOCR( det_model_pathmodels/ocr_det.onnx, rec_model_pathmodels/ocr_rec.onnx, cls_model_pathmodels/ocr_cls.onnx, )参数说明det_model_path是文本检测模型rec_model_path是文本识别模型cls_model_path是方向分类模型。这三个路径支持绝对路径和相对路径建议都用相对路径并保持和源码目录一致方便换机器部署。这里的文件名只是举例源码包里实际的文件名可能不同以你解压出的models目录为准。3.2 一次性跑通调用改造后的识别类并输出HTML源码不要求你直接操作RapidOCR裸返回而是封装了一个OcrHtmlEngine类。使用流程非常短from ocr_html import OcrHtmlEngine engine OcrHtmlEngine(use_angle_clsTrue) html_output engine.recognize_to_html(demo.png) with open(demo_output.html, w, encodingutf-8) as f: f.write(html_output)逻辑说明recognize_to_html内部先调用OCR识别再把识别结果转换成完整HTML字符串。这个HTML里已经嵌入了原图的base64数据所以发给别人时只需要传一个.html文件就能看效果。use_angle_clsTrue是开启方向分类如果确定图片方向没问题可以改成False来提速。如果想在命令行里直接跑源码包里一般会带一个demo.pypython demo.py demo.png运行成功后当前目录会生成一个demo_output.html用浏览器打开你应该能看到图片上悬浮着可选的文字每个文字区域都贴合原图位置。如果这一步通过就说明模型、坐标换算、HTML模板都正常。3.3 结构化结果说明box、text、score三个字段怎么用recognize_to_html适合直接看效果但如果要接自己的后端你需要拿到结构化数据。源码里对应的接口是recognize_to_dictitems engine.recognize_to_dict(demo.png) for item in items: print(item[text], item[score], item[box])返回的是一个列表每个元素是一个字典三个关键字段的含义如下字段类型说明textstr识别出的文本内容scorefloat该文本行的置信度0到1之间boxlist四个坐标点每个点[x, y]box的具体结构是[[x1,y1],[x2,y2],[x3,y3],[x4,y4]]顺序是左上、右上、右下、左下。这个顺序在计算外接矩形时直接用最小值和最大值就行不用重新排列。这里要提醒一个常见误区不要直接拿box当作HTML的left/top/width/height。HTML的定位需要的是外接矩形而不是平行四边形。如果直接用第一个点和第三个点去算宽高遇到倾斜文本时宽度会被算大文本层和原图错位。源码里统一用的是外接矩形换算这个逻辑在_box_to_position函数里。拿到items后还建议先做三条检查一是items数量是否明显异常如果一张营销海报只有两行字却被识别出五十个框大概率检测阈值太低噪声太多二是score是否普遍低于0.5如果绝大多数文本行都低于0.5可能是模型与图片类型不匹配三是box坐标是否越界比如 x 超过图片宽度说明预处理中缩放了图但没有同步坐标。这三条检查建议写成一个函数每次跑完批量自动打印告警。源码里没有集成这个但可以加在recognize_to_dict外层。3.4 坐标换算与HTML后处理的完整逻辑HTML后处理是整个改造里最核心的部分。下面这段是浓缩后的逻辑你可以拿着和源码里对应import html def box_to_style(box, img_width, img_height): xs [point[0] for point in box] ys [point[1] for point in box] left min(xs) / img_width * 100 top min(ys) / img_height * 100 width (max(xs) - min(xs)) / img_width * 100 height (max(ys) - min(ys)) / img_height * 100 return (left, top, width, height) def result_to_html_lines(results, img_width, img_height): lines [] for box, text, score in results: left, top, width, height box_to_style(box, img_width, img_height) lines.append( fdiv classocr-line>image_base64 base64.b64encode(open(img_path, rb).read()).decode(utf-8) html_template f !DOCTYPE html html head style .ocr-container {{ position: relative; display: inline-block; }} .ocr-container img {{ display: block; width: 100%; height: auto; }} .ocr-line {{ position: absolute; }} /style /head body div classocr-container img srcdata:image/png;base64,{image_base64} / {lines_html} /div /body /html 注意这里用了width: 100%; height: auto;图片最外层容器是inline-block这样文本层的位置会跟随图片缩放。如果你希望HTML文件保持原始尺寸可以不用width:100%但那样移动端查看不方便一般保留响应式。3.5 输出模式的调整单文件、外部图片、调试可视化源码提供了一个embed_image开关控制图片是转 base64 嵌入HTML还是外部引用。默认True生成单文件方便分享如果图片很大base64 会让HTML膨胀到几MB建议改成Falsehtml_output engine.recognize_to_html(demo.png, embed_imageFalse)改成False后HTML里的img srcdemo.png指向本地文件浏览器必须和图片在同一目录才能正常显示。这种模式适合做本地工具不适合分发。调试模式下还可以打开可视化输出。rapidOCR 原库支持把检测框画在原图上保存改造版里对应参数是output_visTrue会在识别后生成一张带框的图片方便核对检测区域是否准确。一般先打开可视化看检测框再检查HTML坐标两个都对上才说明链路没问题。4. 实战避坑rapidOCR改造版最常见的五个翻车点这一章写下我在复现和使用这套源码时真实踩过的坑。有些是环境问题有些是模型问题还有两个完全是自己没看文档导致的。每一条都按“现象、原因、解决”来写方便你直接对号入座。4.1 现象识别结果全是乱码或者空字符串第一次跑demo.py时输出HTML生成了但文字全是乱码还有的节点连文本都没有。我第一反应是代码写得有问题后来才发现是模型文件加载错了。原因出在初始化RapidOCR时我把识别模型路径指向了英文模型而图片内容是中文。文本识别模型和检测模型不一样识别模型直接决定字符集合加载错模型自然什么都对不上。更隐蔽的是如果rec_img_shape和模型期望的输入通道不匹配识别器可能输出空序列。解决方法是先确认模型类别。用下面的脚本打印模型输入输出import onnxruntime as ort sess ort.InferenceSession(models/ocr_rec.onnx) print(sess.get_inputs()[0].name, sess.get_inputs()[0].shape)识别模型的输入 shape 一般是[1,3,32,320]或[1,3,32,None]。如果不对检查模型文件是否完整、路径是否正确。再不行删掉缓存目录中自动下载的模型让程序重新下载。4.2 现象首次运行卡在模型下载几十分钟没反应在公司的开发服务器上跑日志停在一行“开始加载模型”然后长时间不动最后超时。这不是源码卡死而是模型自动下载被网络限制住了。rapidOCR 第一次运行时会从外部下载预训练模型内网环境经常连不上。解决方式有两种。第一种是先在有网环境把模型下好复制到服务器初始化和模型相关的路径全部指向本地文件ocr RapidOCR(det_model_pathmodels/ocr_det.onnx, rec_model_pathmodels/ocr_rec.onnx, cls_model_pathmodels/ocr_cls.onnx)第二种是提前准备好模型文件放置到固定目录确保程序不再走自动下载流程。从那以后我每次部署第一件事就是检查三个.onnx文件是否都在模型目录里而不是让它自动下载。这个检查脚本一般放在源码包的check_models.py里跑一下就能确认。4.3 现象HTML文本层错位文字跑到图片外面HTML生成出来了文字内容和原图也对得上但文字的位置明显偏移比如整段文字出现在图片左上角或者叠在了下一行上。我最初以为是坐标算错了逐行打印后发现坐标没算错问题出在HTML容器和图片尺寸的匹配上。现象的本质是识别出的坐标是原始像素坐标但HTML里的图片被CSS缩放到了不同的宽度。如果直接用像素值定位那肯定错位。源码里已经做了百分比换算但如果你改过容器样式比如给图片加了width: 80%或者设置了max-height百分比也会失效。解决方法是确保文本节点的百分比坐标是基于“原始图片宽高”计算的同时图片和容器不要混用固定像素尺寸。最好的状态是.ocr-container { position: relative; display: inline-block; } .ocr-container img { display: block; width: 100%; height: auto; }这样图片始终按容器宽度缩放文本节点也是基于原图百分比的绝对定位两者是同一参照系。如果你把容器改成width: 80%文本节点也会跟着缩因为它的参考容器是同一个父节点。4.4 现象CPU上识别速度慢得离谱一张图要十几秒用一台普通服务器跑单张图识别耗时超过十秒CPU打满。排查后有几个原因叠加在一起。第一个是初始化方式不对。我在循环里反复创建OcrHtmlEngine对象每一次都会重新加载模型、初始化推理会话这部分开销比识别本身还大。解决方法是把引擎对象在进程内只创建一次所有图片共用。第二个原因是模型太大。rapidOCR 默认加载的是服务版模型换成移动版或者小模型能快不少。源码里可以直接替换rec_model_path指向移动版模型。第三个是方向分类没关。如果图片本身没有旋转把use_angle_clsFalse加上能省一次推理。还有一个隐藏因素OpenCV 读图后没有做尺寸限制。rapidOCR 内部有个det_limit_side_len参数默认是 960超过这个边长的图会先缩放。如果设置为更大值大图识别会变慢但精度可能提高。批量任务里一般保持默认否则速度完全不可控。4.5 现象识别结果丢失换行整段文字挤成一行做截图识别时原图里明明是两行HTML 输出的文本层却出现在同一行甚至重叠。第一反应是坐标问题实际上坐标没问题是换行信息在两个环节丢失了。第一个环节是检测模型的合并逻辑当两行文字距离很近且行高差小于阈值检测框会合并成一个识别出来自然只有一行。这种情况需要调det_unclip_ratio或者后处理时按 y 坐标重新切分行。第二个环节是 HTML 渲染即使识别结果里带\ndiv 默认会忽略换行符必须加white-space: pre-wrap;。源码里已经在样式里加了这个属性但如果你提取文本自己渲染很容易漏掉。从工程角度一般会在后处理里对同一文本框内的多行做拆分取 y 方向投影直方图行间空隙超过行高的一定比例就切开再把切开的每一段分别生成 HTML div。这个逻辑在源码里叫split_multi_line默认不启用需要显式开启。遇到多行文本合并不严重时开这个开关能明显提高 HTML 文本层的可读性。5. 进阶用法把HTML结构化输出变成可点击、可编辑的标注层源码生成的HTML是静态文本层但它的结构化标签已经为交互做好了准备。再往前走一步就能变成一个轻量的人工标注工具适合用来校对识别结果。做法是给每个.ocr-line节点加上contenteditable属性和编辑事件。document.querySelectorAll(.ocr-line).forEach(el { el.addEventListener(click, () { el.setAttribute(contenteditable, true); el.classList.add(editing); }); el.addEventListener(blur, () { const editedText el.textContent.trim(); if (editedText ! el.dataset.original) { fetch(/api/ocr_results, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ id: el.dataset.id, text: editedText }) }); } el.removeAttribute(contenteditable); el.classList.remove(editing); }); });逻辑说明点击文本进入编辑模式失去焦点时对比修改前后的内容有变化就提交到后端。这里的>.ocr-line[data-conf^0.4], .ocr-line[data-conf^0.5] { background: rgba(255, 0, 0, 0.25); }这里style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
阅读完成 · 觉得有帮助?