简介:这份资源面向熟悉易语言、希望实现离线OCR文字识别功能的开发者,基于飞桨PaddleOCR框架封装了一套本地识别模块,可在Windows 7与Windows 10环境下无网运行,无需额外安装运行库,解决依赖网络API、部署繁琐的问题。资源包共8个文件,包含4张jpg示例图片、1份docx说明文档、1个html页面、1个txt文本与1份pdf资料,压缩包约1.78MB,便于快速查阅与对照测试。内容覆盖模块基本调用、高级参数设置、模型文件热替换及常见注意事项,并给出普通图片、字节集数据、倾斜图片等多场景代码示例,重点讲解大字体、倾斜文本等特殊情况的参数调优思路,帮助读者按需优化识别效果。目前已有189人学习下载,适合需要频繁批量处理图片、追求稳定离线识别方案的中高级易语言开发者参考。
1. 易语言 OCR 模块:离线识别在 Win7 老机器上到底能不能跑
手上有一批工控机,系统还是 Win7 SP1,CPU 是 Intel G630 这种老双核,内存 4GB,跑个浏览器都卡。业务上又需要把扫描件、截图、票据照片里的文字抠出来,还不能联网——车间网络隔离,数据也不允许出本地。这种场景下,用易语言写个 OCR 模块,基于飞桨(PaddleOCR)做离线推理,支持 Win7/Win10,多种图片格式,参数可调,就成了一个很实际的需求。
这个标题讲的就是这件事:易语言做壳,飞桨做引擎,本地离线跑 OCR,兼容 Win7 和 Win10,能调参数应对不同图片质量。适合谁?适合那些用易语言做工具、做自动化、做辅助软件的人,尤其是需要把 OCR 能力嵌进现有易语言程序里,又不想依赖云服务的场景。热词里提到的“易语言反编译”“乐玩模块在易语言中调用”“易语言窗口同步器源码”这些,说明易语言的生态里,模块化调用和底层集成是常见操作,OCR 模块也是同样的思路。
但问题来了:飞桨官方对 Win7 的支持并不友好,PaddlePaddle 从某个版本开始就只发 Win10 以上的 wheel 包。想在 Win7 上跑,要么用老版本飞桨,要么用 ONNX Runtime 做推理后端,把飞桨训练好的模型转成 ONNX 格式。我一般会选后者,因为 ONNX Runtime 对 Win7 的支持更稳,而且推理速度在 CPU 上也不差。下面就把这条路拆开讲,从环境准备到易语言调用,再到参数调整和踩坑记录,一步步来。
2. 飞桨模型转 ONNX 与 Win7 运行环境搭建
2.1 为什么选 ONNX Runtime 而不是直接上飞桨
飞桨的训练和推理生态确实强,但它的推理库对系统版本有要求。Paddle Inference 在 Windows 上的预编译包,从 2.0 之后基本只保证 Win10 及以上。Win7 上强行装,会遇到api-ms-win-core-path-l1-1-0.dll缺失这类问题,热词里也有人搜这个 dll,说明踩坑的人不少。ONNX Runtime 则一直保留对 Win7 的支持,1.10 之前的版本在 Win7 SP1 上跑得很稳,而且它只依赖 VC++ 运行库,没有其他系统级依赖。
另一个原因是模型转换。PaddleOCR 的检测模型(DB)和识别模型(CRNN)都可以导出为 ONNX。导出之后,推理代码可以用 C++ 写,编译成 DLL,易语言通过调用DLL命令就能用。这样易语言本身不需要装 Python,也不需要飞桨环境,部署时只要带上 ONNX Runtime 的 dll 和模型文件就行。
选型上,我一般会固定 ONNX Runtime 1.8.1 或 1.10.0 这两个版本,它们在 Win7 上经过大量验证,不会出现莫名其妙的崩溃。模型方面,用 PaddleOCR 的ch_PP-OCRv3_det_infer和ch_PP-OCRv3_rec_infer,这两个模型体积小,检测和识别效果在票据、截图场景下够用。
2.2 把飞桨模型导出为 ONNX 的具体命令
导出这一步在 Win10 的开发机上做,因为飞桨的导出工具在 Win10 上更顺。先装好 PaddleOCR 和 Paddle2ONNX,然后执行下面的命令。注意,这里用的是飞桨的 Python 环境,不是易语言环境。
# 安装必要库,建议在虚拟环境里做 pip install paddlepaddle==2.4.2 paddle2onnx==1.0.5 paddleocr==2.6.1.3 # 下载 PP-OCRv3 检测模型并导出 ONNX paddle2onnx --model_dir ./ch_PP-OCRv3_det_infer \ --model_filename inference.pdmodel \ --params_filename inference.pdiparams \ --save_file ./det_model.onnx \ --opset_version 11 \ --enable_onnx_checker True # 下载 PP-OCRv3 识别模型并导出 ONNX paddle2onnx --model_dir ./ch_PP-OCRv3_rec_infer \ --model_filename inference.pdmodel \ --params_filename inference.pdiparams \ --save_file ./rec_model.onnx \ --opset_version 11 \ --enable_onnx_checker True逻辑说明:--model_dir指向飞桨推理模型目录,里面必须有inference.pdmodel和inference.pdiparams两个文件。--opset_version 11是关键参数,ONNX Runtime 1.8.1 最高支持到 opset 12,但 opset 11 兼容性最好,不容易出算子不支持的问题。--enable_onnx_checker True会在导出后做一次校验,如果模型有问题会直接报错,省得后面在易语言里调试。
导出完成后,你会得到两个 onnx 文件,检测模型大概 2.5MB,识别模型大概 10MB。这两个文件加上 ONNX Runtime 的 dll,就是离线 OCR 的全部依赖。
2.3 Win7 上部署 ONNX Runtime 的目录结构和依赖
在 Win7 目标机器上,目录结构建议这样组织:
OCR_Module/ ├── onnxruntime.dll # ONNX Runtime 1.8.1 的 dll ├── det_model.onnx # 检测模型 ├── rec_model.onnx # 识别模型 ├── ocr_engine.dll # 自己编译的 C++ 推理封装 └── config.ini # 参数配置文件onnxruntime.dll从 ONNX Runtime 的 GitHub Release 里下载onnxruntime-win-x86-1.8.1.zip或onnxruntime-win-x64-1.8.1.zip,解压后lib目录下的 dll 就是。注意,Win7 上如果装的是 32 位系统,必须用 x86 版本;64 位系统用 x64 版本。G630 是 64 位 CPU,但工控机可能装的是 32 位 Win7,这个要确认清楚。
VC++ 运行库也要装。ONNX Runtime 1.8.1 依赖msvcp140.dll和vcruntime140.dll,如果目标机器没装 VC++ 2015-2019 运行库,需要把这两个 dll 一起放到目录里,或者装一个vc_redist.x86.exe。热词里有人搜apimswincorepathl110dll下载win7,这个 dll 是 Win10 才有的,Win7 上如果遇到这个报错,说明用错了 ONNX Runtime 版本,换回 1.8.1 就能解决。
2.4 用 C++ 写一个易语言能调的 OCR 推理 DLL
易语言不能直接调 ONNX Runtime 的 C++ API,所以中间要加一层 DLL。这个 DLL 用 C++ 写,导出几个简单的函数:初始化、识别、释放。下面是一个最小实现的代码片段。
// ocr_engine.cpp #include <onnxruntime_cxx_api.h> #include <opencv2/opencv.hpp> #include <string> #include <vector> static Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "OCR"); static Ort::Session* det_session = nullptr; static Ort::Session* rec_session = nullptr; extern "C" __declspec(dllexport) int OCR_Init(const char* det_path, const char* rec_path) { Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(2); // G630 双核,设 2 就行 session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); det_session = new Ort::Session(env, det_path, session_options); rec_session = new Ort::Session(env, rec_path, session_options); return 0; } extern "C" __declspec(dllexport) int OCR_Recognize(const char* image_path, char* result_buf, int buf_size) { // 读取图片,做检测和识别,把结果写入 result_buf // 具体推理代码略,核心是调用 det_session 和 rec_session 的 Run 方法 // 返回识别到的文字,用 \n 分隔 return 0; } extern "C" __declspec(dllexport) void OCR_Release() { delete det_session; delete rec_session; det_session = nullptr; rec_session = nullptr; }逻辑说明:OCR_Init接收两个模型路径,创建 ONNX Runtime 的会话。SetIntraOpNumThreads(2)是针对 G630 这种双核 CPU 的优化,线程数设成物理核心数,多了反而会抢资源。OCR_Recognize接收图片路径和一个输出缓冲区,把识别结果写进去。OCR_Release释放资源。
编译这个 DLL 需要 ONNX Runtime 的头文件和 lib,以及 OpenCV 的头文件和 lib。OpenCV 用来做图片解码和预处理,因为 ONNX Runtime 本身不处理图片格式。编译成 32 位或 64 位要和目标系统一致。
2.5 易语言里调用 DLL 的代码和参数配置
易语言这边就简单了,用调用DLL命令声明三个函数,然后按流程调用。
.版本 2 .DLL命令 OCR_Init, 整数型, "ocr_engine.dll", "OCR_Init" .参数 det_path, 文本型 .参数 rec_path, 文本型 .DLL命令 OCR_Recognize, 整数型, "ocr_engine.dll", "OCR_Recognize" .参数 image_path, 文本型 .参数 result_buf, 文本型, 传址 .参数 buf_size, 整数型 .DLL命令 OCR_Release, , "ocr_engine.dll", "OCR_Release" .子程序 _按钮_识别_被单击 .局部变量 结果, 文本型 .局部变量 返回值, 整数型 结果 = 取空白文本 (4096) 返回值 = OCR_Init (“./det_model.onnx”, “./rec_model.onnx”) .如果真 (返回值 ≠ 0) 信息框 (“初始化失败”, 0, , ) 返回 () .如果真结束 返回值 = OCR_Recognize (“./test.png”, 结果, 4096) .如果真 (返回值 = 0) 编辑框_结果.内容 = 结果 .如果真结束 OCR_Release ()逻辑说明:OCR_Init在程序启动时调一次就行,不用每次识别都调。OCR_Recognize的result_buf参数要传址,易语言里用传址属性。buf_size设 4096 够放一般票据的文字,如果图片文字特别多,可以加大到 8192 或 16384。识别完成后调OCR_Release释放资源。
参数配置方面,检测模型的输入尺寸默认是 960x960,识别模型输入高度是 48,宽度自适应。这些参数在 C++ DLL 里可以做成可调的,通过config.ini读取。比如检测阈值det_db_thresh默认 0.3,识别置信度阈值rec_thresh默认 0.5,这些都可以在 ini 里改。
3. 图片格式兼容与识别参数调整的实操细节
3.1 支持多种图片格式的解码方案
标题里说“多种图片格式”,实际业务里常见的是 JPG、PNG、BMP,偶尔有 TIFF 和 GIF。OpenCV 的imread默认支持 JPG、PNG、BMP、TIFF,但 GIF 需要额外处理。如果不想依赖 OpenCV 的编解码,也可以用 Windows 自带的 WIC(Windows Imaging Component),但 WIC 在 Win7 上对某些格式的支持不如 OpenCV 全。
我一般会在 C++ DLL 里用 OpenCV 的imread读图,如果返回空 Mat,再尝试用imdecode从内存读。对于 GIF,只取第一帧。代码里加一个格式判断:
cv::Mat LoadImage(const std::string& path) { cv::Mat img = cv::imread(path, cv::IMREAD_COLOR); if (img.empty()) { // 尝试用二进制方式读取,再用 imdecode std::ifstream file(path, std::ios::binary); std::vector<uchar> buf((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); img = cv::imdecode(buf, cv::IMREAD_COLOR); } return img; }逻辑说明:imread直接读文件,如果失败,用imdecode从内存解码。这样能覆盖绝大多数格式。如果图片是 CMYK 模式的 JPG,OpenCV 读出来颜色会不对,需要在读之前转成 RGB,或者用cv::cvtColor转一下。
3.2 检测和识别模型的关键参数怎么调
PaddleOCR 的检测模型输出的是文本框,识别模型输出的是文字和置信度。影响识别效果的关键参数有三个:检测阈值、识别阈值、输入尺寸。
检测阈值det_db_thresh控制文本框的判定。默认 0.3,如果图片背景复杂、文字和背景对比度低,可以降到 0.2,让更多候选框通过。但降太低会引入噪声,把非文字区域也框进去。识别阈值rec_thresh控制文字置信度,默认 0.5,如果识别出来的文字明显不对,可以降到 0.3,但会混入更多错误结果。
输入尺寸方面,检测模型的输入长边默认 960,如果图片分辨率很高(比如 4000x3000),可以调到 1280 或 1600,但推理时间会线性增加。G630 上跑 960 大概 200ms 一张,跑 1600 要 500ms 以上。识别模型的输入高度固定 48,宽度按比例缩放,这个不用改。
这些参数在 C++ DLL 里通过config.ini读取,易语言那边不用管。配置文件长这样:
[det] det_db_thresh=0.3 det_db_box_thresh=0.5 det_limit_side_len=960 [rec] rec_thresh=0.5 rec_batch_num=1det_db_box_thresh是文本框的置信度阈值,默认 0.5,如果框太多太碎,可以提到 0.6。rec_batch_num是识别时的批处理数量,G630 上设 1 就行,设大了内存扛不住。
3.3 在易语言里做参数热更新的方法
参数改一次就要重新编译 DLL 太麻烦,我一般会在 DLL 里加一个OCR_SetParam函数,易语言可以随时调它改参数。
extern "C" __declspec(dllexport) int OCR_SetParam(const char* key, float value) { std::string k(key); if (k == "det_db_thresh") { g_det_db_thresh = value; } else if (k == "rec_thresh") { g_rec_thresh = value; } else if (k == "det_limit_side_len") { g_det_limit_side_len = (int)value; } return 0; }易语言里这样调:
.DLL命令 OCR_SetParam, 整数型, "ocr_engine.dll", "OCR_SetParam" .参数 key, 文本型 .参数 value, 小数型 ' 在识别前调整参数 OCR_SetParam (“det_db_thresh”, 0.25) OCR_SetParam (“rec_thresh”, 0.4)逻辑说明:OCR_SetParam接收字符串键和浮点值,内部更新全局变量。这样在易语言界面上可以放几个输入框,让用户自己调,调完立即生效,不用重启程序。注意,det_limit_side_len是整数,但用浮点传也没问题,内部转一下就行。
3.4 识别结果的后处理和输出格式
识别出来的文字是带坐标的,易语言里可能只需要纯文本,也可能需要按行输出。我一般会在 DLL 里把结果整理成两种格式:一种是纯文本,用\n分隔;另一种是带坐标的 JSON,方便易语言解析。
std::string FormatResult(const std::vector<TextBox>& boxes) { std::string result; for (const auto& box : boxes) { result += box.text + "\n"; } return result; }如果要做票据识别,比如热词里提到的“ocr识别固定模板票据”,那就需要按坐标排序,把同一行的文字拼起来。排序规则是先按 y 坐标从上到下,再按 x 坐标从左到右。这个逻辑在 DLL 里做,易语言只拿最终结果。
输出格式可以在config.ini里配,output_format=text或output_format=json。JSON 格式用简单的字符串拼接就行,不用引第三方库,减少依赖。
4. 避坑记录:Win7 兼容性与易语言调用的五个翻车点
4.1 现象:程序在 Win7 上启动就报“无法定位程序输入点”
原因:ONNX Runtime 版本选错了。1.10 之后的版本用了 Win10 特有的 API,Win7 上加载 dll 时就会报这个错。热词里有人搜apimswincorepathl110dll下载win7,就是这个问题的典型表现。
解决:换回 ONNX Runtime 1.8.1 或 1.10.0。下载时注意选onnxruntime-win-x86-1.8.1.zip或onnxruntime-win-x64-1.8.1.zip,不要选onnxruntime-win10之类的。如果已经装了高版本,卸载后清理注册表,再装低版本。
4.2 现象:易语言调用 DLL 时程序直接崩溃,没有任何提示
原因:DLL 的调用约定不对。易语言默认用的是stdcall,而 C++ 导出的函数默认是cdecl。如果声明时没指定,栈会乱掉,直接崩。
解决:在 C++ 导出函数时加__stdcall,或者在易语言的 DLL 命令里指定调用约定。我一般会在 C++ 里统一用__stdcall:
extern "C" __declspec(dllexport) int __stdcall OCR_Init(const char* det_path, const char* rec_path);易语言这边不用改,默认就是 stdcall。如果还是崩,检查参数类型是否匹配,尤其是字符串和缓冲区的传址。
4.3 现象:识别结果全是乱码,或者只识别出几个字
原因:图片预处理没做对。PaddleOCR 的检测模型要求输入是 RGB 三通道,归一化到 [0,1],再减均值除方差。如果直接把 OpenCV 读出来的 BGR 图丢进去,颜色通道反了,检测框会偏,识别自然不准。
解决:在 C++ 里做预处理时,先cvtColor(img, img, cv::COLOR_BGR2RGB),再img.convertTo(img, CV_32FC3, 1.0/255.0),然后按 PaddleOCR 的均值[0.485, 0.456, 0.406]和方差[0.229, 0.224, 0.225]做归一化。这些参数在导出 ONNX 时已经固化在模型里,但输入数据的格式必须匹配。
4.4 现象:G630 上识别一张图要好几秒,CPU 占用 100%
原因:ONNX Runtime 的线程数设多了,或者检测模型的输入尺寸设大了。G630 只有两个物理核心,如果SetIntraOpNumThreads设成 4 或 8,线程切换开销反而拖慢速度。
解决:线程数设成物理核心数,G630 就设 2。检测模型的det_limit_side_len从 960 降到 640,识别速度能快一倍,代价是小文字可能检测不到。如果业务场景里文字比较大,640 完全够用。另外,session_options.SetGraphOptimizationLevel设成ORT_ENABLE_ALL,让 ONNX Runtime 做图优化。
4.5 现象:Win10 上跑得好好的,拷到 Win7 上就提示缺少 msvcp140.dll
原因:目标机器没装 VC++ 运行库。ONNX Runtime 和 OpenCV 都依赖 VC++ 2015-2019 的运行库,Win10 自带,Win7 不一定有。
解决:把msvcp140.dll、vcruntime140.dll、concrt140.dll这几个文件一起放到程序目录里,或者装一个vc_redist.x86.exe。注意,如果程序是 64 位的,要装 x64 版本的运行库;32 位程序装 x86 版本。不确定的话,两个都装。
5. 进阶技巧:用缓存和批处理把 G630 的识别速度再压一压
G630 这种老 CPU,单张识别 200ms 已经不错了,但如果要批量处理几百张图片,总时间还是可观。我一般会加两个优化:模型缓存和批处理。
模型缓存是指OCR_Init只调一次,把det_session和rec_session常驻内存。易语言程序启动时初始化,退出时释放。这样省掉了每次识别重新加载模型的开销,加载一次大概 1-2 秒,批量处理时这个开销不能忽略。
批处理是指一次读多张图,在 C++ 里循环推理,而不是易语言循环调OCR_Recognize。易语言每次调用 DLL 都有开销,虽然不大,但几百次累积起来也有几百毫秒。更好的做法是在 DLL 里加一个OCR_RecognizeBatch函数,接收图片路径数组,返回结果数组。
extern "C" __declspec(dllexport) int __stdcall OCR_RecognizeBatch(const char** image_paths, int count, char* result_buf, int buf_size) { std::string all_results; for (int i = 0; i < count; i++) { std::string single_result = RecognizeSingle(image_paths[i]); all_results += single_result + "\n---\n"; } strncpy_s(result_buf, buf_size, all_results.c_str(), buf_size - 1); return 0; }易语言里把图片路径拼成一个文本数组,传进去,拿回来的结果用---分隔。这样一次调用处理多张图,减少 DLL 边界切换。
还有一个技巧是图片预处理缓存。如果同一张图要反复识别(比如调参数时),可以把预处理后的张量缓存起来,改参数时只重跑推理,不重跑预处理。这个在调试阶段特别有用,能省不少时间。
验证方法很简单:拿一张标准测试图,记录单张识别时间,然后跑 100 张,看总时间是不是接近单张时间乘以 100。如果差太多,说明有额外的开销,检查是不是每次都在重新加载模型或者重新初始化 ONNX Runtime。
我自己的习惯是,在易语言界面上加一个“性能测试”按钮,点一下跑 10 张图,显示平均耗时和 CPU 占用。这样换参数、换模型时,能直观看到效果。G630 上,检测 640 + 识别 48 高度,平均 150ms 一张,批量 100 张大概 15 秒,这个速度在工控场景下完全能接受。
希望帮到你。
本文还有配套的精品资源,点击获取