资讯动态

PaddleOCR 离线 exe 打包与内网批量 OCR 实践

发布时间:2026/10/24 5:42:35 来源:尧图企业网站定制
简介这是一份围绕 PaddleOCR 构建离线 OCR exe 工具的完整打包资料面向无 Python 运行环境、希望输入图片路径即可获得识别结果的使用者也适合想研究桌面化封装与依赖组织的开发者参考可用于内网、断网或不便安装解释器的办公与实验环境。压缩包共 2000 个文件约 279.22MB包含 319 个 py 源码、319 个 pyc、232 个 pyd、139 个 dll以及 enc、tcl、txt、png 等运行时配置与资源文件源码、字节码、扩展模块和动态库相互配合构成可脱离开发环境运行的目录结构。已有 3789 人学习下载说明其在离线文字识别与打包实践场景中具有一定参考价值。读者可获得可执行的离线识别程序及完整依赖免去配置 Python 环境同时能借源码与目录组织理解 PaddleOCR 调用、识别结果写入 txt、模型与依赖随包分发等要点为二次开发和打包排错提供基础。1. 内网机器上跑 OCRPaddleOCR 打包 exe 离线工具到底解决什么问题把 PaddleOCR 打包成 exe 的离线工具真正难的不是识别率而是让一台不能装 Python、不能联网的 Windows 机器也能批量出结果。上周一个做档案数字化的朋友丢来一台内网机三千多张扫描件现场只允许双击运行不允许装环境。这个项目就是干这件事GUI/CLI 收任务本地模型推理结果落 txt/json/csv模型目录外置换模型不用重新打包。适合要把 OCR 交付到客户现场、又不想让客户碰 Python 环境的人也适合想拿 PaddleOCR 做离线批处理工具的开发者。2. 工程架构与目录从模型文件到 exe 的离线边界怎么划打包离线工具最怕一上来就写pyinstaller -F main.py跑是能跑换台机器就翻车。我一般先把边界划清楚哪些东西必须进 exe哪些东西必须留在外面。PaddleOCR 的推理层、业务代码、UI 资源可以进包模型文件、配置文件、日志、临时输出目录必须外置。这样客户换模型不用重新打包现场排查也能直接看日志和配置不用把 exe 当黑匣子猜。这个项目的架构不复杂但每一层都有明确的交付目标。最上面是 GUI 和 CLI 两个入口GUI 给现场操作员用CLI 给批处理脚本用中间是任务层负责扫描文件、排队、失败重试、进度回传下面是 OCR 引擎层封装 PaddleOCR 的检测、方向分类、识别三个模型最后是导出层把结果写成 txt、json、csv并保留坐标和置信度。四层之间用配置和数据结构解耦不让 UI 直接碰推理代码。2.1 运行链路从选择文件夹到结果落盘一次完整任务的链路是入口接收路径 → 文件扫描器过滤图片和 PDF → 任务队列逐条处理 → 预处理器做旋转、缩放、灰度等可选操作 → OCR 引擎返回文本块和坐标 → 后处理器做空结果过滤、置信度阈值过滤、按行合并 → 导出器写文件。链路里最容易出问题的是“路径”和“编码”不是模型本身。开发机上跑得好好的打包后工作目录变成 exe 所在目录相对路径全部失效所以项目里统一用Path和配置中的绝对路径来定位模型与输出目录。任务队列建议做成单生产者单消费者GUI 只负责投递任务和刷新进度不直接调用 OCR。这样批量处理时界面不会卡死失败任务也能单独记录。对于几百张图片单进程顺序跑足够上千张再考虑多进程但多进程会把 PaddleOCR 的模型加载多次内存直接上去现场机器吃不消。常见做法是主进程只加载一次模型用线程池控制并发或者干脆顺序跑用稳定的吞吐换内存。启动顺序也要固定先读配置再解析模型目录再初始化日志最后才创建 OCR 引擎。很多人把引擎初始化放在 UI 构造函数里结果模型路径错了窗口还没出来就崩了现场只看到闪退。正确做法是先把错误写进日志再弹一个可读的错误框告诉操作员“模型目录不存在”或“检测模型文件缺失”而不是丢一个 Python traceback。2.2 目录结构与资源外置策略项目目录我一般按下面这样拆核心原则是“代码和资源分离模型和配置可替换”。下表是文件清单不是安装包清单实际交付时会把models、config、logs留在 exe 同级目录。路径作用是否进 exemain.pyGUI/CLI 入口解析参数是app/ui/窗口、进度条、文件选择是app/core/ocr_engine.pyPaddleOCR 初始化与推理封装是app/core/model_locator.py冻结路径与外部模型目录解析是app/core/batch_runner.py批量任务、失败重试、进度回调是app/core/exporter.pytxt/json/csv 导出是config/app.yaml模型路径、阈值、输出格式否models/det/检测模型否models/rec/识别模型否models/cls/方向分类模型否logs/运行日志否output/默认输出目录否模型目录再往下拆一层让每个模型有独立的inference.pdmodel和inference.pdiparams这类文件。项目里不把模型塞进 Python 包也不在运行时自动下载。配置里写相对路径时解析规则是“先找 exe 同级models再找开发环境项目根目录models最后才报错”。这条规则能同时兼容开发机和客户现场减少“开发机能跑、打包不能跑”的玄学问题。配置文件建议用 YAML字段少但必须稳定。det_model_dir、rec_model_dir、cls_model_dir三个路径分开写use_angle_cls控制是否启用方向分类confidence_threshold控制结果过滤output_format控制导出类型。配置不要写死在代码里否则客户想调阈值只能找你重新打包。日志目录和输出目录也走配置默认放在 exe 同级避免写到C:\Program Files下面没有权限。2.3 打包边界与构建步骤打包边界决定交付形态。单文件 exe 看起来干净但每次启动都要解压到临时目录模型如果也在里面启动慢、杀软误报也多。我一般用 onedir 模式exe 和依赖库放一个目录模型、配置、日志放同级外部目录。这样升级模型只替换models升级程序只替换 exe 和_internal客户现场不用重新配环境。构建步骤按顺序走第一在干净的虚拟环境里安装依赖避免把开发机全局包带进去第二先跑通开发模式下的单图识别和批量识别第三准备外置模型目录确认不联网也能初始化第四写 spec 文件显式收集 PaddleOCR 的动态库和隐藏导入第五在另一台没装 Python 的 Windows 机器上做首次启动验证第六补日志、错误码和版本号。每一步都要留记录不然出问题只能靠猜。提示构建机尽量和客户机器保持相同的系统位数和运行库环境不要在 64 位开发机上打 32 位包也不要把开发机的 CUDA 环境带进 CPU 交付包。3. 从零跑通开发环境、模型准备和 exe 构建的三段式流程这一章按“能跑起来”的目标走不追求一次到位。先在开发机跑通 Python 入口再把模型目录固定下来最后才打包。顺序反了打包后出问题很难定位是代码、模型还是打包器。下面命令里的包版本不写死实际项目里用requirements.txt锁版本避免今天能打包、明天依赖升级又缺 DLL。3.1 开发环境与依赖锁定创建虚拟环境安装 PaddleOCR 推理依赖、GUI 依赖和打包依赖。CPU 交付包只装 CPU 版推理库不要装 GPU 版否则包体积和动态库数量都会上去。下面的命令是开发机初始化流程-i用你实际可用的镜像源没有就删掉。python -m venv .venv .venv\Scripts\activate python -m pip install --upgrade pip pip install paddlepaddle paddleocr opencv-python pyyaml pyside6 pyinstaller pip freeze requirements.txt这段命令做了三件事建隔离环境、装依赖、把当前环境锁定到requirements.txt。paddlepaddle是推理运行时paddleocr提供检测和识别封装opencv-python处理图像读写pyyaml读配置pyside6做 GUIpyinstaller负责打包。实际项目里 GUI 可以用 Tkinter 减体积但 Pyside6 的进度条和文件选择更稳现场操作员不容易点错。依赖锁定后先写一个最小入口验证模型能加载。不要一上来就跑 GUI先用 CLI 跑单张图把模型路径、日志、输出目录三个变量确认下来。最小验证脚本只做一件事读一张图调用 OCR打印第一行文本。能打印出来说明推理链路通了打印不出来先查模型目录和动态库不要怀疑识别效果。# smoke_test.py from pathlib import Path from paddleocr import PaddleOCR # 模型目录全部用绝对路径避免工作目录变化导致找不到 base Path(__file__).resolve().parent / models ocr PaddleOCR( det_model_dirstr(base / det), rec_model_dirstr(base / rec), cls_model_dirstr(base / cls), use_angle_clsTrue, langch, show_logFalse, ) result ocr.ocr(str(Path(samples/demo.png)), clsTrue) print(result[0][0][1][0] if result and result[0] else no text)这段代码的关键参数是三个*_model_dir它们必须指向本地模型目录不能留空。留空时 PaddleOCR 可能走默认模型下载逻辑离线机器直接卡住。use_angle_clsTrue表示启用方向分类扫描件倒置时有用show_logFalse关掉推理日志避免 GUI 控制台刷屏。result的结构是“页 → 文本块 → [坐标, (文本, 置信度)]”取文本时要判空不然空图片会抛异常。3.2 模型目录和离线配置模型准备的原则是“提前下载、离线放置、配置显式指定”。检测、识别、方向分类三个目录分开配置里写相对路径由model_locator统一解析成绝对路径。下面是一个可用的配置示例字段名和代码里的读取逻辑保持一致。# config/app.yaml model: det_model_dir: models/det rec_model_dir: models/rec cls_model_dir: models/cls use_angle_cls: true lang: ch cpu_threads: 4 runtime: confidence_threshold: 0.5 output_format: json output_dir: output log_dir: logsdet_model_dir是文本框检测模型rec_model_dir是文字识别模型cls_model_dir是方向分类模型。cpu_threads控制 CPU 推理线程数现场机器核数少就调低不然界面会卡。confidence_threshold低于阈值的文本块直接丢弃减少噪声。output_format可以选txt、json、csv批量交付时 json 保留坐标最有用。配置解析代码要处理三种情况开发环境从项目根目录找打包后从 exe 同级找用户手动指定路径优先。常见做法是写一个resolve_path函数先判断路径是不是绝对路径再依次拼接候选目录最后返回存在的那个。找不到就抛一个带候选路径的错误日志里写清楚“我找过哪些目录”现场排查不用猜。模型放好之后断网跑一次smoke_test.py。如果这时还在下载模型说明初始化参数没生效或者配置没有传到PaddleOCR。离线工具的第一条验收标准就是“拔网线能启动、能识别”这一条不过后面打包没有意义。3.3 构建 exe 与首次启动验证打包用 spec 文件不用命令行一把梭。spec 里显式写datas、hiddenimports、binaries把 PaddleOCR 的动态库和资源收进去。下面是一个简化片段重点是collect_all和外部模型目录不打包。# build.spec from PyInstaller.utils.hooks import collect_all datas, binaries, hiddenimports [], [], [] for pkg in [paddle, paddleocr, cv2, yaml]: d, b, h collect_all(pkg) datas d binaries b hiddenimports h a Analysis( [main.py], pathex[], binariesbinaries, datasdatas, hiddenimportshiddenimports, excludes[matplotlib, pandas], ) pyz PYZ(a.pure) exe EXE(pyz, a.scripts, exclude_binariesTrue, nameocr_offline) coll COLLECT(exe, a.binaries, a.datas, nameocr_offline)collect_all把包内的动态库、数据文件、隐藏导入一起收集适合 Paddle 这种依赖多的库。excludes里排掉不用的重型包能减体积。COLLECT输出的是目录形态模型、配置、日志放在ocr_offline同级不要打进_internal。打包完成后把整个目录复制到一台没装 Python 的 Windows 机器双击 exe先看日志有没有“模型目录不存在”再看单图识别能不能出结果。首次启动验证按清单走窗口能打开、模型能加载、单图能识别、批量能跑完、输出文件能打开、日志有记录、断网不报错。任何一项失败先看logs/app.log再看 exe 同级目录结构。不要直接在开发机上判断打包成功开发机有 Python 环境很多缺失依赖会被兜住。4. 核心模块拆解模型定位、OCR 初始化和批量导出三段代码这一章拆三个最容易被打包破坏的模块模型定位、引擎初始化、批量导出。三个模块的共同点是都依赖“路径”和“配置”打包后工作目录变化、临时解压目录变化都会让它们失效。代码按项目里实际会写的方式给参数说明放在代码后面。4.1 冻结路径下的模型定位打包后的程序可能从sys._MEIPASS解压临时目录运行也可能从 exe 同级目录读外部资源。模型必须优先读外部目录不能读临时目录否则客户换模型要重新打包。下面函数先判断是否冻结再按“用户指定 → exe 同级 → 项目根目录”的顺序找。# app/core/model_locator.py import sys from pathlib import Path def app_root() - Path: 返回程序可写根目录打包后是 exe 所在目录开发时是项目根目录。 if getattr(sys, frozen, False): return Path(sys.executable).resolve().parent return Path(__file__).resolve().parents[2] def resolve_model_dir(configured: str) - Path: 把配置里的相对模型路径解析成绝对路径找不到时给出候选列表。 raw Path(configured) candidates [] if raw.is_absolute(): candidates.append(raw) else: candidates.append(app_root() / raw) candidates.append(Path.cwd() / raw) for path in candidates: if path.exists() and path.is_dir(): return path.resolve() raise FileNotFoundError(f模型目录不存在候选路径{[str(p) for p in candidates]})app_root()是路径解析的锚点。冻结状态下用sys.executable的父目录开发状态用源码文件往上推。resolve_model_dir()先处理绝对路径再拼 exe 同级和当前工作目录。找不到时把候选路径拼进异常信息现场看日志就知道程序到底在找哪里。参数configured来自 YAML不要在这里写死models/det。4.2 OCR 引擎初始化与参数引擎初始化要懒加载不要放在模块导入时执行。模块导入时初始化一旦模型缺失整个程序启动就崩连错误框都弹不出来。下面类把初始化包在ensure_ready()里调用方先读配置、再初始化、再推理。# app/core/ocr_engine.py from paddleocr import PaddleOCR from .model_locator import resolve_model_dir class OcrEngine: def __init__(self, config: dict): self.config config self._ocr None def ensure_ready(self): if self._ocr is not None: return self._ocr model self.config[model] self._ocr PaddleOCR( det_model_dirstr(resolve_model_dir(model[det_model_dir])), rec_model_dirstr(resolve_model_dir(model[rec_model_dir])), cls_model_dirstr(resolve_model_dir(model[cls_model_dir])), use_angle_clsbool(model.get(use_angle_cls, True)), langmodel.get(lang, ch), cpu_threadsint(model.get(cpu_threads, 4)), show_logFalse, ) return self._ocr def recognize(self, image_path: str): ocr self.ensure_ready() result ocr.ocr(image_path, clsTrue) return result[0] if result else []ensure_ready()做两件事第一次调用时初始化 PaddleOCR之后复用同一个实例。det_model_dir、rec_model_dir、cls_model_dir都走resolve_model_dir()保证打包后也能找到。cpu_threads从配置读现场机器性能差就调小。recognize()返回第一页的结果空结果返回空列表调用方不用处理None。show_logFalse关掉底层日志项目自己的日志统一写文件。初始化失败要捕获异常并转成可读错误不要直接把 traceback 丢给操作员。常见错误包括模型文件缺失、动态库加载失败、内存不足。日志里记录模型路径和异常类型错误框只提示“模型加载失败请查看日志”。这样现场人员能按提示找维护人员不会乱点。4.3 批量任务与结果导出批量任务的核心是“单条失败不影响整批”每张图独立 try失败记录到结果列表最后统一导出。导出格式用 json 保留坐标和置信度csv 给表格工具用txt 给纯文本交付。下面代码是批量处理和 json 导出的简化版。# app/core/batch_runner.py import json from pathlib import Path from .ocr_engine import OcrEngine SUPPORTED {.png, .jpg, .jpeg, .bmp, .tif, .tiff} def collect_images(input_dir: Path): return [p for p in input_dir.rglob(*) if p.suffix.lower() in SUPPORTED] def run_batch(input_dir: str, output_dir: str, engine: OcrEngine, threshold: float 0.5): images collect_images(Path(input_dir)) records [] for image in images: try: blocks engine.recognize(str(image)) lines [] for block in blocks: text, score block[1][0], float(block[1][1]) if score threshold: lines.append({text: text, score: score, box: block[0]}) records.append({file: str(image), ok: True, lines: lines}) except Exception as exc: records.append({file: str(image), ok: False, error: str(exc)}) out Path(output_dir) / result.json out.parent.mkdir(parentsTrue, exist_okTrue) out.write_text(json.dumps(records, ensure_asciiFalse, indent2), encodingutf-8-sig) return outcollect_images()递归扫描支持的图片后缀PDF 可以另起一个转换分支先把页转成图片再进 OCR。run_batch()对每张图单独捕获异常失败也写进records最后统一落盘。threshold控制置信度过滤低分文本不进结果。ensure_asciiFalse保证中文不转义utf-8-sig让 Excel 打开 csv 不乱码json 用这个编码也能兼容部分 Windows 工具。导出层不要和推理层混在一起exporter.py可以再拆 txt、csv、json 三个函数批量任务只负责收集结果。这样二次开发加一种输出格式不用改推理代码。批量跑完输出一条汇总日志总文件数、成功数、失败数、耗时。现场人员看到汇总就知道有没有漏图。5. 避坑与排查打包后模型、动态库和路径的翻车现场打包离线工具的血泪经验基本集中在这几类模型下载、动态库缺失、路径错乱、杀软误报。每一条我都见过现场版现象看起来像玄学原因都很具体。下面按“现象 → 原因 → 解决”写排查时按顺序看日志和目录不要一上来就重装系统。5.1 启动就联网下载模型现象双击 exe 后进度条卡住日志里出现下载超时或连接失败断网机器直接报错。原因通常是PaddleOCR初始化时没有传本地模型目录或者配置里的相对路径在打包后失效底层走了默认模型下载逻辑。解决方法是三个模型目录全部显式传绝对路径配置解析失败时直接报错退出不要回退到默认下载。构建前在断网环境跑一次初始化确认不触发网络请求。日志里打印最终解析到的模型路径现场一眼能看出找没找对。5.2 动态库缺失导致闪退现象开发机运行正常复制到客户机器双击没反应或者命令行里看到DLL load failed。原因是打包器没有收集 Paddle 依赖的推理动态库、OpenMP 运行库或图像处理库单文件模式解压后路径又变了。解决方法是 spec 里用collect_all收集paddle、paddleocr、cv2的 binaries构建环境保持干净不要混装多个 Python 版本。打包后在没装 Python 的机器上测试别只看开发机结果。命令行启动 exe 能看到更完整的错误输出GUI 闪退时先切命令行复现。5.3 中文路径和输出乱码现象模型放在中文目录下找不到或者导出的 csv 用 Excel 打开乱码。原因是部分底层库对非 ASCII 路径处理不一致加上打包后工作目录变成 exe 所在目录相对路径全部偏了。解决方法是模型和输出目录尽量用英文路径配置解析统一用Path.resolve()转绝对路径导出文本统一utf-8-sig。日志文件也用固定编码不要依赖系统默认编码。现场如果必须用中文目录先在开发机复现一次确认模型加载和输出都正常再交付。5.4 杀软误报和包体积过大现象exe 被拦截、双击没反应或者整个交付目录几百兆客户拷贝都嫌慢。原因是单文件自解压行为容易被杀软盯上加上 Paddle 依赖本身就不小GPU 版依赖更大。解决方法是改用 onedir 模式CPU 交付包只装 CPU 推理库spec 里排除不用的重型包给 exe 做代码签名或至少提供校验值。包体积不要靠删模型解决模型外置后程序本体能小很多。现场加白名单要提前沟通别等交付当天才处理。注意排查顺序固定为“看日志 → 看模型目录 → 看动态库 → 看路径编码”不要同时改三个地方不然问题复现不了。6. 二次开发换模型、加接口和做增量升级的三个改造点二次开发不建议直接改ocr_engine.py里的初始化参数先把模型配置抽成注册表config/models.yaml里写多套模型运行时按profile选。换模型只改配置不改代码验证方法是用同一批固定样张跑新旧模型对比文本行数和低分块比例。加接口可以在批量任务外面套一层轻量 HTTP 服务请求传图片路径返回 json现场其他系统就能调。接口层只做参数校验和任务投递不重复写推理逻辑。# app/api/server.py from fastapi import FastAPI from pydantic import BaseModel from app.core.ocr_engine import OcrEngine app FastAPI() engine OcrEngine(config{}) # 实际从配置加载 class OcrRequest(BaseModel): image_path: str app.post(/ocr) def ocr_one(req: OcrRequest): blocks engine.recognize(req.image_path) return {lines: [b[1][0] for b in blocks if b[1][1] 0.5]}这个接口只做最小闭环image_path必须是服务端可访问的绝对路径返回文本列表。上线前用curl测单图、空图、错误路径三种情况确认错误码和日志对得上。增量升级的做法是把版本号写进配置升级时只替换models和 exe保留config、logs、output。每次发版前在干净机器上走一遍“解压 → 启动 → 单图 → 批量 → 断网”流程。从那以后我每次打离线包都强制走一遍断网验证不再相信开发机上的“应该没问题”。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑