几周前做一个小工具,需要从一堆截图和合同扫描件里批量提取短文本。试了一圈 OCR 方案,Tesseract 纯 CPU 识别汉字吃力,云厂商 OCR 接口虽然有额度但上传文件涉及权限流程麻烦,最后盯上了 ddddocr 这个库。它是本地离线运行的深度学习 OCR,主要针对图形验证码和短字符识别做了优化,调 Python 接口几行代码就能跑起来,不需要 GPU,普通机器也能扛住。
身边不少朋友一听到 ddddocr,第一反应是“爬虫验证码破解”,其实这个理解太窄了。它能做的远比“绕验证码”多:自动化测试脚本里识别测试环境的图形码、本地归档图片的关键信息抽取、对老旧系统里无法复制的验证图做辅助输入,都是正经使用场景。当然这里我得先把话说明白:本文分享的是 ddddocr 的常规 API 用法和部署经验,如果你的用途是未经授权地访问或绕过第三方服务的安全机制,那不在这次讨论范围内。所有示例我建议都先框定在授权测试和本地个人数据处理里。
这篇文章我会从环境搭建讲起,理一遍几个常用功能的调用逻辑,再给出一个可以直接抄的批量识别脚本,最后把我实际用的时候踩过的几种坑列出来。所以如果你是刚学 Python、正被各种图片识别需求搞得头大,可以放心看下去。
1. 为什么选 ddddocr:它不是通用 OCR,是“短文本识别利器”
1.1 这个库到底做了什么
ddddocr 本质上是一个运行在本地、模型格式为 ONNX 的深度学习字符识别工具。开发者训练它的时候,样本大多来自互联网上常见的图形验证码、随机字符图、滑块缺口图,所以它对短文本、扭曲字符、带干扰线的数字和字母识别效果非常突出。
和通用 OCR 相比,ddddocr 最大的特点在于“短”。你扔给它一张十来个字符的图片,它能给出比较可靠的结果;但你要是扔一整页 A4 扫描件让它识别,这不是它的强项,因为它并不是把整页布局做版面分析的引擎,而是更擅长单行/单区域内少量字符的提取。
我用一个不算特别严谨但很好懂的方式来描述它的内部流程:图片输入后,先把图像处理成固定尺寸和颜色通道的输入张量,再交给一个训练好的卷积模型做特征编码,最后把特征序列映射成若干字符。整个过程走的是轻量级推理,所以 CPU 单张识别一般也就几十到几百毫秒,这是它能“跑得动”的关键前提。
1.2 对比 Tesseract、PaddleOCR 和云厂商 API
选型的时候很多人纠结:免费 OCR 有 Tesseract,国产开源有 PaddleOCR,商用有百度/阿里腾讯云,为什么最后我会选 ddddocr。我直接放一个基于我自己测试经验的对比表,供大家参考:
| 方案 | 推理位置 | 中文整页识别能力 | 图形验证码/短字符识别 | 部署成本 | 适合场景 |
|---|---|---|---|---|---|
| Tesseract | 本地 | 中下(需要语言包和训练) | 弱(对干扰很敏感) | 低 | 扫描文档英文识别 |
| PaddleOCR | 本地 | 强(版面分析、多语言齐全) | 一般 | 中(依赖较多) | 文档、票据、整页材料 |
| 云厂商 OCR API | 云端 | 强 | 中(通常有风控) | 按次付费 | 有网络条件、数据合规的产品 |
| ddddocr | 本地 | 弱 | 很好 | 低(pip 安装即可) | 短文本、测试辅助、轻量自动化 |
Tesseract 在遇到背景色复杂或字符粘连的图片时,经常给我输出一串无意义字符,调参耗时太长;PaddleOCR 功能确实全面,但部署起来依赖太重,为一个小功能引入这么多东西不划算;云厂商 API 不便宜,还要担心数据安全。ddddocr 在很多场景里是“最短路径”,安装包不大,离线也能用,识别短字符串场景的准确率很能打。
1.3 先说清楚用法边界
和所有 OCR 工具一样,ddddocr 本身是中性的,它能被用来做自动化辅助,也可能被滥用。我个人的一个原则是:工具只用在你有权限或被授权处理的数据上。
举例来说,你给自己负责开发的内部测试系统写一个登录辅助脚本,自动读取图形验证码完成测试登录,这是正常的提效工具;但如果你把这个能力用来批量访问第三方网站、绕过对方的人机验证,这就完全变了性质。所以后面所有代码示例,我都建议你替换成自己的测试图片或开放的样例图片。先把能力学会,再把它用在该用的地方,这才是 Python 实践该有的样子。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:安装方式和首次初始化的隐藏坑
2.1 Python 版本与虚拟环境选择
ddddocr 对 Python 版本不算苛刻,常见的 3.8 到 3.11 都能正常用。我看到很多教程直接推荐全局环境 pip install,但我的建议是先建一个独立虚拟环境,避免这个包升级时把别的库带的 numpy、onnxruntime 等依赖搞乱。
Windows 或 Linux 下创建虚拟环境的命令如下:
bash复制python -m venv ocr_env
Windows 里激活环境:
bash复制ocr_env\Scripts\activate
Linux / macOS 里激活环境:
bash复制source ocr_env/bin/activate
如果你电脑上 Python 命令没生效,多半是安装 Python 时漏勾选了“Add Python to PATH”。这时候要么重新安装并勾选,要么在命令里使用完整路径 C:\Python311\python.exe -m venv ocr_env。这个问题搜“python 安装教程”能解决,但我在实际给同事配环境时发现,漏配 PATH 是很多新手卡住的第一个点。
2.2 pip 安装 ddddocr
激活虚拟环境后执行:
bash复制pip install ddddocr
如果你在国内网络环境下安装速度较慢,可以加国内镜像源:
bash复制pip install ddddocr -i https://pypi.tuna.tsinghua.edu.cn/simple
我建议在安装时顺便升级一下 pip,因为 ddddocr 依赖的 onnxruntime 在部分 Python 版本上需要较新的 pip 才能解析出正确版本:
bash复制python -m pip install --upgrade pip
安装完成后,可以快速验证一下能不能正常导入:
bash复制python -c "import ddddocr; print(ddddocr.__version__)"
如果输出没有报错,环境基本就没问题了。整个过程比很多深度学习库简单太多,不需要你下载 CUDA,也不需要配置训练环境。
2.3 初始化 DdddOcr 时的模型加载细节
安装完库之后,第一次创建 DdddOcr 对象可能要比预期慢几秒,因为要加载 ONNX 模型。ddddocr 的部分版本在使用时会检查本机是否已有模型缓存,如果缺少模型文件,会自动下载到用户主目录下的某个隐藏目录里。这里最容易出现两个坑。
第一个坑是“下载模型超时”。如果你所在网络环境访问不到模型下载地址,程序会在初始化阶段卡很久然后报错。解决方案通常是手动把模型文件下载好放到指定目录,或者从内网镜像获取。因为不同版本模型路径不同,我这里不写死路径,大家在程序报错信息里找 “download” 或 “model path” 关键字,一般就能看到它到底想找哪个目录。
第二个坑更隐蔽:有些开发者会给自己写的测试脚本命名为 ddddocr.py,然后脚本里再写 import ddddocr。这时候 Python 会优先导入当前目录下同名文件,导致出现类似 ImportError: cannot import name 'DdddOcr' 的错误。这个问题看起来特别蠢,但真的会浪费一个人半小时。解决办法就是千万别把文件命名为 ddddocr.py 或 ddddocr_test.py,换个正常名字比如 ocr_demo.py。
2.4 大型工作流中的包依赖冲突
前面的热词里出现了不少 “要安装缺失的节点,请先在你的 python 环境中运行 pip install” 这类提示,其实很多人安装 ddddocr 时也会遇到类似情况,尤其是在某个已有的 Python 工作流环境里补装包。这种提示的根本原因是:当前环境中缺了某个库,或者现有库版本不满足要求。
如果你是在 ComfyUI、量化回测这类大型环境中补装 ddddocr,最稳妥的做法不是直接往全局环境塞包,而是先看看当前用的 Python 解释器是哪一个,当前环境装过什么版本的 numpy 和 onnxruntime。ddddocr 对 numpy、pillow 有依赖,升级时容易和已有环境里的其他包产生版本冲突。每次看到这种报错,先执行 pip list 看全量包,比盲目装缺哪个包更靠谱。
3. 核心 API 拆解:识图、定位、滑块匹配一次讲明白
3.1 classification:一张图片的字符识别基础款
ddddocr 最常用的方法就是 classification,它能接收图片的二进制内容,返回识别出的字符串。下面是我最常用的一段代码:
python复制import ddddocr
ocr = ddddocr.DdddOcr(show_ad=False)
with open("sample.png", "rb") as f:
image_bytes = f.read()
result = ocr.classification(image_bytes)
print("识别结果:", result)
需要注意两点。第一,这里传入的是图片的字节流(bytes),不是文件路径,也不是 PIL Image 对象,更不是 numpy 数组。第二,show_ad=False 是用来关闭旧版本里初始化时打印的广告信息,不用纠结,加上就好。
很多新手第一次跑通这段代码后很兴奋,接着就把图片换成了自己的验证码截图,结果可能识别错误。先不要慌,图片字符不规范的时候,ddddocr 不会报错,只会“自信地给出错误结果”,这是所有机器学习模型的通病。后面第 5 章我会讲怎么通过图片预处理来改善准确率。
classification 还支持一些可选参数,比如概率输出 probability、字符集限制 charsets 等。在 2.x 版本里,你不一定能确定当前版本参数是否可用,稳妥办法是 Python 里执行:
python复制help(ocr.classification)
直接看你自己安装版本的函数签名。这一点特别重要,因为 ddddocr 版本演进比较快,网上老教程里的参数可能在新版里已经被调整了。
3.2 detection:先从整图里找到文字位置
有些图片里文字并不是铺满整张的,而是分布在图的某个区域。比如一个登录页截图里有账号输入框、密码输入框、验证码图片、按钮文字,如果直接整张丢给 classification,大概率识别结果乱七八糟。
这时可以先使用 ddocr 的目标检测能力,把图片中出现的目标框找出来,再把框出来的区域切分,送给 classification 识别。实际用法大致是:
python复制import ddddocr
det = ddddocr.DdddOcr(det=True, ocr=False)
with open("scene.png", "rb") as f:
image_bytes = f.read()
boxes = det.detection(image_bytes)
print("检测到的目标框:", boxes)
不同版本的返回格式会有一点差别,但一般会给出包含文字目标位置的多个框坐标。你可以把每个框理解成一个“图片中的目标物品范围”,获取到坐标后,用 PIL 把对应区域裁剪成小图,再调用 classification 去做字符识别。
这个组合用法的价值在于,它把“整图大而全”的难题拆分成了“先找文字在哪儿、再单独识别文字”的两步,准确率会有明显改善。不过说实话,如果你处理的是单张干净验证码,不需要 detection 这个步骤,直接用 classification 反而更快。
3.3 slide_match:滑块缺口定位怎么算距离
ddddocr 除了字符识别外,还提供滑块匹配能力。很多人提到滑块就只想到自动化操作,但在合理的开发场景里,比如公司内部工具的数据标注、测试环境滑块组件验证,也需要快速找到目标图在背景图中的位置。
核心逻辑是输入两张图片:一张是滑块小图 target,一张是带缺口或带背景的大图 background,然后返回匹配结果。示例代码如下:
python复制import ddddocr
ocr = ddddocr.DdddOcr(show_ad=False)
with open("target.png", "rb") as f:
target_bytes = f.read()
with open("background.png", "rb") as f:
background_bytes = f.read()
result = ocr.slide_match(target_bytes, background_bytes, simple_target=True)
print(result)
simple_target=True 表示 target 图片是一个纯色背景的简单滑块小图,没有额外复杂背景。如果你的滑块小图带着和背景一致的纹理,那就不能简单用 True,需要先做图片处理。如果接口返回给你坐标、缺口位置或者页面移动距离字段,你可以根据自己的需求取出来做位移计算。
我在这里要诚实地提醒一句:滑块匹配这类功能如果不加限制地去套用到线上业务里,很容易撞到风险红线。我建议你用它只做两件事:一是个人学习模型效果,二是授权范围内的测试自动化,不要在未授权场景里对第三方平台反复尝试,这既不安全也不礼貌。
3.4 读取图片的几种姿势对比
这个细节很多人会忽略。classification 接收的是 bytes,写代码时获取图片字节流的方式不同,代码风格差别很大。
直接用文件路径读取是最省心的:
python复制from pathlib import Path
image_bytes = Path("sample.png").read_bytes()
result = ocr.classification(image_bytes)
如果图片已经在网络请求里,比如 requests 请求拿到了响应内容,可以直接传响应体的 content:
python复制import requests
resp = requests.get("https://example.com/captcha.png", timeout=10)
result = ocr.classification(resp.content)
如果项目中你已经用 OpenCV 读成了 numpy 数组,也不能直接传,需要先用 imencode 编码回图片字节:
python复制import cv2
img = cv2.imread("sample.png")
success, encoded_img = cv2.imencode(".png", img)
image_bytes = encoded_img.tobytes()
result = ocr.classification(image_bytes)
我见过不少同学拿 cv2 读图后直接传给 ddddocr,然后跟我报错。cv2 里默认读出来的是 BGR 通道的数组,这不是 ddddocr 需要的数据格式。理解这一点,很多错误就不难排查了。
4. 实战落地:批量识别图片目录并输出结果
4.1 一个值得动手实现的小场景
我当时的实际任务是这样的:某个目录里存了几百张导出的小图,每张图是某个业务单据里的编号片段,需要把这些编号统一提取出来生成 Excel 对账。人工看图眼睛会花,而且每张图只有四到六位数字和字母,用 ddddocr 再合适不过。
我先看一眼目录下的图片格式,有的 JPG 有的 PNG,有的图片尺寸小到只有 40x20 像素。这种情况下直接识别,结果会惨不忍睹。所以我做了两件事:先把图片放大 2 到 3 倍,再对部分偏小的图片做灰度化,把预处理后的图片内容送进识别。
4.2 完整可运行的批量识别脚本
下面这个脚本我直接复制关键代码,你根据自己的目录修改一下即可运行。它遍历一个文件夹下所有 png 和 jpg 图片,逐张识别并把结果打印出来,方便你观察输出:
python复制import ddddocr
from pathlib import Path
ocr = ddddocr.DdddOcr(show_ad=False)
image_dir = Path("./images")
results = []
for image_path in image_dir.glob("*.png"):
image_bytes = image_path.read_bytes()
text = ocr.classification(image_bytes)
results.append((image_path.name, text))
print(f"{image_path.name}: {text}")
for filename, text in results:
print(f"{filename}\t{text}")
如果你图片命名是 .jpg 后缀,就把 glob 里的后缀改成 .jpg,或者用 image_dir.glob("*") 然后判断后缀。跑完先不要急着追求速度,因为第一次跑 DdddOcr 会加载模型,耗时较长,后续单张识别就会快很多。
如果你要处理几百张图,想提升速度,不要在一个进程里开很多线程调用同一个 DdddOcr 实例。稳妥的做法是使用多进程,让每个子进程创建自己的 OCR 实例。多线程共享同一个 ONNX 会话是否线程安全我没有查到明确承诺版本,所以不要为了一点点速度去赌稳定性。
下面给一个多进程版本的思路示例:
python复制import ddddocr
from pathlib import Path
from concurrent.futures import ProcessPoolExecutor
def recognize_one(image_path: str):
ocr = ddddocr.DdddOcr(show_ad=False)
image_bytes = Path(image_path).read_bytes()
return Path(image_path).name, ocr.classification(image_bytes)
if __name__ == "__main__":
image_paths = [str(p) for p in Path("./images").glob("*.png")]
with ProcessPoolExecutor(max_workers=4) as executor:
results = list(executor.map(recognize_one, image_paths))
for name, text in results:
print(f"{name}\t{text}")
这个写法在 Windows 上也可以运行,但注意如果你的图片文件特别小,多进程反而会增加额外开销,图多才值得用。
4.3 识别结果的后处理:不要直接信 OCR
批量识别结束后你会发现一个问题:返回的识别结果有时会把数字 0 识别成字母 O,把 1 识别成 l,把 5 识别成 S。这种混淆在图形字符里太常见了。
如果你的业务场景有明确的字符规则,比如“内部编号只由大写字母和数字组成”,那可以在结果里加一层清洗:
python复制import re
def clean_text(text: str) -> str:
text = text.upper()
text = text.replace("O", "0").replace("I", "1")
return re.sub(r"[^A-Z0-9]", "", text)
注意:清洗规则必须根据你的业务场景来决定。如果编号里真的允许字母 O,那就不能无脑替换,否则会把本来正确的数据改错。我的习惯是每次先统计所有识别结果的字符分布,哪些字符高频出现但看起来可疑,再决定要不要映射替换。
还有一个细节是识别结果前后是否有多余空格。classification 一般不会输出一堆空格,但某些图片边缘带白边时有可能把空白也算进去。看到结果后先 strip() 一下,减少后面写文件的干扰。
4.4 接入到自动化测试流程里的写法
如果你做 Web UI 自动化测试,系统登录页有一个图形验证码,测试脚本每次跑到这一步就卡住。在很多授权的内部测试场景下,可以写一个辅助识别模块,把验证码图片从页面下载后转成字节流,调用 ddddocr 得到结果,再自动填入登录框。
一个最小示例是:
python复制def recognize_captcha_code(image_bytes: bytes) -> str:
ocr = ddddocr.DdddOcr(show_ad=False)
return ocr.classification(image_bytes)
不要把 DdddOcr 放在每个测试用例里反复实例化,这样会拖慢测试速度。正确的用法是在测试模块初始化时创建一个全局 OCR 实例,后续所有用例复用。如果测试用例是并发执行的,建议加锁或改为每个线程独立一个实例。
我特别想强调一个点:这种做法只适用于你自己有权限控制和维护的测试系统。假如登录页属于某个你没有任何管理授权的线上服务,用 OCR 自动输入验证码来批量登录,就算技术上实现了,也是越过对方安全策略的行为,请不要去尝试。
4.5 把结果保存到文件
批量识别完,直接把结果输出到 CSV 是最务实的做法。Python 自带的 csv 模块就够了,不要为了一个简单的落盘引入 pandas 等重型依赖:
python复制import csv
with open("ocr_output.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["filename", "text"])
writer.writerows(results)
utf-8-sig 是一种带 BOM 的 UTF-8 编码,这样导出的 CSV 用 Excel 直接打开时中文不会乱码。这个细节虽然小,但在交付给同事的时候特别加好感。
5. 参数细节与优化思路:真正值得调的东西是什么
5.1 probability 参数:看置信度而不是盲目相信结果
ddddocr 2.x 版本的 classification 方法支持返回带概率的候选结果。如果你想判断当前识别结果靠不靠谱,可以这样尝试:
python复制result = ocr.classification(image_bytes, probability=True)
print(result)
有些版本会返回一个包含字符和概率的结构;有些版本返回多个候选结果列表。拿到概率之后,你可以设定一个阈值:如果最高置信度低于 0.8,就不要直接采纳这个结果,而是转人工或重新预处理后再识别。
这种做法比“拿到 OCR 字符串就入库”可靠得多。我批量测试时发现,如果最终业务能容忍 3% 的误识别,阈值策略都不需要;但如果业务要求必须 100% 准确,你反而应该用阈值把低置信度图片筛出来,而不是让错误结果混进数据库。
不过还是要提醒,不同版本对 probability 返回格式的定义有差异。拿到结构后先打印看字段名,再写后续解析代码。
5.2 图像预处理对准确率的影响
我做过一组简单对比测试:同一批 100 张低分辨率图片,直接识别准确率可能只有 82%;把它放大两倍后再识别,准确率能提升到 93% 左右。再叠加灰度化,会有小幅波动,但放大是最管用的一招。
因为 ddddocr 训练时主要面对的图片分辨率相对稳固,如果输入图片太小,字符特征提取容易丢失细节。放大图片能够补足这部分信息,让模型更容易认出字符。
PIL 预处理代码参考:
python复制from PIL import Image
img = Image.open("raw.png").convert("L")
img = img.resize((img.width * 2, img.height * 2), Image.LANCZOS)
img.save("processed.png")
with open("processed.png", "rb") as f:
result = ocr.classification(f.read())
如果你确认图片有大量椒盐噪声,可以用 opencv 做一个中值滤波:
python复制import cv2
img = cv2.imread("raw.png", cv2.IMREAD_GRAYSCALE)
img = cv2.medianBlur(img, 3)
img = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LANCZOS4)
success, encoded_img = cv2.imencode(".png", img)
result = ocr.classification(encoded_img.tobytes())
中值滤波对去除随机噪点很有效,但对本身颜色就淡的字可能也会有削弱作用,需要结合实际图片测试。这条经验是我在反复对比后才总结出来的,预处理不是越重越好,要保留字符边缘的完整性。
5.3 不同模型组合对识别的影响
ddddocr 为了兼容不同类型的图片,在不同版本里引入了模型选择参数。在实例化时有 det 和 ocr 参数,分别表示是否启用检测模型和识别模型;另外个别版本还有“旧模型”选项,主要对应一些老式风格字符。
如果你发现新模型对自己手里那批历史图片识别效果反而不如旧模型,可以检查你安装的库里 DdddOcr 构造函数是否支持对应参数。比如:
python复制ocr = ddddocr.DdddOcr(old=True)
有些版本的 old 参数是 None/True/False,需要先查自己版本的帮助文档。这个参数的坑在于:网络上的教程很可能基于几个月前的版本,你用最新版 pip 装到的是新版本,反而找不到旧教程里的参数。遇到这种情况,打开终端执行:
python复制import ddddocr
help(ddddocr.DdddOcr)
看参数表,比网上搜索更准确。
5.4 CPU 部署下的性能实测
我自己的开发机是普通 x86 笔记本,没有独立显卡。连续识别 500 张 60x30 像素左右的图片,整体跑下来大约花了 25 秒左右,折合单张 50 毫秒。这个速度对绝大多数批处理需求来说完全够用。
如果图片更大,比如 800x200 的带背景截图,单张识别时间会到 100 到 200 毫秒。瓶颈不完全在模型前向推理,还有图片解码时间。所以前面代码里推荐直接用原图字节流,不要先把图片在内存里做大尺寸变换,变换完之后又编码回 PNG,这个过程会浪费很多时间。
如果你部署在高并发的 Web 服务里,一个 OCR 实例响应不过来,不要尝试开 50 个线程同时调用同一个实例,而是按进程扩容。每个 worker 进程里持有一个 DdddOcr 对象,由负载均衡把请求分散到不同进程。
6. 实际运行中常见的报错与排查经验
6.1 报错“module 'ddddocr' has no attribute 'DdddOcr'”
出现这个错误,先检查你有没有把脚本文件名命名为 ddddocr.py。如果取消了,检查当前 Python 环境里是否真的安装了 ddddocr。一个很隐蔽的原因是你在全局环境里装过库,但现在用的虚拟环境里没有装,Python 找不到就会报属性不存在。
排查顺序是:
bash复制python -c "import ddddocr; print(ddddocr.__file__)"
如果这行命令能正常输出路径,就说明当前环境有包。然后再检查路径指向的位置是不是一个真实的 site-packages 文件,而不是你脚本目录下的同名文件。如果它输出的路径指向你当前目录,说明导入顺序出了问题。
6.2 提示模型文件不存在或初始化卡住
新版 ddddocr 安装包本身会带模型文件,但在部分版本中,如果安装环境有问题或模型目录缺失,初始化 DdddOcr 时会提示找不到模型文件。这时最容易踩的坑是:你以为丢了模型文件就去网上乱下,实际上可能是你之前装了某个精简版或包损坏。
解决办法是重新安装:
bash复制pip uninstall ddddocr -y
pip install ddddocr --no-cache-dir
如果加了国内镜像源依然报错,优先检查磁盘空间。ONNX 模型文件不算特别大,但下载和写入时如果磁盘空间不足,也会出现类似加载失败的错误。不要问我为什么知道,我在一台只有 10GB 剩余空间的服务器上排过这个问题。
6.3 onnxruntime 和 numpy 版本不匹配
ddddocr 依赖 onnxruntime,而 onnxruntime 对 numpy 的版本有一定要求。如果你环境中 numpy 版本过新或过旧,可能报出一些奇怪的底层错误。在我的测试里,onnxruntime 1.15.x 搭配 numpy 1.24.x 是比较稳定的组合,但这不是硬性约束,因为不同 Python 版本会有差异。
如果你看到错误信息里出现 onnxruntime、numpy、protobuf 等关键字,建议重新安装这两个包:
bash复制pip install onnxruntime --upgrade
pip install numpy --upgrade
如果是在已有大型工作流环境里调试,千万不要随意升降全局依赖。先为这个 OCR 功能创建一个独立 venv 或使用相关环境管理工具隔离,避免为了识别图片把项目里其他模块搞崩。这一点在热词里反复提到的各种“安装缺失节点”问题里同样适用:解决包依赖冲突的根本思路是隔离环境,而不是反复升级降级同一个环境里的包。
6.4 识别结果为空字符串或乱码
识别结果为空时,我一般按以下顺序排查。
第一步,确认图片内容真的是可识别的短字符。如果图片是空白图或者字符过小,模型可能诚实地说“我啥也没看出来”。第二步,把图片放大后重新测试。第三步,检查图片是不是 JPEG 格式,虽然 ddddocr 一般支持常见格式,但某些渐进式 JPEG 在解码时会有问题,可以转为 PNG 再识别。第四步,看字符是不是非常规字体。如果图片里的字符是手写体、艺术字、生僻字,ddddocr 能识别的概率会显著下降。
乱码和空字符串不同,通常意味着模型“猜了但猜错了”。针对乱码,最好的办法不是反复调参,而是准备一个由原模型再训练或使用规则校验的兜底层。对 OCR 出来的结果做正则校验,让不符合规则的候选结果自动落入“待人工复核”,这比追求一次识别准确率更重要。
6.5 多进程模式下内存占用过高
前面提到用 ProcessPoolExecutor 提升识别速度,多进程的优点是多核并行,缺点也很明显:每个子进程都会加载一份 OCR 模型,内存翻倍。假如你的机器内存只有 8GB,开 8 个进程可能会导致 OOM。
解决思路是限制 max_workers,4 通常比 8 更稳妥。另外,不要让每个任务临时创建 DdddOcr,而要利用进程池的初始化器,在每个子进程启动时只创建一次 OCR 实例。
python复制from concurrent.futures import ProcessPoolExecutor
def init_worker():
global _ocr
_ocr = ddddocr.DdddOcr(show_ad=False)
def recognize_one(image_path: str):
image_bytes = Path(image_path).read_bytes()
return Path(image_path).name, _ocr.classification(image_bytes)
if __name__ == "__main__":
image_paths = [str(p) for p in Path("./images").glob("*.png")]
with ProcessPoolExecutor(max_workers=4, initializer=init_worker) as executor:
results = list(executor.map(recognize_one, image_paths))
这种写法非常省事,而且规避了每个任务重复读取模型的巨大开销。如果你之前是用闭包或 lambda 往每个任务里传 OCR 实例,会发现子进程根本拿不到,因为 DdddOcr 对象无法被 pickle 跨进程传输。用 initializer 初始化全局变量才是正解。
写在最后的一点实战体会
如果从头到尾看下来,你会发现 ddddocr 的使用门槛其实很低,真正的门槛在“如何判断结果是否可信”和“如何把它放进一个稳定流程里”。我在本地跑干净短文本字符图时,准确率能到 95% 上下;但一旦图片背景复杂、字符扭曲严重、字体陌生,准确率会明显下降。所以任何时候都不要把 OCR 结果当成唯一真相,设计一套规则校验或人工抽检,会让整个流程可靠得多。
这也是我建议所有入门 Python OCR 的同学必须做的一件事:先在本地收集 20 到 50 张不同风格的图片,跑一遍识别,把结果分门别类记录下来。你会发现模型对哪些字体敏感、对哪些颜色组合发虚,心里有数之后,后续再碰到实际问题就不慌了。希望这篇文字能让你少走一点弯路。
