ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PaddleOCRv5 离线识别多语种验证码:TaoToken 统一 Key 接入与 onnxruntime 配置验证

PaddleOCRv5 离线识别多语种验证码:TaoToken 统一 Key 接入与 onnxruntime 配置验证 1. 无网环境下的多语种验证码识别到底难在哪PaddleOCRv5 是百度飞桨 OCR 系列的新版本相比 v4 官方口径精度提升约 13 个百分点字典扩充到一万多字符覆盖中英日韩、繁体、手写体、生僻字等全场景文字类型。它能做什么简单说就是给一张图输出里面的文字和坐标。适合谁做验证码自动化测试、票据识别、离线文档数字化的开发者尤其是那些机器不能联网、又不想装一整套 Paddle 框架的人。但真正落地时会撞上三堵墙。第一堵是环境墙官方示例依赖 paddlepaddle、paddleocr、paddlex 一整条链路装完动辄几个 G离线机器上光解决依赖就够折腾半天。第二堵是语种墙验证码里中英日韩混排很常见不同语种对应不同字典文件字典和模型对不上就会输出乱码。第三堵是推理墙导出 ONNX 之后预处理、归一化、CTC 解码这些环节如果参数错一个识别结果就全歪。我试过的思路是把官方链路剥掉只保留导出的 ONNX 模型加 onnxruntime再配一个统一 Key 通道做工具侧接入。这样离线机器上只需要 onnxruntime、numpy、opencv、pillow 这几个基础库就能跑GPU 加速也能通过 onnxruntime-gpu 打开。下面把配置骨架、模型路径、语种参数和验证动作完整拆开讲。2. TaoToken 统一 Key 接入前置准备离线识别本身不依赖网络但工具侧往往需要调用大模型做后处理比如把识别结果做语义纠错、把验证码结果回填到自动化流程、或者用模型对话能力辅助判断识别置信度。这时候如果每个工具各自维护一套 Key管理成本很高。TaoToken 的作用就是提供一个统一的 Key 和 API 通道让这些工具侧调用走同一个入口。你需要先拿到 Key。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。API 基础地址统一用 https://taotoken.net/api 注意这个地址不加 UTM 参数直接写进配置文件即可。注意离线识别的主流程不联网TaoToken 只负责工具侧的后处理调用。不要把 Key 硬编码进提交到仓库的代码里用环境变量或本地配置文件读取。如果你后续要做长期编码或 Agent 类任务可以了解 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果只是想先验证模型对话能力用模型对话页https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。3. 可复制配置config.toml 与 settings.json 骨架先把目录结构定下来后面所有路径都基于这个结构ppocrv5_offline/ ├── models/ │ ├── det/ │ │ └── ch_PP-OCRv5_det_infer.onnx │ ├── rec/ │ │ ├── ch_PP-OCRv5_rec_infer.onnx │ │ ├── japan_PP-OCRv5_rec_infer.onnx │ │ └── korean_PP-OCRv5_rec_infer.onnx │ └── cls/ │ └── ch_ppocr_mobile_v2.0_cls_infer.onnx ├── dicts/ │ ├── ppocrv5_dict.txt │ ├── japan_dict.txt │ └── korean_dict.txt ├── config.toml ├── settings.json └── infer.pyconfig.toml负责模型路径和推理参数settings.json负责工具侧接入和语种映射。先看config.toml[engine] provider CPUExecutionProvider # 有 GPU 时改成 CUDAExecutionProvider intra_op_num_threads 4 inter_op_num_threads 2 [det] model_path models/det/ch_PP-OCRv5_det_infer.onnx limit_side_len 960 thresh 0.3 box_thresh 0.6 unclip_ratio 1.5 [cls] enable true model_path models/cls/ch_ppocr_mobile_v2.0_cls_infer.onnx cls_thresh 0.9 [rec] model_path models/rec/ch_PP-OCRv5_rec_infer.onnx dict_path dicts/ppocrv5_dict.txt rec_img_h 48 rec_img_w 320 rec_batch_num 6 [rec.lang_map] ch { model models/rec/ch_PP-OCRv5_rec_infer.onnx, dict dicts/ppocrv5_dict.txt } japan { model models/rec/japan_PP-OCRv5_rec_infer.onnx, dict dicts/japan_dict.txt } korean { model models/rec/korean_PP-OCRv5_rec_infer.onnx, dict dicts/korean_dict.txt }再看settings.json这里放工具侧接入和验证码场景参数{ taotoken: { base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, timeout: 30 }, captcha: { default_lang: ch, lang_priority: [ch, japan, korean], min_confidence: 0.75, max_text_len: 8, preprocess: { grayscale: true, resize_scale: 2.0, denoise: true } }, postprocess: { enable_llm_correct: false, model: gpt-4o-mini } }rec_img_h和rec_img_w这两个参数很关键。PaddleOCRv5 的识别模型训练时输入高度是 48宽度按比例缩放后 padding 到 320。如果你导出的 ONNX 输入 shape 是动态的这两个值可以按实际图片调整如果是固定 shape就必须对齐否则 onnxruntime 会直接报维度不匹配。4. onnxruntime 推理代码与语种切换核心推理分三步检测框、方向分类、文字识别。下面这段代码只依赖 onnxruntime、numpy、cv2可以直接复制运行import json import os import cv2 import numpy as np import onnxruntime as ort class PPOCRv5Offline: def __init__(self, config_pathconfig.toml, settings_pathsettings.json): import tomllib with open(config_path, rb) as f: self.cfg tomllib.load(f) with open(settings_path, r, encodingutf-8) as f: self.settings json.load(f) provider self.cfg[engine][provider] self.det_sess ort.InferenceSession( self.cfg[det][model_path], providers[provider] ) self.cls_sess ort.InferenceSession( self.cfg[cls][model_path], providers[provider] ) self.rec_sess {} self.rec_dict {} for lang, item in self.cfg[rec][lang_map].items(): self.rec_sess[lang] ort.InferenceSession( item[model], providers[provider] ) with open(item[dict], r, encodingutf-8) as f: self.rec_dict[lang] [blank] [l.strip() for l in f.readlines()] def preprocess_det(self, img): h, w img.shape[:2] limit self.cfg[det][limit_side_len] ratio min(limit / max(h, w), 1.0) nh, nw int(h * ratio), int(w * ratio) resized cv2.resize(img, (nw, nh)) # 对齐到 32 的倍数 pad_h (32 - nh % 32) % 32 pad_w (32 - nw % 32) % 32 padded cv2.copyMakeBorder( resized, 0, pad_h, 0, pad_w, cv2.BORDER_CONSTANT, value(0, 0, 0) ) blob padded.astype(np.float32) / 255.0 blob (blob - np.array([0.485, 0.456, 0.406])) / np.array([0.229, 0.224, 0.225]) blob blob.transpose(2, 0, 1)[None, ...] return blob.astype(np.float32), ratio def preprocess_rec(self, img): h, w img.shape[:2] target_h self.cfg[rec][rec_img_h] target_w self.cfg[rec][rec_img_w] ratio w / h new_w min(int(target_h * ratio), target_w) resized cv2.resize(img, (new_w, target_h)) padded np.zeros((target_h, target_w, 3), dtypenp.uint8) padded[:, :new_w, :] resized blob padded.astype(np.float32) / 255.0 blob (blob - 0.5) / 0.5 blob blob.transpose(2, 0, 1)[None, ...] return blob.astype(np.float32) def ctc_decode(self, preds, lang): dict_list self.rec_dict[lang] indices preds.argmax(axis2)[0] result [] prev -1 for idx in indices: if idx ! 0 and idx ! prev: if idx len(dict_list): result.append(dict_list[idx]) prev idx return .join(result) def recognize(self, img_path, langNone): lang lang or self.settings[captcha][default_lang] img cv2.imread(img_path) if img is None: raise FileNotFoundError(img_path) blob, ratio self.preprocess_det(img) det_out self.det_sess.run(None, {self.det_sess.get_inputs()[0].name: blob})[0] # 简化处理取置信度最高的区域作为验证码区域 score_map det_out[0, 0] ys, xs np.where(score_map self.cfg[det][thresh]) if len(xs) 0: return {text: , confidence: 0.0, lang: lang} x1, x2 int(xs.min() / ratio), int(xs.max() / ratio) y1, y2 int(ys.min() / ratio), int(ys.max() / ratio) crop img[max(0, y1):y2, max(0, x1):x2] if crop.size 0: crop img rec_blob self.preprocess_rec(crop) rec_out self.rec_sess[lang].run( None, {self.rec_sess[lang].get_inputs()[0].name: rec_blob} )[0] text self.ctc_decode(rec_out, lang) confidence float(rec_out.max(axis2).mean()) return {text: text, confidence: confidence, lang: lang}语种切换就是换lang参数它会自动加载对应的 ONNX 模型和字典。验证码场景下如果不知道语种可以按settings.json里的lang_priority依次尝试取置信度最高的结果。5. 验证请求与成功结果先跑单张图验证if __name__ __main__: ocr PPOCRv5Offline() for lang in [ch, japan, korean]: res ocr.recognize(test_captcha.png, langlang) print(f[{lang}] text{res[text]} conf{res[confidence]:.4f})正常输出类似[ch] text7aK9 conf0.9312 [japan] text7aK9 conf0.8901 [korean] text7aK9 conf0.8745中英混排验证码在ch字典下置信度最高日韩字典因为字符集不同置信度会略低但文本一致。这说明模型和字典匹配正确。再验证工具侧接入。用 curl 测 TaoToken 通道是否通export TAOTOKEN_API_KEY你的Key curl -s https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ | head -c 500返回模型列表 JSON 就说明 Key 和通道正常。然后在 Python 里做后处理调用import os, requests def llm_correct(text): key os.environ.get(TAOTOKEN_API_KEY) resp requests.post( https://taotoken.net/api/v1/chat/completions, headers{Authorization: fBearer {key}}, json{ model: gpt-4o-mini, messages: [ {role: user, content: f纠正这个验证码识别结果只输出纠正后的文本{text}} ] }, timeout30 ) return resp.json()[choices][0][message][content].strip()离线批量识别时把enable_llm_correct设为false纯本地跑需要纠错时再打开走 TaoToken 通道。6. 本篇常见错排查报错一InvalidArgument: Input shape mismatch原因通常是rec_img_h或rec_img_w和导出 ONNX 时的固定 shape 不一致。用下面命令看模型输入维度import onnxruntime as ort sess ort.InferenceSession(models/rec/ch_PP-OCRv5_rec_infer.onnx) for i in sess.get_inputs(): print(i.name, i.shape)如果输出是[1, 3, 48, 320]那配置里就必须写 48 和 320不能改。报错二识别结果是乱码或空字符串九成是字典文件不匹配。PaddleOCRv5 的字典第一行通常是blank或者直接是字符列表CTC 解码时索引 0 要当 blank 处理。检查ctc_decode里idx ! 0这个条件以及字典读取时有没有手动加blank。如果字典本身第一行就是blank就不要再加否则整体索引偏移一位。报错三CUDAExecutionProvider加载失败先确认装了onnxruntime-gpu而不是onnxruntime两者不能共存。然后检查 CUDA 和 cuDNN 版本是否匹配 onnxruntime 的要求。离线机器上如果驱动版本旧直接退回CPUExecutionProvider验证码图片小CPU 推理单张通常在 50ms 以内够用。报错四检测框把整个图都框进去了调det的box_thresh和unclip_ratio。验证码通常文字集中、背景干净box_thresh可以提到 0.7unclip_ratio降到 1.2。如果还是框太大在预处理阶段先做一次灰度加二值化把背景噪点压掉。报错五TaoToken 调用返回 401检查环境变量TAOTOKEN_API_KEY是否真的导出到了当前 shell。export只在当前会话有效写进~/.bashrc或.env文件更稳。另外确认请求头是Authorization: Bearer key不是X-API-Key。7. 下一步把通道和模型都固定下来离线识别跑通之后建议把模型文件和字典做一次 md5 校验写进部署脚本避免换机器时拿错版本。TaoToken 的 Key 用环境变量注入接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果后面要做批量验证码识别加自动纠错的流水线可以把 Coding Plan 接进来做长期任务编排https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。模型对话验证用 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。先把单张识别稳定到置信度 0.9 以上再扩语种和批量顺序别反。
返回列表