ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python OCR识别图片写入Excel并打包exe,实现办公自动化

Python OCR识别图片写入Excel并打包exe,实现办公自动化 上个月朋友扔给我一个活儿说他手上有两三百张设备铭牌照片都是型号、序列号、日期这类参数需要整理成Excel表格上交。刚开始他想找人手工录入一算成本直接劝退。我说这事用Python十分钟能写个脚本——OCR把图片文字抠出来openpyxl写进Excel再用PyInstaller打包成exe以后他拿到任何一台Windows电脑上双击就能跑连Python环境都不用装。这篇文章就把我这次完整落地的过程拆开讲从技术选型、环境准备、核心代码、踩坑排查到最终打包成exe的每一步。既照顾刚接触Python的新手也提供老手可以直接拿来用的完整实现。如果你也想把“图片转Excel”这种重复劳动自动化或者正在纠结OCR库怎么选、打包后怎么保证能跑这篇应该能帮你少走不少弯路。1. 为什么要把“OCR识别Excel写入exe打包”拼在一起做1.1 这个需求的真实来源从手工录入到自动化我接触到的这类需求绝大多数来自非技术岗位的同事或小团队电商运营要对账截图、仓库管理员要录入送货单、行政人员要整理名片和信息卡、工程人员要归档设备铭牌。他们的共同点是——电脑里有一大堆图片但最终交付物是Excel表格。之前最常见的做法是肉眼看着图片一个个字段敲进表格。这个流程有三个致命问题第一是慢一张图平均要敲一到两分钟几百张图就是大半天第二是容易错长时间盯屏后漏行、串列非常普遍第三是交差的时候如果遇到“excel无法复制粘贴”这种剪贴板抽风的情况整个人心态直接崩掉。用Python来做这件事本质上就是把“人眼看图人脑识别人手录入”替代成“算法识别规则清洗程序写入”。OCR负责识图openpyxl负责写表格至于打包exe是为了把运行环境一起封装好让不懂Python的人也能使用这个工具。三件事拼在一起才算把需求闭环了。1.2 技术选型对比PaddleOCR、EasyOCR、Tesseract怎么选OCR库这块我实际对比过三个主流方案Tesseract、EasyOCR、PaddleOCR还简单试过rapidocr。这里直接给结论省得你再花时间做对比测试。方案中文识别效果安装难度模型体积运行速度适用场景Tesseract一般需要额外下载语言包需要安装系统程序相对繁琐中快印刷体英文、简单文档EasyOCR良好pip安装模型较多较大慢对小语种支持较好PaddleOCR优秀pip安装需要匹配PaddlePaddle版本大快中文图片、复杂版面、弯曲文字RapidOCR优秀pip安装无需PaddlePaddle小快轻量部署、打包exe场景如果是处理中文图片我基本只推荐PaddleOCR或RapidOCR。Tesseract在中文场景下需要自己训练或调参才能达到理想效果而且它在Windows上的安装方式容易劝退新手。EasyOCR精度尚可但速度偏慢识别一张中等分辨率图片可能要两三秒批量处理时会觉得煎熬。PaddleOCR的布局分析和角度纠正能力对“手机随便拍的照片”非常友好因为实际场景里很少有一张端端正正的图片多少都会有点透视、倾斜或反光。而RapidOCR是PaddleOCR模型的一个独立推理实现不依赖PaddlePaddle框架模型体积小很多后面打包exe时优势明显。这次教程我先以PaddleOCR为主线讲因为它的资料最多、遇到问题最容易搜到解决方案。1.3 整体流程设计数据怎么从图片流到Excel整个链路的输入输出非常清晰理清数据流之后代码写起来就不会乱输入一张或多张图片路径可以是单个文件、文件夹路径或者拖拽到程序窗口的多个文件。第一步处理对图片做必要的图像预处理灰度、二值化、对比度提升这一步不是必须的但能显著提升识别率。第二步处理调用OCR识别接口拿到包含文字内容、坐标位置、置信度的结构化结果。第三步处理对结果做结构化清洗把无用的空白行、低置信度结果过滤掉必要时按坐标排序保证多条文字的顺序符合阅读习惯。输出用openpyxl按固定列写入Excel每张图片的识别结果占据对应的行或区块循环处理最后一键保存。这个链路里最容易被人忽略的是第三步。很多人以为OCR返回什么就直接写什么结果写进Excel的文字顺序乱七八糟根本没法用。实际上OCR返回的文字通常带坐标信息只有按坐标排序后才能还原出“从左到右、从上到下”的原始布局这一点后面代码部分会细讲。2. 环境准备80%的坑都出现在这个阶段2.1 Python环境与虚拟环境配置如果你电脑上已经装了Python 3.8到3.11之间的版本可以直接跳过这步。我这台机器用的是Python 3.9.13PaddleOCR在3.9下表现最稳。太新的Python版本比如3.12甚至3.13有些PaddlePaddle的预编译轮子可能还没同步容易出现安装失败。新建一个干净的虚拟环境是打包exe的前提条件之一。原因很简单PyInstaller打包时会扫描当前环境里的所有依赖如果系统Python里装了一堆和项目无关的包打包出来的exe体积会无端变大还可能触发依赖冲突。用虚拟环境能保证打包产物只包含必要依赖。python -m venv ocr_env ocr_env\Scripts\activate pip install --upgrade pip虚拟环境激活成功后命令行提示符前会出现(ocr_env)字样后续所有安装命令都在这个环境下执行。2.2 核心依赖安装与版本匹配注意事项依赖安装是整个项目里最容易翻车的一环特别是PaddleOCR和PaddlePaddle的版本组合。我的安装命令是pip install paddlepaddle2.6.1 pip install paddleocr2.7.3 pip install openpyxl pip install pyinstaller这里有几个关键点第一PaddlePaddle分为CPU版和GPU版普通需求装CPU版就完全够用GPU版安装包体积巨大且需要额外配置CUDA打包exe时只会带来麻烦。上面的paddlepaddle就是CPU版本。第二PaddleOCR 2.x和3.x的API差异非常大。2.x版本文档多、示例多、网上报错案例也多遇到问题好搜索。3.x版性能更强但接口换了写法用旧教程的代码会直接报错。如果你是首次上手我建议先锁版本用2.x跑通流程后再考虑升级。第三Windows上PaddleOCR依赖Visual C运行库缺了它会出现DLL加载失败。安装前可以先确认一下系统里有没有Microsoft Visual C Redistributable没有就去微软官网下载安装最新的x64版本。这和热搜词里“paddle ocr vc”“vs2017使用paddle ocr”说的是同一件事——报错信息五花八门根子往往缺个运行库。安装完成后验证一下python -c from paddleocr import PaddleOCR; print(OK)如果能正常打印OK说明核心依赖就绪。如果这一步报错优先检查Python版本和pip源国内网络环境下建议给pip换用清华或阿里镜像源。3. 核心代码图片文字识别与Excel写入的完整实现3.1 图像预处理什么时候需要什么时候完全不需要很多新手一上来就写一堆灰度化、二值化、高斯模糊的操作其实没必要。PaddleOCR内部本身就有图像预处理模块对大多数正常拍摄、光线尚可的图片直接喂原图效果最好。我实践下来的判断标准很简单先用原图测试如果识别结果准确率超过95%就不做任何预处理只有识别率不理想时再考虑针对性增强。常见的增强手段有三种灰度化去除颜色干扰适合背景复杂、色彩鲜艳的图片。对比度拉伸用cv2.convertScaleAbs或PIL.ImageEnhance.Contrast增强文字与背景的差异适合偏灰、偏暗的图片。二值化把图片变成纯黑白适合扫描件、高对比度文档但拍糊了的照片二值化后反而更差。有一种情况必须在OCR之前做预处理图片有明显倾斜。手机拍文档经常拍歪PaddleOCR虽然内置了角度分类器但超过一定角度的倾斜还是会掉点。可以用OpenCV的cv2.minAreaRect配合霍夫变换做矫正不过这个属于进阶玩法基础场景用不上。3.2 PaddleOCR识别调用与关键参数解读下面这段是核心代码中的核心完成OCR识别并输出结构化结果from paddleocr import PaddleOCR # use_angle_clsTrue 启用角度分类器识别倾斜文字更准 ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) def recognize_text(image_path): # clsTrue 表示同时对图片做方向分类 result ocr.ocr(image_path, clsTrue) if not result or not result[0]: return [] lines [] for line in result[0]: box line[0] # 四个角的坐标形如 [[x1,y1],[x2,y2],[x3,y3],[x4,y4]] text line[1][0] # 识别出的文字内容 confidence line[1][1] # 置信度取值范围0~1 lines.append({ box: box, text: text, confidence: confidence }) return linesshow_logFalse这个参数很实用否则运行时会刷屏输出一堆调试日志影响阅读也拖慢速度。langch表示识别中英文混合内容如果图片里只有英文改成langen准确率会更高。PaddleOCR返回的结果是嵌套结构最外层是一个列表第一项是第二张图的识别结果result[0]内层每个元素对应一条文字包含坐标框和识别内容。这个结构不直观新手经常在这里卡住取不到数据所以我直接用上面的代码把它转换成了更友好的字典列表。3.3 识别结果的结构化处理和Excel写入OCR返回的文字顺序不一定符合人眼阅读顺序尤其是证件照、票据这类包含大量字段的图片。利用坐标信息按“从上到下、从左到右”排序是标准化解法def sort_lines_by_position(lines): # 先按y坐标分组行再在每个行内按x坐标排序 lines.sort(keylambda line: (line[box][0][1], line[box][0][0])) return lines简单解释一下原理box[0]是文字框左上角坐标box[0][1]是y值、box[0][0]是x值。先按y排序能让上面行在前面同一行内按x排序能让左边的内容先出现。有了这个排序表格里文字顺序基本就和原图一致了。然后是写入Excel的代码这里用了openpyxlfrom openpyxl import Workbook from openpyxl.styles import Font, Alignment def write_to_excel(all_results, output_file): wb Workbook() ws wb.active ws.title 识别结果 # 设置表头 headers [图片序号, 识别文字, 置信度] ws.append(headers) for col in range(1, len(headers) 1): cell ws.cell(row1, columncol) cell.font Font(boldTrue) cell.alignment Alignment(horizontalcenter) # 写入数据 row_idx 2 for img_index, lines in enumerate(all_results, 1): for line in lines: ws.cell(rowrow_idx, column1, valueimg_index) ws.cell(rowrow_idx, column2, valueline[text]) ws.cell(rowrow_idx, column3, valueround(line[confidence], 4)) row_idx 1 # 调整列宽 ws.column_dimensions[A].width 10 ws.column_dimensions[B].width 60 ws.column_dimensions[C].width 10 wb.save(output_file)用openpyxl而不是pandas写入Excel是因为它对单元格样式控制更灵活不依赖其他重型库打包exe时体积更小。这段代码里设置表头加粗、调整列宽这些细节看似可有可无但实际交付给非技术同事时一个排版清晰的表格能避免很多“这个表能不能再美化一下”的反复沟通。3.4 完整脚本批量处理文件夹下所有图片把上面的功能拼起来再加上批量处理逻辑就形成了一个可直接使用的完整脚本import os from paddleocr import PaddleOCR from openpyxl import Workbook from openpyxl.styles import Font, Alignment def recognize_image(ocr, image_path): 识别单张图片 result ocr.ocr(image_path, clsTrue) lines [] if result and result[0]: for line in result[0]: text line[1][0] confidence line[1][1] if confidence 0.5: # 过滤低置信度的识别结果 continue lines.append({ box: line[0], text: text, confidence: confidence }) lines.sort(keylambda item: (item[box][0][1], item[box][0][0])) return lines def process_folder(folder_path, output_file): 遍历文件夹下所有图片识别并写入Excel ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) supported_ext (.png, .jpg, .jpeg, .bmp, .tiff) image_files [f for f in os.listdir(folder_path) if f.lower().endswith(supported_ext)] image_files.sort() if not image_files: print(该文件夹下没有图片文件) return wb Workbook() ws wb.active ws.title 识别结果 headers [图片文件名, 识别文字, 置信度] ws.append(headers) for col in range(1, len(headers) 1): cell ws.cell(row1, columncol) cell.font Font(boldTrue) row_idx 2 for idx, img_file in enumerate(image_files, 1): img_path os.path.join(folder_path, img_file) print(f[{idx}/{len(image_files)}] 正在识别: {img_file}) lines recognize_image(ocr, img_path) for line in lines: ws.cell(rowrow_idx, column1, valueimg_file) ws.cell(rowrow_idx, column2, valueline[text]) ws.cell(rowrow_idx, column3, valueround(line[confidence], 4)) row_idx 1 ws.column_dimensions[A].width 30 ws.column_dimensions[B].width 60 ws.column_dimensions[C].width 10 wb.save(output_file) print(f识别完成结果已保存到: {output_file}) if __name__ __main__: input_folder input(请输入图片所在文件夹路径).strip() input_folder input_folder.strip() # 处理Windows下复制路径自带引号的问题 output_file os.path.join(input_folder, 识别结果.xlsx) process_folder(input_folder, output_file)这个脚本里有两个容易忽略的细节。第一是strip()Windows用户从资源管理器地址栏复制路径粘贴到命令行后路径两端会带英文双引号不去掉会导致路径错误。第二是置信度阈值过滤实际图片里总有些模糊不清的角落会被识别成莫名其妙的内容低于0.5置信度的结果基本可以判定为噪声。4. 实测中必须处理好的三类异常4.1 “ocr could not create a primitive... no text detected”的完整排查链路如果你搜过这个报错会发现网上有两个高频片段“could not create a primitive”和“no text detected”。我一开始也被搞懵了后来一步步排查才发现这是两个不同环节的问题。先说我遇到的具体情况脚本在开发机上跑得好好的打包成exe换到同事电脑上就报no text detected。排查链路是这样的第一步确认图片是否读取成功。报错里出现could not create a primitive本质是OpenCV或图像处理库在操作一个空图像对象。检查图片路径有没有中文检查文件名大小写是不是写错了尤其要检查从文件夹复制路径时是不是带了不可见字符。最简单的方式是在调用OCR之前用cv2.imread读一下打印图像的shape如果是None就说明读取失败。第二步确认模型是否正常加载。即使图片读取成功如果模型文件损坏或缺失OCR也会给出异常结果。可以尝试在代码初始化时打印一下模型加载日志确认模型文件路径中存在对应文件。第三步确认图片内容本身质量。如果一张图片模糊到人眼都看不出字那OCR返回no text detected非常正常。此时应该回到图像预处理先尝试增强对比度、放大图片后再识别。PaddleOCR对尺寸小于30像素的文字基本无能为力放大2到3倍后识别率能显著提升。我最终定位到问题根源是exe程序在调用图片路径时没有正确拼接导致传入的是空字符串图片读取失败。加上路径校验和日志打印后问题解决。这个例子也提醒我打包exe后很多开发时不会暴露的问题会集中爆发关键是把每一层的输入输出都变成可见的日志。4.2 首次运行卡在模型下载的应对方案PaddleOCR第一次运行时会自动下载模型文件包括检测模型、方向分类器、识别模型三部分体积大约在十几MB到几十MB之间。网络状况不好的时候这一步可能卡很久甚至下载到一半报错。墙裂建议你在一开始就手动把模型跑通一次。因为我吃过这个亏脚本写完后直接打包exe结果交付到客户电脑上双击运行一直卡在“downloading model”客户以为是程序死了。实际上模型文件正在往C盘用户目录里下载但下载速度实在太慢。解决思路有两条。一是提前下载模型后放到指定目录让PaddleOCR直接加载本地模型。模型文件可以到PaddleOCR的GitHub releases页面下载解压后放到用户目录下的.paddleocr文件夹里。二是用PaddleOCR初始化时指定模型路径参数det_model_dir、rec_model_dir、cls_model_dir这样程序启动后不再联网下载。对打包exe交付的场景第二种更稳妥。把三个模型文件放在exe同级的models目录下代码里通过相对路径加载这也能解决部分“exe在陌生电脑上首次运行依赖网络”的烦恼。4.3 中文路径、Excel文本格式的隐藏问题Windows下中文路径是Python文件处理的常客问题。脚本里如果图片路径包含中文部分图像库读取时会因为编码问题失败。我在脚本里额外加了一道保险用pathlib.Path替代os.path.join并且在读取图片前对路径做一次编码转换处理。虽然PaddleOCR本身在多数中文路径下能正常工作但图片路径含特殊字符如#、、空格时可能出幺蛾子。另一个很多人没注意到的坑是Excel单元格默认把长数字当作数值类型处理导致序列号、卡号这类长字符串出现科学计数法、尾数丢失的情况。如果你识别的文字里包含身份证号、银行卡号这类超过15位的数字写入前必须先把单元格格式设为文本。用openpyxl可以这样处理from openpyxl.styles import numbers cell ws.cell(rowrow_idx, column2, value123456789012345678) cell.number_format # 设置为文本格式防止科学计数法如果不设置Excel打开后看到的就是1.23457E17这种低级错误在交付验收时特别尴尬而且不是OCR识别错误是表格写入格式问题。遇到“识别结果和原图一样但Excel里就是不对”的情况十有八九是单元格格式的锅。5. 打包exe的全过程与体积优化5.1 PyInstaller打包步骤与关键参数当脚本在开发环境完全跑通后就可以打包exe了。PyInstaller是打包Python程序最成熟的工具支持一键打包成单文件。我的打包命令是pyinstaller -F -w --name OCR2Excel --clean ocr2excel.py几个关键参数拆开说-F打包成单个exe文件方便分发。缺点是启动速度稍慢因为要先把依赖解压到临时目录。-w禁用控制台窗口。这个参数对纯GUI程序适用但如果你的程序里有input()交互像我上面脚本里的输入文件夹路径就不能加-w否则运行时没有任何输入界面。--name指定生成exe的名称。--clean清理上次打包的缓存文件。如果你用了我上面的完整脚本带input交互打包时不要加-w否则双击exe后只会在后台运行看起来就像没反应。正确做法是保留控制台窗口让用户能看到“请输入图片所在文件夹路径”的提示。5.2 打包体积大的根因与瘦身思路PaddleOCR程序打包出来的exe体积会让人震惊动辄一两百MB起步这是正常的不用怀疑自己打包错了。大头主要来自PaddlePaddle框架本身CPU版接近100MB。PaddleOCR的Python包及其依赖。opencv-python提供的原生库。想瘦身的思路有三个方向。第一个是用RapidOCR替代PaddleOCR。RapidOCR不用加载完整的PaddlePaddle框架只用onnxruntime推理打包体积能压缩三分之一而且识别模型完全复用PaddleOCR的训练成果精度几乎无损。第二个是精简openpyxl的依赖。openpyxl本身不大但它依赖的et_xmlfile是必需的基本没有太多裁剪空间。第三个是合理利用PyInstaller的--exclude-module参数排除项目中确定用不到的模块。但这个方法对新手不太友好容易误伤依赖。我的建议是别在体积上过度纠结如果是内部工具100多MB完全能接受如果是对外分发优先考虑RapidOCR方案。我给朋友打出来的exe是180MB左右用U盘拷贝完全没问题。他唯一的反馈是“双击后要等几秒才出窗口”这和单文件模式启动时解压到临时目录有关想要启动更快可以改用-D目录模式打包会生成一个包含多个文件的文件夹启动速度快很多。5.3 打包后exe换电脑运行失败的典型修复打包是一个坑但真正让人头疼的是“在我电脑上能跑换台电脑就报错”。我遇到过的情况有这几种第一种是缺VC运行库。报错信息可能是DLL load failed while importing或直接提示找不到某个DLL。解决办法就是在那台电脑上安装Microsoft Visual C Redistributable x64一次装好基本能覆盖大多数问题。第二种是模型文件没跟着走。如果你用了本地模型加载方案模型文件夹必须和exe放在一起分发或者放在固定目录让程序去读取。文件丢了程序只在内存里跑模型加载逻辑但找不到文件就会报错。第三种是杀毒软件误报。PyInstaller打包的exe经常被Windows Defender或其他杀毒软件误判为可疑程序。这不是程序有问题是打包方式触发了启发式扫描。内部分发时可以给exe加个白名单对外分发没有太好办法这也是PyInstaller打包工具的普遍现状。第四种是路径写死问题。开发机上C:\Users\你的用户名\...这种绝对路径在新的电脑上不存在程序启动后找不到输入文件或输出目录报错五花八门。代码里应尽量使用相对路径或者让用户通过交互输入路径。我在脚本里就是用input让用户自己输入文件夹路径从源头避开路径硬编码问题。5.4 日志优化让exe报错时能被“看懂”给非技术同事用exe时最痛苦的事情就是对方甩你一张截图说“程序报错了”但报错还是那个黑窗口里滚动的堆栈traceback。对不懂代码的人来说这基本等于没有信息。我在正式交付前会把脚本外层包一层全局异常处理把错误信息写入一个独立的日志文件import traceback if __name__ __main__: try: main() except Exception as e: with open(error.log, w, encodingutf-8) as f: f.write(traceback.format_exc()) print(程序出错详细信息已保存到 error.log) input(按回车键退出...)这样做的价值在于即使程序运行失败也能拿到一个结构化的日志文件能顺着日志快速定位问题。对使用者来说“程序报错”从一个完全黑盒变成了“有据可查”。6. 从“能跑”到“好用”进阶方向建议6.1 批量识别与文件夹拖拽支持上面脚本已经实现了对文件夹内所有图片的遍历但实际使用中用户有时只想处理一张图有时想处理拖进来的几十张图不一定都是完整文件夹。体验更好的做法是做一个拖拽窗口把图片或文件夹直接拖进窗口就行。Python里实现拖拽的常用库是tkinterdnd2它给Tkinter补充了拖拽文件事件的支持。不过这个库本身是纯Python实现打包时不会带来太重负担。如果不想引入额外依赖也可以利用批处理文件把参数传给exeecho off OCR2Excel.exe %*然后用户在文件管理器里选中多张图片直接拖到bat文件上就能把文件路径批量传给Python脚本。这个做法不需要写任何额外Python代码只用一个两行的bat文件就解决了交互问题。我已经用这个方案交付过好几个内部工具同事们用起来都很顺手。6.2 为工具加一个简单GUI的可行方案如果最终用户完全不碰命令行那给程序加一个简单GUI是值得的。最轻的方案是TkinterPython自带不需要额外安装库打包体积增加很小。一个小窗口上面一个路径输入框、一个“开始识别”按钮、一个进度条、一个文本框显示日志就足够满足绝大多数需求。Tkinter写法不复杂核心就是布局控件和事件回调。对于识别完成后自动打开Excel文件可以用os.startfile(output_file)一行代码实现这在Windows上效果特别直观用户看到Excel自己弹出来比什么提示都管用。如果想要更现代一点的界面可以考虑PySide6或CustomTkinter但打包体积会上一个台阶。我的经验是内部工具用Tkinter就够了简单、稳定、打包体积可控。为了一点点界面漂亮换来几十MB的体积和潜在的兼容性问题不划算。6.3 识别结果后处理自动分类与格式纠错识别只是第一步真正产生价值的往往是识别之后的数据处理。同样是发票识别有人只需要把所有文字堆在一个单元格里有人需要把发票号码、日期、金额分别填到不同列。建议你在脚本里预留一个后处理函数专门对OCR结果做字段归类。比如设备铭牌通常包含型号、序列号、生产日期可以利用正则表达式匹配“SN”之后的字符串作为序列号匹配“MODEL”之后的字符串作为型号。这种后处理让输出从“一段文字”变成“结构化字段”实用性翻倍。后处理还有一个重要用途是纠错。OCR对数字和字母的识别偶尔会有混淆比如0和O、1和I。结合正则表达式做合法性校验比如日期字段必须符合\d{4}-\d{2}-\d{2}格式型号字段必须包含特定关键字可以拦截明显异常的结果并提示人工确认。数据质量在交付场景里是最关键的一环宁可多写几十行校验代码也不要让错误数据流进正式报表。最后分享一个我自己的实操习惯每次写完这类工具我会故意准备一张“坏图片”——模糊、倾斜、反光、乱码都占全的那种专门用来测试程序的稳定性。能扛住坏图片的程序才能拿给外行人用。打包exe之前也在干净虚拟机里完整跑一遍确认没有依赖缺失再交付。做工具做到最后拼的往往不是代码能力而是对边界情况的考虑。遇到识别混乱的时候也别急着怀疑库不行先看看图片本身质量再看预处理有没有跟上多数问题都出在中间这一层。
返回列表