上个月朋友扔给我一个活儿,说他手上有两三百张设备铭牌照片,都是型号、序列号、日期这类参数,需要整理成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.13,PaddleOCR在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 paddlepaddle==2.6.1 pip install paddleocr==2.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_cls=True 启用角度分类器,识别倾斜文字更准 ocr = PaddleOCR(use_angle_cls=True, lang='ch', show_log=False) def recognize_text(image_path): # cls=True 表示同时对图片做方向分类 result = ocr.ocr(image_path, cls=True) 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_log=False这个参数很实用,否则运行时会刷屏输出一堆调试日志,影响阅读也拖慢速度。lang='ch'表示识别中英文混合内容,如果图片里只有英文,改成lang='en'准确率会更高。
PaddleOCR返回的结果是嵌套结构:最外层是一个列表,第一项是第二张图的识别结果(result[0]),内层每个元素对应一条文字,包含坐标框和识别内容。这个结构不直观,新手经常在这里卡住取不到数据,所以我直接用上面的代码把它转换成了更友好的字典列表。
3.3 识别结果的结构化处理和Excel写入
OCR返回的文字顺序不一定符合人眼阅读顺序,尤其是证件照、票据这类包含大量字段的图片。利用坐标信息按“从上到下、从左到右”排序是标准化解法:
def sort_lines_by_position(lines): # 先按y坐标分组(行),再在每个行内按x坐标排序 lines.sort(key=lambda line: (line['box'][0][1], line['box'][0][0])) return lines简单解释一下原理:box[0]是文字框左上角坐标,box[0][1]是y值、box[0][0]是x值。先按y排序能让上面行在前面,同一行内按x排序能让左边的内容先出现。有了这个排序,表格里文字顺序基本就和原图一致了。
然后是写入Excel的代码,这里用了openpyxl:
from 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(row=1, column=col) cell.font = Font(bold=True) cell.alignment = Alignment(horizontal='center') # 写入数据 row_idx = 2 for img_index, lines in enumerate(all_results, 1): for line in lines: ws.cell(row=row_idx, column=1, value=img_index) ws.cell(row=row_idx, column=2, value=line['text']) ws.cell(row=row_idx, column=3, value=round(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, cls=True) 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(key=lambda item: (item['box'][0][1], item['box'][0][0])) return lines def process_folder(folder_path, output_file): """遍历文件夹下所有图片,识别并写入Excel""" ocr = PaddleOCR(use_angle_cls=True, lang='ch', show_log=False) 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(row=1, column=col) cell.font = Font(bold=True) 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(row=row_idx, column=1, value=img_file) ws.cell(row=row_idx, column=2, value=line['text']) ws.cell(row=row_idx, column=3, value=round(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(row=row_idx, column=2, value="123456789012345678") cell.number_format = '@' # 设置为文本格式,防止科学计数法如果不设置,Excel打开后看到的就是1.23457E+17,这种低级错误在交付验收时特别尴尬,而且不是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', encoding='utf-8') as f: f.write(traceback.format_exc()) print("程序出错,详细信息已保存到 error.log") input("按回车键退出...")这样做的价值在于:即使程序运行失败,也能拿到一个结构化的日志文件,能顺着日志快速定位问题。对使用者来说,“程序报错”从一个完全黑盒变成了“有据可查”。
6. 从“能跑”到“好用”:进阶方向建议
6.1 批量识别与文件夹拖拽支持
上面脚本已经实现了对文件夹内所有图片的遍历,但实际使用中,用户有时只想处理一张图,有时想处理拖进来的几十张图,不一定都是完整文件夹。体验更好的做法是做一个拖拽窗口,把图片或文件夹直接拖进窗口就行。
Python里实现拖拽的常用库是tkinterdnd2,它给Tkinter补充了拖拽文件事件的支持。不过这个库本身是纯Python实现,打包时不会带来太重负担。如果不想引入额外依赖,也可以利用批处理文件把参数传给exe:
@echo off "OCR2Excel.exe" %*然后用户在文件管理器里选中多张图片,直接拖到bat文件上,就能把文件路径批量传给Python脚本。这个做法不需要写任何额外Python代码,只用一个两行的bat文件就解决了交互问题。我已经用这个方案交付过好几个内部工具,同事们用起来都很顺手。
6.2 为工具加一个简单GUI的可行方案
如果最终用户完全不碰命令行,那给程序加一个简单GUI是值得的。最轻的方案是Tkinter,Python自带,不需要额外安装库,打包体积增加很小。一个小窗口,上面一个路径输入框、一个“开始识别”按钮、一个进度条、一个文本框显示日志,就足够满足绝大多数需求。
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之前,也在干净虚拟机里完整跑一遍,确认没有依赖缺失再交付。做工具做到最后,拼的往往不是代码能力,而是对边界情况的考虑。遇到识别混乱的时候也别急着怀疑库不行,先看看图片本身质量,再看预处理有没有跟上,多数问题都出在中间这一层。