目录
[为了支持 Win7,我们具体做了什么](#为了支持 Win7,我们具体做了什么)
[1. 从源码编译 OpenCV 5](#1. 从源码编译 OpenCV 5)
[2. 明确指定 Windows 7 系统版本](#2. 明确指定 Windows 7 系统版本)
[3. 静态集成 OpenCV 和 C++ 运行时](#3. 静态集成 OpenCV 和 C++ 运行时)
[4. 使用兼容 .NET Framework 3.5 的 C# 调用层](# 调用层)
[5. 避免 GPU 和新驱动依赖](#5. 避免 GPU 和新驱动依赖)
[6. 做成可以整体复制的绿色工程](#6. 做成可以整体复制的绿色工程)
[为什么提供 C ABI](#为什么提供 C ABI)
[C# 完整调用代码](# 完整调用代码)
[1. 为什么程序只能使用x64?](#1. 为什么程序只能使用x64?)
[2. cpu_threads=0是什么意思?](#2. cpu_threads=0是什么意思?)
[3. 模型放在中文目录会不会失败?](#3. 模型放在中文目录会不会失败?)
[4. 初始化失败先检查什么?](#4. 初始化失败先检查什么?)
[5. 8080端口被占用怎么办?](#5. 8080端口被占用怎么办?)
[6. 能不能多线程调用?](#6. 能不能多线程调用?)
[7. 支持哪些图片?](#7. 支持哪些图片?)
[8. 为什么钢印、反光文字效果不好?](#8. 为什么钢印、反光文字效果不好?)
很多人以为 Win7 已经离开主流,就等于离开了生产现场。
但在工控机、产线终端、检测设备和老旧业务系统里,情况完全不同:设备运行稳定、改造成本高、上位机软件仍停留在 .NET Framework 3.5,既不能随意升级系统,也不适合安装一长串运行环境。
为了解决这类真实需求,我做了 lw.OpenCVDNN.PPOCR 1.0.0.1:基于 OpenCV 5 DNN 和 PP-OCRv6 的 x64 纯 CPU OCR 组件,并把 OpenCV 与 C++ 运行时静态集成进 DLL。部署时不需要 CUDA、不需要 Python,也不需要再携带一组 OpenCV 动态库。
一个很典型的工业现场
假设产线上有一台已经稳定运行多年的工控机:Windows 7 SP1 x64、4核CPU、没有独立显卡,上位机程序还是 .NET Framework 3.5。
现在需要增加标签、包装或铭牌 OCR。如果直接引入 Python,现场需要处理解释器、第三方包和环境变量;如果选择 GPU 方案,又会碰到显卡型号、驱动和 CUDA 版本;如果升级操作系统和 .NET,原有采集卡、PLC 通讯组件或专用驱动也可能受到影响。
这类设备真正需要的不是最复杂的技术栈,而是:
-
保留现有 Windows 和上位机程序。
-
不安装 Python,不配置 CUDA。
-
解压即可运行,离线也能部署。
-
C# 3.5 可以直接调用,其他语言也有接口。
-
出错时能看到日志、模型路径和明确的初始化信息。
lw.OpenCVDNN.PPOCR 就是围绕这些约束做的一套工程化实现。
先看结论
核心引擎:OpenCV 5 DNN
OCR模型:PP-OCRv5 / PP-OCRv6 tiny / PP-OCRv6 small
运行设备:CPU
原生组件:x64 C++ DLL,约14MB
C#版本:.NET Framework 3.5
部署方式:DLL直调 / HTTP服务 / Windows服务
模型路径:支持中文路径
额外运行时:无需独立OpenCV DLL,无需VC++运行库,无需Python和CUDA
这次发布包含什么
-
lw.OpenCVDNN.PPOCR.dll:约 14MB 的原生 OCR DLL,提供稳定 C ABI。 -
lw.OpenCVDNN.PPOCR.HttpServer.exe:开箱即用的 HTTP OCR 服务。 -
.NET Framework 3.5WinForms 测试程序:可选择图片、初始化模型、查看耗时和文本框。 -
PP-OCRv5、PP-OCRv6 tiny/small 模型与字典。
-
浏览器测试页面:上传图片后显示识别文字、坐标和 OCR 耗时,并绘制检测框。
-
C/C++ 头文件、导入库、C# P/Invoke 参考封装和 SDK 接入说明。
整个推理链只考虑 CPU。少一层后端选择,就少一批驱动、版本和部署问题,对工控现场反而更实用。
为什么没有直接选择其他方案
不同方案都有自己的价值。这里的选择标准不是"谁在所有环境里最强",而是谁更适合老旧、离线、依赖受限的工控设备。
| 方案 | 典型部署依赖 | Win7与老.NET接入 | GPU要求 | 本项目中的定位 |
|---|---|---|---|---|
| Python + PaddleOCR | Python、Paddle及第三方包 | 通常需要进程或HTTP中转 | 可选 | 研发验证方便,现场部署较重 |
| OpenCvSharp + OpenCV | 托管组件与原生OpenCV库 | 需要匹配可用版本 | 不需要 | C#开发方便,但组件数量更多 |
| ONNX Runtime | ONNX Runtime动态库 | 取决于运行库版本和封装 | 可选 | 通用性好,需额外维护运行时 |
| TensorRT | CUDA、cuDNN、TensorRT及驱动 | 不适合无NVIDIA GPU设备 | 必需 | NVIDIA设备上追求极致速度 |
| 本项目 | DLL、模型和字典 | 提供.NET 3.5封装及C ABI | 不需要 | 面向纯CPU、绿色部署和旧系统 |
这并不意味着 OpenCV DNN 在所有硬件上都最快。它的优势是依赖少、接口稳定、模型通用、部署路径清晰,特别适合维护周期长的工业软件。
一张图看懂调用链
JPG/PNG/BMP图片
│
├── C#、C++等程序传入图片字节
└── HTTP客户端传入Base64
│
▼
lw.OpenCVDNN.PPOCR.dll
│
OpenCV 5 DNN CPU推理
│
文本检测 → 透视裁剪 → 方向分类(可选)→ 文字识别
│
▼
UTF-8 JSON:文字、置信度、四点坐标、OCR耗时
为了支持 Win7,我们具体做了什么
"支持 Win7"不是把工程里的目标系统名称改一下就结束了。OpenCV 5、C++ 运行库、系统 API 和 .NET 调用层只要有一处引入高版本依赖,程序就可能在目标机器上直接报错或闪退。
这次主要做了以下兼容处理。
1. 从源码编译 OpenCV 5
项目没有直接使用面向新系统发布的通用 OpenCV 二进制包,而是保存了完整 OpenCV 5.0.0 源码、构建配置、头文件和编译后的静态库。
OpenCV 使用 Visual Studio 2019 的 MSVC v142 工具集编译。v142 能满足 OpenCV 5 对 C++17 的要求,同时比 VS2022 的 v143 更适合作为 Win7 兼容目标。整个源码和静态库都放在项目目录中,复制到另一台编译机后仍然可以复现。
2. 明确指定 Windows 7 系统版本
C++ DLL 和 HTTP Server 都设置了:
WINVER=0x0601
_WIN32_WINNT=0x0601
PE Subsystem Version=6.01
6.01 对应 Windows 7。这样可以在编译和链接阶段约束目标系统,减少无意调用高版本 Windows API 的风险。
3. 静态集成 OpenCV 和 C++ 运行时
OpenCV 5 相关模块使用静态库链接,MSVC 运行时使用 /MT 静态链接。最终 lw.OpenCVDNN.PPOCR.dll 不需要额外携带 opencv_world500.dll,也不要求客户机器预先安装 Visual C++ Redistributable。
实际检查 DLL 导入表后,系统依赖只剩:
KERNEL32.dll
ole32.dll
这两项都是 Windows 系统组件。对工业现场来说,依赖越少,安装失败和版本冲突的概率就越低。
4. 使用兼容 .NET Framework 3.5 的 C# 调用层
测试程序不是只面向新版 .NET,而是建立了完整的 .NET Framework 3.5 WinForms x64 项目。C# 通过 P/Invoke 调用稳定的 C ABI,不依赖 OpenCvSharp,也不要求安装 NuGet 运行组件。
模型路径和错误消息使用 UTF-16,OCR 结果使用 UTF-8 JSON。这样既能处理中文安装目录,也能让老版本 C# 正确接收中文识别结果。
5. 避免 GPU 和新驱动依赖
Win7 工控机的显卡型号、驱动版本和 CUDA 环境通常并不统一,因此这一版只保留 OpenCV DNN CPU 推理。代码中已经移除 GPU 参数和相关分支,不需要 CUDA、cuDNN、DirectML 或 OpenVINO 运行时。
6. 做成可以整体复制的绿色工程
源码、解决方案、OpenCV 静态库、第三方组件、模型、测试图片和发布脚本全部放在同一目录。程序根据 EXE 所在目录寻找 inference 和 www,不依赖启动时的当前工作目录,放在中文路径下也能正常初始化。
需要说明的是,不同 Win7 工控机的补丁、CPU 指令集和系统组件可能不同。项目已经按 Win7 SP1 x64 目标构建并控制依赖,正式部署前仍建议在客户的实际设备或同配置镜像中完成一次验收测试。
绿色包里有什么
lw.OpenCVDNN.PPOCR.Win7-x64/
lw.OpenCVDNN.PPOCR.dll OCR原生动态库
lw.OpenCVDNN.PPOCR.HttpServer.exe HTTP服务
lw.OpenCVDNN.PPOCR.Win7.Test.exe .NET 3.5测试程序
inference/ PP-OCR模型和字典
www/ 浏览器测试页面
deploy/ Windows服务安装/卸载脚本
sdk/
native/ DLL、LIB和ppocr_api.h
csharp/ NativeOcr.cs参考封装
SDK_INTEGRATION.md 多语言接入说明
test-images/ 测试图片
start_console.bat 启动HTTP服务
start_winforms_test.bat 启动窗体测试程序
普通用户只需要关心两个启动脚本;C#开发者可以直接查看 sdk/csharp;C、C++、Delphi、Python ctypes、Java JNA 等语言可以依据 sdk/native/ppocr_api.h 对接。
发布版怎么用
解压绿色包后,目录中已经带有服务程序、DLL、模型、网页和测试图片。
双击 start_console.bat,默认启动:
http://127.0.0.1:8080/
浏览器打开这个地址,选择图片并点击识别,就能看到 OCR 结果和文本框。接口地址为:
POST /api/ocr
Content-Type: application/json
{"imageBase64":"图片的Base64内容"}
需要作为 Windows 服务运行时,以管理员身份执行 deploy/install_service.bat;卸载执行 deploy/uninstall_service.bat。
需要测试原生 DLL 时,双击 start_winforms_test.bat。界面中的"CPU线程=0"表示使用 OpenCV 默认线程策略;在多实例或高并发机器上,可以分别测试 1、2、4,实际吞吐往往比直接填满逻辑核心更有参考价值。
实际运行效果
HTTP Server 启动后,会在命令行中输出产品版本、作者信息、监听地址、模型路径和主要推理参数。DLL 初始化时也会固定输出版本与作者信息,方便确认加载的是正式发布组件。
使用发布包中的 3.jpg 和 PP-OCRv6 tiny 模型进行最终回归,一次测试结果如下:
接口返回:code=0
OCR耗时:165.328 ms
检测文本框:16个
第一行识别结果:纯臻营养护发素
这里的耗时是开发机单次回归结果,只用于证明完整发布链路可以正常运行。实际速度会受到 CPU 型号、图片尺寸、文本数量、模型版本、CPU 线程数和同时请求数量影响,应以目标工控机连续多次测试后的平均值为准。
浏览器页面可以直接上传 JPG、PNG 等图片,识别完成后显示文字、置信度、坐标和 OCR 耗时,并在原图上绘制检测框。

.NET Framework 3.5 WinForms 测试程序支持 PP-OCRv5、PP-OCRv6 tiny/small 模型切换,也可以调整检测阈值、识别批次、预测器数量和 CPU 线程数。初始化成功或失败、模型参数和多次识别耗时都会显示在界面中。

怎样正确测试速度
OCR性能不能只报一次"最快耗时"。首次推理通常包含缓存和内存准备,图片尺寸、文本框数量、CPU线程数也会明显影响结果。建议按下面的方法记录:
-
固定CPU电源模式、模型、图片和检测参数。
-
初始化模型后先预热5次,不计入统计。
-
连续识别100次,记录平均值、P50、P95和最大值。
-
同时记录初始化时间、图片尺寸、文本框数量和进程内存。
-
分别测试
cpu_threads=0/1/2/4,不要只看单次最低值。 -
HTTP场景还要分别测试单请求延迟和多客户端总吞吐。
本次最终发布链路回归数据可以作为一条基线:
| 环境 | 模型 | 图片 | 文本框 | 单次OCR耗时 | 结果 |
|---|---|---|---|---|---|
| 当前开发机 | PP-OCRv6 tiny | 3.jpg |
16 | 165.328ms | 成功 |
| Win7目标工控机 | 待你们填写发在评论区 | 待你们填写发在评论区 | 待你们填写发在评论区 | 待你们填写发在评论区 | 待你们填写发在评论区 |
为什么提供 C ABI
C++ 类接口容易受到编译器、运行库和 ABI 变化影响。发布版只暴露一组 extern "C" + __cdecl 函数,模型路径使用 UTF-16,识别结果统一返回 UTF-8 JSON。
因此除了 C#,C、C++、Python ctypes、Delphi、Java JNA 等语言也可以直接对接。SDK 位于发布包的 sdk 目录,其中包含 DLL、LIB、ppocr_api.h、C# 封装和完整说明。
接口返回格式保持简单:
{
"code": 0,
"msg": "ok",
"elapsed_ms": 165.328,
"results": [
{
"text": "纯臻营养护发素",
"score": 1.0,
"x1": 22, "y1": 32,
"x2": 307, "y2": 32,
"x3": 307, "y3": 75,
"x4": 22, "y4": 75
}
]
}
前端可以直接利用四点坐标绘制文本框,业务程序也可以按照置信度过滤结果。
curl调用HTTP接口
curl -X POST "http://127.0.0.1:8080/api/ocr" \
-H "Content-Type: application/json" \
-d "{\"imageBase64\":\"这里填写图片Base64\"}"
浏览器JavaScript调用
const response = await fetch("http://127.0.0.1:8080/api/ocr", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ imageBase64 })
});
const result = await response.json();
console.log(result.elapsed_ms, result.results);
C# 完整调用代码
下面是一份可直接加入 .NET Framework 3.5 x64 控制台项目的完整示例。将 DLL 放在 EXE 同目录,并确认 inference 中存在对应模型。
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
classProgram
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
privatestruct PpocrConfig
{
publicint struct_size;
[MarshalAs(UnmanagedType.LPWStr)] publicstring det_model_path;
[MarshalAs(UnmanagedType.LPWStr)] publicstring rec_model_path;
[MarshalAs(UnmanagedType.LPWStr)] publicstring rec_dict_path;
[MarshalAs(UnmanagedType.LPWStr)] publicstring cls_model_path;
publicint limit_side_len;
publicdouble det_db_thresh;
publicdouble det_db_box_thresh;
publicdouble det_db_unclip_ratio;
publicint use_dilation;
publicint use_angle_cls;
publicdouble cls_thresh;
publicint cls_batch_num;
publicint rec_batch_num;
publicint rec_img_h;
publicint rec_img_w;
publicint rec_predictor_num;
publicint cpu_threads;
}
privateconststring DllName = "lw.OpenCVDNN.PPOCR.dll";
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
private static extern void ppocr_config_init(ref PpocrConfig config);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode)]
private static extern int ppocr_create_ex_w(ref PpocrConfig config,
out IntPtr engine, StringBuilder error, int errorCapacity);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode)]
private static extern int ppocr_ocr_encoded(IntPtr engine,
IntPtr imageBytes, int imageSize, out IntPtr utf8Json,
out int jsonSize, StringBuilder error, int errorCapacity);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
private static extern void ppocr_free(IntPtr memory);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
private static extern void ppocr_destroy(IntPtr engine);
static void Main(string[] args)
{
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string modelDir = Path.Combine(baseDir, "inference");
string imagePath = args.Length > 0
? Path.GetFullPath(args[0])
: Path.Combine(baseDir, "test-images", "3.jpg");
PpocrConfig config = new PpocrConfig();
ppocr_config_init(ref config);
config.det_model_path = Path.Combine(modelDir, "PP-OCRv6_tiny_det.onnx");
config.rec_model_path = Path.Combine(modelDir, "PP-OCRv6_tiny_rec.onnx");
config.rec_dict_path = Path.Combine(modelDir, "PP-OCRv6_tiny_rec_dict.txt");
config.rec_predictor_num = 1;
config.cpu_threads = 0;
IntPtr engine = IntPtr.Zero;
StringBuilder error = new StringBuilder(512);
int code = ppocr_create_ex_w(ref config, out engine, error, error.Capacity);
if (code != 0)
thrownew InvalidOperationException("初始化失败(" + code + "): " + error);
try
{
byte[] image = File.ReadAllBytes(imagePath);
GCHandle pinned = GCHandle.Alloc(image, GCHandleType.Pinned);
IntPtr jsonPtr = IntPtr.Zero;
try
{
int jsonSize;
error.Length = 0;
code = ppocr_ocr_encoded(engine, pinned.AddrOfPinnedObject(),
image.Length, out jsonPtr, out jsonSize, error, error.Capacity);
if (code != 0)
thrownew InvalidOperationException("识别失败(" + code + "): " + error);
byte[] jsonBytes = newbyte[jsonSize];
Marshal.Copy(jsonPtr, jsonBytes, 0, jsonSize);
Console.WriteLine(Encoding.UTF8.GetString(jsonBytes));
}
finally
{
if (jsonPtr != IntPtr.Zero) ppocr_free(jsonPtr);
pinned.Free();
}
}
finally
{
if (engine != IntPtr.Zero) ppocr_destroy(engine);
}
}
}
这段代码中特别重要的是两点:项目必须编译为 x64;DLL 返回的 JSON 内存必须调用 ppocr_free,不能交给 C# 自己释放。
为中文路径和长期运行做的处理
工业现场的软件经常安装在中文目录。原生 API 使用 UTF-16 模型路径,HTTP 服务也会基于 EXE 目录解析模型和网页,不依赖当前工作目录。
作者信息由 DLL 初始化函数固定输出。控制台直接使用 Unicode,输出重定向时使用 UTF-8;HTTP 服务启动时也会设置 UTF-8 控制台代码页,解决中文作者信息乱码。
在资源管理方面,模型、预测器、Windows 文件句柄和 OCR 返回内存都使用明确的 RAII 或成对释放策略。预测器即使在 OpenCV 推理抛异常时也会自动归还池中,避免长期服务出现请求越跑越少、最后全部等待的问题。
这次还做了哪些稳定性处理
这次不是只把模型"跑起来",还对长期服务中容易出问题的资源路径做了专项整理:
-
OCR主对象改为智能指针,模型加载中途抛异常也能自动释放已经创建的对象。
-
Windows模型文件句柄使用RAII封装,读取或内存分配失败时不会遗留句柄。
-
识别预测器使用自动租约,OpenCV推理异常时仍会归还预测器池。
-
修复多路径轮廓展开可能使用错误长度而越界的问题。
-
过滤无效文字裁剪,避免空图进入识别网络。
-
DLL返回的UTF-8 JSON统一通过
ppocr_free释放。 -
C#封装将识别与销毁串行化,避免原生句柄正在使用时被释放。
-
HTTP日志按天和大小滚动,单文件最大1MB,最多保留7天。
更准确的表述是:项目已经对主要资源所有权和异常路径进行了专项审计。任何需要长期无人值守运行的工业项目,仍建议在目标设备上执行数小时或数天的循环压力测试,并观察句柄数、线程数和内存是否稳定。
常见问题
1. 为什么程序只能使用x64?
当前OpenCV静态库、原生DLL、HTTP服务和WinForms测试程序统一按x64编译。调用方也必须选择x64,不能使用x86;老项目使用Any CPU时,建议取消"首选32位"并明确改为x64。
2. cpu_threads=0是什么意思?
0 表示保留OpenCV默认线程策略;正整数会调用 cv::setNumThreads。线程越多不一定越快,多请求并发时还可能产生CPU过度竞争,应在目标机器实测1、2、4和0。
3. 模型放在中文目录会不会失败?
模型路径通过UTF-16传入DLL,程序也以EXE目录解析相对路径,已经验证中文路径场景。HTTP控制台使用UTF-8,DLL作者信息使用Unicode输出。
4. 初始化失败先检查什么?
先确认进程为x64,再检查DLL是否与EXE同目录、模型和字典文件是否存在、识别模型与字典是否配套,然后查看界面初始化信息或 logs 目录。
5. 8080端口被占用怎么办?
可以指定其他端口:
lw.OpenCVDNN.PPOCR.HttpServer.exe --port 18080
6. 能不能多线程调用?
同一个OCR句柄的调用会在DLL中串行保护。需要并行吞吐时可以由上层建立多个独立实例,但每个实例都会加载模型并占用内存,必须根据CPU核心数和内存容量测试。
7. 支持哪些图片?
编码图片接口由OpenCV解码,常见的JPG、PNG、BMP可以直接传入。HTTP接口使用Base64上传,默认限制请求大小,超大图片建议先缩放或裁剪ROI。
8. 为什么钢印、反光文字效果不好?
OCR模型更擅长对比度清晰的印刷文字。钢印主要依赖微弱明暗和边缘变化,建议从侧向光源、固定相机、ROI裁剪、CLAHE、高通和Scharr梯度增强入手,必要时采集现场数据微调模型。
适用边界
这套方案适合依赖受限的CPU OCR,但并不打算覆盖所有场景。
适合:
-
固定工位的标签、包装、铭牌、票据和印刷文字。
-
Windows x64、离线部署和老.NET上位机。
-
中低并发、本地调用或局域网HTTP服务。
需要额外评估:
-
高反光、低对比钢印、严重透视、弯曲文字和手写体。
-
每秒大量并发请求或超高分辨率整图。
-
强制要求x86进程的历史软件。
-
没有完成Win7 SP1补丁和硬件指令集核验的特殊设备。
主动说明边界不是削弱产品,反而能帮助项目在正确的环境中稳定落地。
上线前检查清单
-
目标系统为Windows 7 SP1 x64或更高版本。
-
调用程序明确编译为x64。
-
DLL、模型和字典已经复制完整。
-
识别模型与对应字典配套。
-
使用客户真实图片完成准确率验收。
-
对初始化、首次推理和预热后速度分别计时。
-
连续循环至少1000次,观察内存、句柄和线程数。
-
HTTP部署已确认端口、防火墙和局域网访问范围。
-
Windows服务使用有权限读取模型和写入日志的账户。
-
已保存可回退的旧版本程序和配置。
适合哪些项目
-
Win7/Win10/Win11 工控机上的标签、铭牌、包装和单据识别。
-
仍使用 .NET Framework 3.5/4.x 的 C# 上位机软件。
-
不方便安装 Python、CUDA 或复杂运行环境的离线设备。
-
希望通过 HTTP 快速接入 OCR,又需要保留原生 DLL 二次集成能力的项目。
三种使用入口
如果只想快速体验:解压绿色包,双击 start_console.bat,浏览器打开 http://127.0.0.1:8080/。
如果是C#开发者:把 sdk/csharp/NativeOcr.cs 加入.NET Framework 3.5/4.x的x64项目,并参考本文完整代码。
如果使用其他语言:从 sdk/native/ppocr_api.h 开始,按照C ABI加载DLL,并严格遵守句柄和返回内存的释放规则。
OCR 真正有价值的地方,不是演示时能读出一张图,而是能在受限环境中稳定部署、持续运行,并让旧系统也有清晰的升级路径。
lw.OpenCVDNN.PPOCR做的正是这件事。
下载
通过网盘分享的文件:lw.OpenCVDNN.PPOCR.Win7-x64 链接: https://pan.baidu.com/s/1fyhPDD4eI7y-2fwlAhioqA 提取码: qzfh