让 C# 读懂整张图:DeploySharp 驱动 PaddleOCR v4/v5/v6,打通 OCR 全流程

文章目录

    • [一、DeploySharp 项目简介](#一、DeploySharp 项目简介)
    • [二、2.0 新特性与本篇 OCR 实战](#二、2.0 新特性与本篇 OCR 实战)
      • [2.1 从模型调用,走到完整应用流程](#2.1 从模型调用,走到完整应用流程)
      • [2.2 为什么 OCR 需要一条流水线](#2.2 为什么 OCR 需要一条流水线)
    • [三、v4、v5、v6 应该怎样选择](#三、v4、v5、v6 应该怎样选择)
      • [3.1 先按部署需求选择组合](#3.1 先按部署需求选择组合)
      • [3.2 DET、CLS、REC 和字典要配套](#3.2 DET、CLS、REC 和字典要配套)
    • [四、创建项目与安装 NuGet](#四、创建项目与安装 NuGet)
      • [4.1 同一份 C# 代码,在 Windows 与 Ubuntu 运行](# 代码,在 Windows 与 Ubuntu 运行)
      • [4.2 按操作系统安装 CPU 依赖](#4.2 按操作系统安装 CPU 依赖)
      • [4.3 其他场景怎样选包](#4.3 其他场景怎样选包)
      • [4.4 检查项目配置](#4.4 检查项目配置)
    • 五、按需准备模型和测试图片
      • [5.1 只下载当前组合需要的文件](#5.1 只下载当前组合需要的文件)
      • [5.2 项目目录](#5.2 项目目录)
    • [六、编写完整 OCR 程序](#六、编写完整 OCR 程序)
    • 七、关键实现:从三个模型到一个结果
      • [7.1 目录选择和本地文件加载](#7.1 目录选择和本地文件加载)
      • [7.2 检测前处理与文本行裁剪](#7.2 检测前处理与文本行裁剪)
      • [7.3 字典和 CTC 为什么必须对应](#7.3 字典和 CTC 为什么必须对应)
      • [7.4 方向、坐标、异步和资源释放](#7.4 方向、坐标、异步和资源释放)
    • 八、运行程序,查看实际识别结果
      • [8.1 默认 v5 Mobile 的真实输出](#8.1 默认 v5 Mobile 的真实输出)
      • [8.2 三种输出文件怎样使用](#8.2 三种输出文件怎样使用)
      • [8.3 切换 v4、v6 或自己的图片](#8.3 切换 v4、v6 或自己的图片)
      • [8.4 放到 Ubuntu,同一份代码怎样运行](#8.4 放到 Ubuntu,同一份代码怎样运行)
      • [8.5 Ubuntu 上运行 OpenVINO、OpenCV DNN 与 TensorRT](#8.5 Ubuntu 上运行 OpenVINO、OpenCV DNN 与 TensorRT)
    • 九、Batch、推理通道与时间应该怎样看
      • [9.1 一张图,多条文字行](#9.1 一张图,多条文字行)
      • [9.2 本文记录的时间包含什么](#9.2 本文记录的时间包含什么)
      • [9.3 长文本、低分行与区域 OCR](#9.3 长文本、低分行与区域 OCR)
    • 十、常见问题与处理方法
    • 十一、总结与后续

一、DeploySharp 项目简介

DeploySharp 是一个面向 C#/.NET 开发者 的开源模型部署框架,由「椒颜皮皮虾」开发,项目源码遵循 Apache License 2.0 。它基于 ONNX Runtime、OpenVINO、TensorRT、OpenCV DNN 等推理引擎,提供模型加载、图像前处理、推理执行和结果后处理接口,方便大家把模型接入桌面软件、工业视觉和文档应用。

项目已经接入目标检测、分类、分割、关键点、旋转框、异常检测和背景移除等视觉任务,也提供 PP-OCRv4/v5/v6、PP-Structure 文档处理和部分图文、本地语言模型的使用入口。常用模型有配套前后处理,自有模型可以通过扩展接口接入。

项目地址:

text 复制代码
https://github.com/guojin-yan/DeploySharp/tree/DeploySharpV2.0

**NuGet 包:**在包管理器中搜索 JYPPX.DeploySharp,按任务与后端选择组件。

text 复制代码
https://www.nuget.org/packages?q=JYPPX.DeploySharp

二、2.0 新特性与本篇 OCR 实战

2.1 从模型调用,走到完整应用流程

上一篇用 YOLO26 完成了目标检测。这一篇把多个模型组合起来,在 C# 中完成文字检测、文本行裁剪、方向处理和文字识别。

2.0 版本为 OCR 准备了以下功能:

新版能力 对 OCR 应用有什么用
多后端与按需安装 图像处理与推理引擎分别选择,先用 CPU 跑通,再评估目标设备
v4/v5/v6 核心模型目录 根据模型 ID 取得配套输入输出与解码配置,减少逐版本手工填写
完整 OcrPipeline 组织检测、透视裁剪、方向分类、识别及结果合并,返回原图区域与文字
动态 Batch 与独立 Session 池 多条文本行可以分批识别,也可以交给多个独立推理器调度
可配置的区域处理 按需要接入长文本策略、ROI 和文档区域工作流

下面先用 ONNX Runtime CPU 完成基础程序,再把相同图片和模型放到 Ubuntu 22.04 上,分别运行 ORT、OpenVINO、OpenCV DNN 和 TensorRT。CUDA 专用前后处理和 PP-Structure 页面处理会在对应专题中介绍。

2.2 为什么 OCR 需要一条流水线

一张图片里的文字数量、大小和角度都不固定,而识别模型通常以一条裁好的文本行为输入。因此,需要先找到文字,再裁剪、调整方向,最后识别内容。

这次使用三个模型,各自负责一件事:

阶段 作用 结果怎样传给下一步
DET:文字检测 找到图片中的文字位置 返回原图文字区域,用于透视裁剪
CLS:文本行方向分类 判断裁好的文字行是否需要旋转 180° 按方向处理后,再准备识别输入
REC:文字识别 将文本行转换为字符序列 CTC 解码得到文字和置信度

中间还有裁剪、缩放、归一化、Batch 组织和坐标对应。下图把整条处理过程放在一起,图中的多个文本行来自同一张原图:

本文默认使用 PP-OCRv5 Mobile DET + Mobile CLS + Mobile REC ,识别一张包含大标题、多栏小字、照片、图注和引语 的中文报纸页面,并生成三个文件:result.txt、result.json、result.jpg。随后用同一份代码切换到 v4 Mobile 和 v6 Tiny,讲清模型、字典和方向分类怎样配对。

下面是本次输入,来自 PaddleOCR 官方 PP-Structure 示例,原图大小为 1524 × 1368。

三、v4、v5、v6 应该怎样选择

3.1 先按部署需求选择组合

DeploySharp 为以下七组核心组合提供了模型目录与流水线配置,通过右侧参数即可切换:

版本与组合 主要选择方向 本文命令行参数
PP-OCRv4 Mobile 已有 v4 轻量化项目,继续使用对应模型与字典 v4-mobile
PP-OCRv4 Server 评估 v4 Server 组合,允许更大的模型和计算开销 v4-server
PP-OCRv5 Mobile 本文默认组合,先完成完整部署与结果使用 v5-mobile
PP-OCRv5 Server 在目标图像上评估 v5 Server 组合 v5-server
PP-OCRv6 Tiny 从较小模型开始,评估设备延迟与识别效果 v6-tiny
PP-OCRv6 Small 进一步评估模型规模与效果之间的取舍 v6-small
PP-OCRv6 Medium 允许更大的模型,按实际数据评估效果 v6-medium

本篇运行了 v4 Mobile、v5 Mobile 和 v6 Tiny 三组模型。选型时可以先从轻量组合开始,再用自己的小字、低对比度、特殊字体或长文本图片比较效果。其他组合的后端测试情况可以查看仓库验证矩阵。

3.2 DET、CLS、REC 和字典要配套

三种版本的方向分类和识别字典存在差异:

组合 方向分类器 REC 配套字典
v4 Mobile / Server legacy 0°/180° CLS ppocrv4_keys.txt
v5 Mobile / Server 对应 Mobile / Server 的 PP-LCNet 文本行方向模型 ppocrv5_dict.txt
v6 Tiny / Small / Medium 复用 v5 Mobile CLS 对应规模的 PP-OCRv6_*_rec_dict.txt

v6 组合复用 paddleocr/ppocrv5/mobile-cls。这里使用的 CLS 负责文本行 0°/180° 分类,整页文档的四方向分类则由另一类模型处理。

各版本的流水线阶段相同,具体的模型和字典配对如下:

识别字典负责将输出类别索引映射为字符。下载 REC 时一起准备对应字典,并保留原有顺序,才能得到正确文字。

四、创建项目与安装 NuGet

4.1 同一份 C# 代码,在 Windows 与 Ubuntu 运行

我分别在 Windows 和 Ubuntu 上运行了这套程序,使用相同的模型、字典和报纸页面,环境如下:

项目 Windows 本机 Ubuntu 测试设备
系统 Windows 11 x64,10.0.26200 Ubuntu 22.04.5 LTS x64,内核 6.8.0-138-generic
CPU Ryzen 7 5800H,8 核 16 线程 Core i7-1165G7,4 核 8 线程
GPU 本文 Windows 基线只使用 CPU NVIDIA RTX 2060,6 GB;驱动 580.178.04
.NET SDK 10.0.401 / 运行时 10.0.12 SDK 10.0.301 / 运行时 10.0.9
应用与 DeploySharp net10.0、x64、NuGet 2.0.0 同左
基础图像与推理组件 OpenCV 5.0.0、ORT CPU 1.28.0 OpenCV 5.0.0、ORT CPU 1.28.0
输入 pp_structure_v3_demo.png,1524 × 1368 相同图片、模型和字典

准备 .NET 10 SDK,然后在 PowerShell 或 Ubuntu Bash 中创建项目:

shell 复制代码
dotnet new console -n DeploySharpOcrDemo -f net10.0
cd DeploySharpOcrDemo

后续命令均在这个项目目录执行。使用 Visual Studio 的朋友,也可以创建 .NET 10 控制台应用,再从 NuGet 管理器安装相同依赖。

4.2 按操作系统安装 CPU 依赖

OCR 编排、DB 检测后处理和 CTC 解码由视觉组件提供,Visual.OpenCV 负责图片和文本行处理,ONNX Runtime 后端执行模型。这套 C# ONNX 程序可以直接运行,无需安装 Python 或 PaddlePaddle。

三个托管/推理包在两种系统中相同,先安装:

shell 复制代码
dotnet add package JYPPX.DeploySharp.Visual.OpenCV --version 2.0.0
dotnet add package JYPPX.DeploySharp.Backend.OnnxRuntime --version 2.0.0
dotnet add package Microsoft.ML.OnnxRuntime --version 1.28.0

Windows x64 再安装图像原生库:

powershell 复制代码
dotnet add package JYPPX.OpenCV.runtime.win-x64 --version 5.0.0

Ubuntu 22.04 x64 选择相应的原生库:

bash 复制代码
dotnet add package JYPPX.OpenCV.runtime.ubuntu.22.04-x64 --version 5.0.0
显式安装的包 在 OCR 中负责什么
JYPPX.DeploySharp.Visual.OpenCV 解码原图、检测前处理、文本行透视裁剪、缩放与归一化
JYPPX.DeploySharp.Backend.OnnxRuntime 将 DET、CLS、REC 推理交给 ONNX Runtime
Microsoft.ML.OnnxRuntime 提供 CPU 原生推理运行库
对应操作系统的 JYPPX.OpenCV.runtime.* 提供图像处理、绘制与保存所需的原生库,两种系统择一

Core、Visual 等基础依赖由 NuGet 自动引入。

Ubuntu 下还需让系统找到 OpenCV 的 .so 及其同目录依赖。本例使用默认 NuGet 缓存目录,在完成还原后设置:

bash 复制代码
dotnet restore
opencv_native="$HOME/.nuget/packages/jyppx.opencv.runtime.ubuntu.22.04-x64/5.0.0/runtimes/ubuntu.22.04-x64/native"
export LD_LIBRARY_PATH="$opencv_native:${LD_LIBRARY_PATH:-}"

修改过 NUGET_PACKAGES 时,将 $HOME/.nuget/packages 换成自己的缓存根目录,并在启动程序前设置好搜索路径。若出现 NETSDK1206 警告,检查对应目录中的原生文件及其依赖是否能被加载。

4.3 其他场景怎样选包

使用其他后端时,按下表安装组件,并配置对应的后端注册和设备请求:

部署场景 主要变化
ONNX Runtime CUDA 保留 ORT 适配器,选择匹配的 GPU runtime 与 CUDA/cuDNN,显式启用 CUDA Provider
OpenVINO 选择 Backend.OpenVINO,准备匹配的 OpenVINO runtime
OpenCV DNN 选择 Backend.OpenCV,确认实际导出图的 Batch 与动态宽度支持
TensorRT 执行 DET/CLS/REC 选择 Backend.TensorRT、匹配 bridge 和厂商运行库,在目标 GPU 构建 Engine;第八节有 Ubuntu 实跑
TensorRT CUDA 专用 OCR 进一步使用 Visual.TensorRT 的 CUDA 裁剪与专用处理路径,按任务单独验证
应用内自动下载模型 按需增加 ModelFactory,使用 PaddleOcrReleaseClient

直接加载本地文件时,使用前面的依赖即可;需要自动下载时再增加 ModelFactory。Windows 的后端安装见第二篇,Ubuntu 的运行方式见第八节。不同的 ORT CPU/GPU 原生包应使用独立输出目录。

text 复制代码
按场景选择 NuGet(第二篇第四节):
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/blog/2.0.0/2026-10/2026-10-28-02-yolo26-quickstart.md

TensorRT CUDA OCR:
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/articles/tensorrt-cuda-ocr.md

4.4 检查项目配置

本文使用的 .csproj 完整内容如下。显式设置 x64,避免进程架构和原生运行库不一致:

xml 复制代码
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="JYPPX.DeploySharp.Visual.OpenCV" Version="2.0.0" />
    <PackageReference Include="JYPPX.DeploySharp.Backend.OnnxRuntime" Version="2.0.0" />
    <PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.28.0" />
    <PackageReference Include="JYPPX.OpenCV.runtime.win-x64" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
    <PackageReference Include="JYPPX.OpenCV.runtime.ubuntu.22.04-x64" Version="5.0.0" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
  </ItemGroup>
</Project>

五、按需准备模型和测试图片

5.1 只下载当前组合需要的文件

PaddleOCR 模型放在独立的 models-paddleocr Release 中。每个 DET、CLS、REC 和字典均可单独下载,使用轻量组合时,不需要把全部模型打包下载。

下面分别给出 Bash 和 PowerShell 命令,按当前终端选择一组。默认下载 v5 Mobile 的三个 ONNX 和一个字典。

Ubuntu Bash: 修改 variant 可选择七种组合之一。

bash 复制代码
variant=v5-mobile
prefix="ppocr${variant}"
case "$variant" in
  v4-mobile|v4-server)
    cls=ppocrv4-legacy-cls.onnx; dictionary=ppocrv4_keys.txt ;;
  v5-mobile|v5-server)
    cls="$prefix-cls.onnx"; dictionary=ppocrv5_dict.txt ;;
  v6-tiny|v6-small|v6-medium)
    cls=ppocrv5-mobile-cls.onnx
    dictionary="PP-OCRv6_${variant#v6-}_rec_dict.txt" ;;
  *) printf '不支持的组合:%s\n' "$variant"; exit 1 ;;
esac
mkdir -p models images
model_base='https://github.com/guojin-yan/DeploySharp/releases/download/models-paddleocr'
for file in "$prefix-det.onnx" "$cls" "$prefix-rec.onnx" "$dictionary"; do
  curl -fL --retry 2 "$model_base/$file" -o "models/$file"
done
curl -fL --retry 2 \
  'https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png' \
  -o images/pp_structure_v3_demo.png

Windows PowerShell: 修改 $variant 选择相同组合。

powershell 复制代码
$variant = 'v5-mobile'
$prefix = $variant -replace '^v', 'ppocrv'
switch -Regex ($variant) {
  '^v4-(mobile|server)$' {
    $cls = 'ppocrv4-legacy-cls.onnx'
    $dictionary = 'ppocrv4_keys.txt'
    break
  }
  '^v5-(mobile|server)$' {
    $cls = "$prefix-cls.onnx"
    $dictionary = 'ppocrv5_dict.txt'
    break
  }
  '^v6-(tiny|small|medium)$' {
    $cls = 'ppocrv5-mobile-cls.onnx'
    $size = $variant.Substring(3)
    $dictionary = "PP-OCRv6_${size}_rec_dict.txt"
    break
  }
  default { throw '请选择本文支持的七种组合之一。' }
}
New-Item -ItemType Directory -Force -Path .\models, .\images | Out-Null
$modelBase = 'https://github.com/guojin-yan/DeploySharp/releases/download/models-paddleocr'
foreach ($file in @("$prefix-det.onnx", $cls, "$prefix-rec.onnx", $dictionary)) {
  Invoke-WebRequest -Uri "$modelBase/$file" -OutFile (Join-Path '.\models' $file)
}
Invoke-WebRequest `
  -Uri 'https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png' `
  -OutFile '.\images\pp_structure_v3_demo.png'

如果命令行下载不方便,也可以在 Release 页面选择对应文件下载。已有本地模型和图片,直接按名称放入目录即可。本文选用的默认文件是:

文件 用途
ppocrv5-mobile-det.onnx 文字检测
ppocrv5-mobile-cls.onnx 文本行 0°/180° 方向分类
ppocrv5-mobile-rec.onnx 文本行识别
ppocrv5_dict.txt v5 REC 字符索引表
pp_structure_v3_demo.png 官方中文多栏报纸输入图片
text 复制代码
PaddleOCR 单文件模型:
https://github.com/guojin-yan/DeploySharp/releases/tag/models-paddleocr

默认测试图片:
https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png

5.2 项目目录

默认组合下载完成后,目录如下。其他组合可以放在同一个 models 目录,程序只会加载所选组合:

text 复制代码
DeploySharpOcrDemo/
  DeploySharpOcrDemo.csproj
  Program.cs
  models/
    ppocrv5-mobile-det.onnx
    ppocrv5-mobile-cls.onnx
    ppocrv5-mobile-rec.onnx
    ppocrv5_dict.txt
  images/
    pp_structure_v3_demo.png

六、编写完整 OCR 程序

下面代码可以直接替换 Program.cs。它根据组合选择模型,加载本地文件和配套字典,创建三阶段流水线,最后保存文字、结构化结果和原图文字框。

为方便切换,三个可选命令行参数依次为:图片路径、模型组合、模型目录 。这里先使用 batch=1、channels=1 建立逐行识别基线,后面再说明怎样评估 Batch 和通道;密集页面的检测后处理与流水线区域上限都显式设为 512。

csharp 复制代码
using System.Diagnostics;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using JYPPX.DeploySharp;
using JYPPX.DeploySharp.Backends.OnnxRuntime;
using JYPPX.DeploySharp.Models;
using JYPPX.DeploySharp.Registry;
using JYPPX.DeploySharp.Visual;
using JYPPX.DeploySharp.Visual.Models.PaddleOcr;
using JYPPX.DeploySharp.Visual.OpenCV;
using JYPPX.OpenCvSharp.Core;
using JYPPX.OpenCvSharp.ImgProc;
using ImageCodecs = JYPPX.OpenCvSharp.ImgCodecs.Cv2;
using Drawing = JYPPX.OpenCvSharp.ImgProc.Cv2;

Console.OutputEncoding = Encoding.UTF8;
string imagePath = Path.GetFullPath(args.Length > 0 ? args[0] : "images/pp_structure_v3_demo.png");
string variant = args.Length > 1 ? args[1].ToLowerInvariant() : "v5-mobile";
string modelRoot = Path.GetFullPath(args.Length > 2 ? args[2] : "models");
const int batch = 1;
const int channels = 1;

var ids = variant switch
{
    "v4-mobile" => ("ppocrv4/mobile-det", "ppocrv4/legacy-cls", "ppocrv4/mobile-rec"),
    "v4-server" => ("ppocrv4/server-det", "ppocrv4/legacy-cls", "ppocrv4/server-rec"),
    "v5-mobile" => ("ppocrv5/mobile-det", "ppocrv5/mobile-cls", "ppocrv5/mobile-rec"),
    "v5-server" => ("ppocrv5/server-det", "ppocrv5/server-cls", "ppocrv5/server-rec"),
    "v6-tiny" => ("ppocrv6/tiny-det", "ppocrv5/mobile-cls", "ppocrv6/tiny-rec"),
    "v6-small" => ("ppocrv6/small-det", "ppocrv5/mobile-cls", "ppocrv6/small-rec"),
    "v6-medium" => ("ppocrv6/medium-det", "ppocrv5/mobile-cls", "ppocrv6/medium-rec"),
    _ => throw new ArgumentException("版本应为 v4-mobile/server、v5-mobile/server 或 v6-tiny/small/medium。")
};
PaddleOcrModelDescriptor detRow = Find(ids.Item1);
PaddleOcrModelDescriptor clsRow = Find(ids.Item2);
PaddleOcrModelDescriptor recRow = Find(ids.Item3);
string dictionaryPath = Require(recRow.ReleaseArtifact!.DictionaryAssetName!);
OcrCharacterSet characters = PaddleOcrProfiles.LoadCharacterSet(
    dictionaryPath, "local." + variant, "1", useSpaceCharacter: true);
PaddleOcrProfile det = CreateProfile(detRow, maximumBatch: 1);
PaddleOcrProfile cls = CreateProfile(clsRow, maximumBatch: batch);
PaddleOcrProfile rec = CreateProfile(recRow, maximumBatch: batch, characters);

using var backends = new BackendRegistry();
backends.UseOnnxRuntime();
var profiles = new VisualProfileRegistry();
profiles.Register(det.VisualProfile);
profiles.Register(cls.VisualProfile);
profiles.Register(rec.VisualProfile);
profiles.Freeze();
var request = new BackendRequest(BackendCapabilities.TensorInference,
    OnnxRuntimeBackendProvider.BackendId, "cpu");

using var pipeline = new OcrPipeline(
    backends,
    Select(det, detRow), request,
    Select(cls, clsRow), request, cls.CropProfile!,
    Select(rec, recRow), request, rec.CropProfile!,
    new OcrPipelineOptions(maximumConcurrency: 1, maximumRegions: 512,
        maximumRecognitionBatch: batch, maximumRecognitionPaddingRatio: 1.0,
        autoRotateVerticalText: true),
    detectionSessionOptions: new SessionOptions(1),
    orientationSessionOptions: new SessionOptions(channels),
    recognitionSessionOptions: new SessionOptions(channels),
    orientationRejectionPolicy: OcrOrientationRejectionPolicy.UseZeroDegrees);

// 此 Mat 只用于取得原图尺寸及绘图;OCR 输入在内部保留自己的源图。
if (!File.Exists(imagePath)) throw new FileNotFoundException("找不到图片。", imagePath);
using Mat canvas = ImageCodecs.ImRead(imagePath);
if (canvas.Empty) throw new IOException("无法解码输入图片。");
var sourceSize = new VisualSize(canvas.Cols, canvas.Rows);
var prepare = Stopwatch.StartNew();
using OpenCvOcrImageInput input = new OpenCvOcrImageInputFactory().CreateFromFile(
    imagePath, det.VisualProfile.Input.Name,
    OpenCvStage19Preprocessing.CreatePaddleOcrOfficialInferenceDetectionOptions(sourceSize, resizeLong: 1600));
prepare.Stop();
using var cancellation = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cancellation.Cancel(); };
var run = Stopwatch.StartNew();
OcrResult result = await pipeline.RunAsync(input,
    new OcrExecutionOptions(timeout: TimeSpan.FromSeconds(60)), cancellation.Token);
run.Stop();

Console.WriteLine($"backend=onnxruntime;device=cpu;variant={variant};batch={batch};channels={channels}");
Console.WriteLine($"regions={result.Regions.Count}");
Console.WriteLine(FormattableString.Invariant(
    $"prepare_ms={prepare.Elapsed.TotalMilliseconds:F1};run_ms={run.Elapsed.TotalMilliseconds:F1}"));
for (int i = 0; i < result.Regions.Count; i++)
{
    OcrRegionResult item = result.Regions[i];
    Console.WriteLine(FormattableString.Invariant(
        $"[{i + 1:D2}] score={item.Recognition.Confidence:F3};angle={OrientationDegrees(item.Region.Orientation)};{item.Recognition.Text}"));
    var points = item.Region.Polygon.Vertices;
    for (int j = 0; j < points.Count; j++)
    {
        var a = points[j];
        var b = points[(j + 1) % points.Count];
        Drawing.Line(canvas, new Point((int)a.X, (int)a.Y), new Point((int)b.X, (int)b.Y),
            new Scalar(80, 210, 30), 2);
    }
    var first = points[0];
    Drawing.PutText(canvas, (i + 1).ToString("D2"),
        new Point(Math.Clamp((int)first.X, 0, Math.Max(0, canvas.Cols - 35)),
            Math.Clamp((int)first.Y - 4, 18, Math.Max(18, canvas.Rows - 1))),
        HersheyFonts.HersheySimplex, 0.5, new Scalar(10, 10, 230), 1);
}
File.WriteAllLines("result.txt", result.Regions.Select(x => x.Recognition.Text), new UTF8Encoding(false));
var report = new
{
    variant, backend = "onnxruntime", device = "cpu", batch, channels,
    sourceSize = new { width = result.SourceSize.Width, height = result.SourceSize.Height },
    timing = new { prepareMs = prepare.Elapsed.TotalMilliseconds, runMs = run.Elapsed.TotalMilliseconds },
    regions = result.Regions.Select((x, i) => new
    {
        index = i + 1, text = x.Recognition.Text, confidence = x.Recognition.Confidence,
        detectionScore = x.Region.Score, angle = OrientationDegrees(x.Region.Orientation),
        polygon = x.Region.Polygon.Vertices.Select(p => new { x = p.X, y = p.Y })
    })
};
File.WriteAllText("result.json", JsonSerializer.Serialize(report, new JsonSerializerOptions
{
    WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}), new UTF8Encoding(false));
if (!ImageCodecs.ImWrite("result.jpg", canvas)) throw new IOException("无法保存结果图。");
Console.WriteLine("saved=result.txt,result.json,result.jpg");

static int OrientationDegrees(TextOrientation orientation) => orientation switch
{
    TextOrientation.Degrees0 => 0,
    TextOrientation.Clockwise90 => 90,
    TextOrientation.Degrees180 => 180,
    TextOrientation.CounterClockwise90 => 270,
    _ => throw new ArgumentOutOfRangeException(nameof(orientation))
};

PaddleOcrModelDescriptor Find(string id) => PaddleOcrModelCatalog.Find(new ModelId("paddleocr/" + id));

string Require(string file)
{
    string path = Path.Combine(modelRoot, file);
    return File.Exists(path) ? path : throw new FileNotFoundException("缺少模型或字典。", path);
}

PaddleOcrProfile CreateProfile(PaddleOcrModelDescriptor row, int maximumBatch, OcrCharacterSet? dictionary = null)
{
    // 本地加载:使用目录中的模型规格,不要求填写模型来源元数据。
    var contract = new PaddleOcrArtifactContract(row.ReleaseArtifact!.Opset,
        "", "", "", "Apache-2.0", "local-input-v1", "local-output-v1");
    if (row.Family == "det")
        return PaddleOcrProfiles.CreateDetection(row.ModelId, contract, row.InputName, row.OutputName,
            postprocess: new PaddleDbPostprocessOptions(maximumRegions: 512), maximumBatch: maximumBatch);
    return PaddleOcrModelCatalog.CreateProfile(row, contract, dictionary, maximumBatch);
}

VisualProfileSelection Select(PaddleOcrProfile profile, PaddleOcrModelDescriptor row) =>
    profiles.Select(profile.CreateArtifact(Require(row.ReleaseArtifact!.AssetName)),
        backends, request, profile.VisualProfile.Task);

七、关键实现:从三个模型到一个结果

7.1 目录选择和本地文件加载

代码中的 PaddleOcrModelCatalog 用于查找模型规格,例如 v4 Mobile DET 的输出名与 v5 Mobile DET 并不相同。通过目录选择 Profile,便可以复用对应的检测解码、方向类别和识别配置。

ReleaseArtifact 提供文件名和模型规格,Require 检查本地文件,CreateArtifact 将路径交给后端。模型使用前面下载到 models 目录中的文件。

CreateProfile 为 DET 声明单图输入,CLS 和 REC 的 Batch 上限由顶部的 batch 控制,默认逐行运行。调整 Batch 时,需要所用模型支持对应的批次大小。

测试这张密集报纸时,我把两个区域上限都设为了 512:一个是 DB 后处理的 PaddleDbPostprocessOptions.maximumRegions,另一个是 OcrPipelineOptions.maximumRegions。DB 后处理默认最多返回 128 个区域,单独调大流水线的上限,前面仍会截断。本图调整后返回了 144 个区域。

7.2 检测前处理与文本行裁剪

检测输入使用 CreatePaddleOcrOfficialInferenceDetectionOptions。为了保留页面中的小字,这里传入 resizeLong: 1600,1524 × 1368 原图经过尺寸对齐后为 1536 × 1408,使用 BGR 和配套均值、标准差归一化。检测尺寸会影响小字效果和计算开销,换图时可以结合文字大小与设备能力调整。

DET 的 DB 后处理将概率图转换为文字区域,默认采用概率阈值 0.3、框阈值 0.6 和扩张比例 1.5。随后,OcrPipeline 从 OCR 输入保留的同一张原图中透视裁剪文本行,再按 CLS 和 REC 各自的配置准备输入。

REC 默认按高度 48 保持比例准备动态宽度,组批时为同一批文本行补齐宽度。方向分类则有不同配置:legacy CLS 使用 BGR、48×192;本篇 v5 CLS 使用 RGB、80×160。它们由对应 Profile 处理,不需要在主程序中手工旋转每一行。

程序另外读取一份 canvas,用于获取尺寸和绘图。OCR 输入持有自己的源图,所有文本行裁剪都复用这张图。

7.3 字典和 CTC 为什么必须对应

REC 输出的每个时间步包含各字符类别的分数。CTC(Connectionist Temporal Classification)解码先选择类别,再按规则合并相邻重复项、移除 blank,最后按字典映射成文字。

下面用一段示意序列说明 CTC 的解码过程:

LoadCharacterSet 按字典顺序读取字符。本例使用 useSpaceCharacter: true,由加载器追加模型需要的空格类别;blank 由 CTC 配置管理。使用自有字典时,按模型约定配置空格和 blank,保留它们各自的索引。

如果 REC 类别数不匹配或输出乱码,先核对模型与字典是否配套,再检查空格配置、blank 索引和输出是否已经过 Softmax。

7.4 方向、坐标、异步和资源释放

本例启用 CLS 和 autoRotateVerticalText。高宽比至少为 1.5 的细长文字框先逆时针旋转 90°,其余区域执行文本行 0°/180° 分类。前者用于处理侧着印刷的长条文字;中文竖排的阅读分析需要另行处理。

方向分类的默认拒识阈值为 0.9。这里选择 UseZeroDegrees,低置信度时按 0° 继续识别;如果业务要求方向不确定就报错,可以改用 Fail。

item.Region.Polygon 返回原图像素坐标,可以直接绘制。Region.Orientation 记录文本行在识别前采用的离散旋转方向。

程序通过 RunAsync 等待完整结果,调用超时设为一分钟,按 Ctrl+C 可以取消。等待调用结束后,再释放输入资源。

输入、流水线、后端注册表和 Mat 都通过 using 释放。OpenCvOcrImageInput 持有原图和裁剪工作区,返回的文字与坐标则是独立的托管结果。批量处理图片时,可以复用 Profile、注册表和流水线,为每张图分别创建、释放输入。

八、运行程序,查看实际识别结果

8.1 默认 v5 Mobile 的真实输出

在项目目录执行:

shell 复制代码
dotnet run -c Release

Windows ORT CPU 返回了 144 个文字区域 。下面节选部分输出,完整文字保存在 result.txt,全部区域、分数和坐标保存在 result.json。Ubuntu 的运行结果与后端对照见第 8.4、8.5 节。

text 复制代码
backend=onnxruntime;device=cpu;variant=v5-mobile;batch=1;channels=1
regions=144
prepare_ms=107.2;run_ms=5879.9
[01] score=0.997;angle=0;助力双方交往
[02] score=0.999;angle=0;搭建友谊桥梁
[03] score=0.955;angle=0;本报记者沈小晓任彦黄培昭
[04] score=0.999;angle=0;身着中国传统民族服装的厄立特里亚青
[05] score=0.998;angle=0;厄立特里亚高等教育与研究院合作建立,开
[06] score=0.996;angle=0;年依次登台表演中国民族舞、现代舞、扇子舞
[07] score=0.997;angle=0;设了中国语言课程和中国文化课程,注册学
[08] score=0.997;angle=0;等,曼妙的舞姿赢得现场观众阵阵掌声。这
...(第 09---23 项省略)...
[24] score=0.234;angle=0;90-
...(第 25---44 项省略)...
[45] score=0.995;angle=0;在厄立特里亚不久前举办的第六届中国风筝文化节上,当地小学生体验风筝制作。
...(第 46---144 项省略)...
saved=result.txt,result.json,result.jpg

控制台时间来自一次未预热运行,prepare_ms 和 run_ms 的计时范围见第 9.2 节;预热后的多后端对照放在第 8.5 节。

下图是同次运行生成的 result.jpg。绿色线标出区域,红色编号与控制台编号、JSON 中的 index 对应,结果保留原图 1524 × 1368 尺寸:

可以看到,大标题、密集正文和长图注都进入了识别结果。照片内的第 24 区域输出了 90-,分数只有 0.234,属于需要复核的低置信度候选;引号、标点和个别字也有进一步校正的空间。

这页采用多栏排版,当前 OCR 按几何位置排序,相近高度的左右栏文字会穿插。要按栏目组织文字、区分标题和图注,可以先做版面分析,再在各区域内运行 OCR,第六篇会介绍这种处理方式。

8.2 三种输出文件怎样使用

文件 保存内容 使用方式
result.txt 按结果顺序保存的逐区域文字,UTF-8 查看文本、复制到后续业务
result.json 文字、识别分数、检测分数、方向、原图 polygon 和计时 与区域编号关联,接入业务或界面
result.jpg 原图上的文字框和编号 检查定位、漏检、误检及坐标

查看文字时用 TXT,接入业务时读取 JSON,检查定位时打开图片。三种输出与 OcrResult 字段的关系如下:

JSON 中的 confidence 是识别置信度,detectionScore 是文字区域的检测分数。angle 将方向枚举转换为顺时针 0/90/180/270,记录识别前的离散旋转处理;polygon 保存原图坐标。

绘图使用 OpenCV 内置 Hershey 字体,只写数字编号;该字体不适合直接绘制中文。需要在界面显示中文时,可以用 WPF/WinForms、SkiaSharp 或其他支持中文字体的绘制组件,按相同编号关联文字。

8.3 切换 v4、v6 或自己的图片

先把第五节下载命令中的 variant(PowerShell 为 $variant)改为所需组合,下载对应文件,再通过第二个程序参数选择相同组合。下面的正斜杠路径可用于 Bash 和 PowerShell:

shell 复制代码
dotnet run -c Release -- ./images/pp_structure_v3_demo.png v4-mobile ./models
dotnet run -c Release -- ./images/pp_structure_v3_demo.png v6-tiny ./models

同一份代码在三个组合上的结果如下,均使用 ORT CPU,执行 DET、裁剪、CLS、REC 和结果合并:

模型组合 返回区域数 第一行识别文字 运行结果
v4 Mobile + legacy CLS 143 助力双方交往 完整运行并保存三个结果文件
v5 Mobile + Mobile CLS 144 助力双方交往 完整运行并保存三个结果文件
v6 Tiny + v5 Mobile CLS 145 助力双方交往 完整运行并保存三个结果文件

三个组合都识别出了页面标题,检测区域数略有不同。可以打开各自的 JSON 和结果图,继续比较小字、标点和低分区域的识别效果。

换自己的图片和模型目录时,传入实际路径:

shell 复制代码
dotnet run -c Release -- ./images/my-document.jpg v5-mobile ./my-models

程序按标准文件名加载模型。使用自己训练或重新导出的模型时,同步配置张量名称、输入高度、颜色、归一化、动态宽度、Batch 和字典。符合 PP-OCR 输出结构的模型可以使用 PaddleOcrProfiles 工厂,传入对应输入输出名;其他结构则编写自己的 Profile 或 Decoder。

三个结果文件会写到当前工作目录,再次运行会覆盖前一次。需要保留多个版本或多张图的结果时,应按图片或组合名称设置各自的输出目录。

8.4 放到 Ubuntu,同一份代码怎样运行

在 Ubuntu 上安装 .NET 10,按第 4.2 节准备 Linux OpenCV runtime,再下载模型和图片。使用第六节的 C# 主程序,在工程目录执行:

bash 复制代码
dotnet run -c Release -- ./images/pp_structure_v3_demo.png v5-mobile ./models

Ubuntu ORT CPU 同样返回 144 个区域 ,逐区域文字与 Windows 结果一致,并写出 result.txt/result.json/result.jpg。

程序直接保存文件,可以在没有图形桌面的服务器或边缘设备上运行,再把结果取回开发机查看。

8.5 Ubuntu 上运行 OpenVINO、OpenCV DNN 与 TensorRT

我把多后端版本整理成了一个独立工程,为 DET、CLS、REC 分别配置后端注册表。切换引擎时,保持字典、原图、检测尺寸、DB 阈值、裁剪和 CTC 解码相同。

text 复制代码
Ubuntu PaddleOCR 后端对照工程:
https://github.com/guojin-yan/DeploySharp/tree/DeploySharpV2.0/docs/blog/2.0.0/2026-10/examples/03-paddleocr-backends

将该目录中的 Program.cs 与 PaddleOcrBackends.csproj 下载到一个与基础案例并列的新目录。工程里已引用各后端包;下面列出这次使用的引擎与原生环境,方便核对:

后端 本次环境与准备
ORT CPU Microsoft.ML.OnnxRuntime 1.28.0
OpenVINO CPU OpenVINO.runtime.ubuntu.22-x86_64 2026.3.1,选择 Latency 提示
OpenCV DNN CPU 与图像组件共用 JYPPX.OpenCV.runtime.ubuntu.22.04-x64 5.0.0
TensorRT CUDA TensorRT 11.0.0.114、CUDA 12.9、cuDNN 9.22.0、RTX 2060
TensorRT bridge JYPPX.TensorRT.CSharp.API.Runtime.linux-x64.ubuntu22.04.trt11.0.cuda12.9.cudnn9.22.Bridge 4.0.0

OpenVINO 原生库还依赖同目录的版本化 .so。先还原包,再为当前进程设置 OpenCV、OpenVINO 和 CUDA 搜索路径,在后端对照工程目录执行:

bash 复制代码
dotnet restore
opencv_native="$HOME/.nuget/packages/jyppx.opencv.runtime.ubuntu.22.04-x64/5.0.0/runtimes/ubuntu.22.04-x64/native"
openvino_native="$HOME/.nuget/packages/openvino.runtime.ubuntu.22-x86_64/2026.3.1/runtimes/linux-x64/native"
cuda_native=/usr/local/cuda-12.9/lib64
export LD_LIBRARY_PATH="$opencv_native:$openvino_native:$cuda_native:${LD_LIBRARY_PATH:-}"

image=../DeploySharpOcrDemo/images/pp_structure_v3_demo.png
models=../DeploySharpOcrDemo/models
dotnet run -c Release -- "$image" v5-mobile "$models" openvino
dotnet run -c Release -- "$image" v5-mobile "$models" opencv-dnn
dotnet run -c Release -- "$image" v5-mobile "$models" tensorrt

这里假定基础案例叫 DeploySharpOcrDemo;按自己的实际位置修改 image/models,CUDA 安装目录不同也要相应修改。需要重新运行 ORT CPU 时,最后一个参数使用 ort-cpu。结果按后端与组合写入 results/<backend>/<variant>/,控制台只打印前 10 个区域,完整文字和区域继续写入 TXT/JSON。

TensorRT 首次运行会通过库的 TensorRtOnnxEngineBuilder 在 Ubuntu 目标 GPU 上构建三份 Engine ,以后复用模型旁的 .ubuntu-trt11.engine。本次 Profile 如下:

阶段 Engine 接受的输入
DET 固定 1 × 3 × 1408 × 1536,对应本文页面的前处理
CLS 固定 1 × 3 × 80 × 160
REC 1 × 3 × 48 × W,W 的 min/opt/max 为 48/320/3200

代码选择 TensorRtApiVersion.TensorRt11,Profile 和工件格式都设为 tensorrt-engine。TensorRT 11 使用强类型网络,这里保留 Float32 图并关闭 TF32。Engine 在 Ubuntu 目标 GPU 上构建;更换模型、输入尺寸、GPU 或 TensorRT 版本后,重新生成匹配的文件。

下面是同一 Ubuntu 设备上的完整流程结果。四条路径都执行 DET → 透视裁剪 → CLS → REC → 合并 ,统一 batch=1 / channels=1 / paddingRatio=1.0 / maximumRegions=512 / resizeLong=1600:

后端 区域数 相对 ORT CPU 的逐区域文字 完整调用 P50 / ms P95 / ms
ORT CPU 1.28.0 144 基线 4893.204 5103.264
OpenVINO CPU 2026.3.1 144 144/144 一致 3882.420 3924.977
OpenCV DNN CPU 5.0.0 144 144/144 一致 14151.585 14544.885
TensorRT 11.0.0.114 CUDA 144 144/144 一致 2160.845 2198.410

将四种后端的完整调用时间画成图,可以更直观地看出差异:

本次预热 2 次、统计 5 次,按 nearest-rank 计算 P50/P95 。计时从读图、检测前处理开始,到完整 RunAsync 返回结束,不包含 Session/Engine 构建、绘图和文件写出。样本数为 5,因此 P95 取本次最大值;评估长期运行性能时,需要增加采样次数。

这页包含 144 个候选区域,检测输入较大,而且采用逐行识别。TensorRT 执行 DET、CLS、REC,裁剪和结果解码仍在 CPU。若继续优化,可以从文本行 Batch、独立通道和 CUDA 专用裁剪、CTC 路径入手,再测完整调用时间。

OpenCV DNN 在这页上耗时较高。工程保留动态识别宽度,Shape 缓存容量为 16,宽度种类多时可能反复专门化和导入网络。固定宽度或宽度分桶可以作为调优方向,测试时同时检查补边对识别文字的影响。

四种后端的 144 条区域文字与 ORT CPU 一致,照片内的低分候选、标点和多栏排序问题也保留在结果中。后端对照之后,还需要用标注数据评估识别准确率。

九、Batch、推理通道与时间应该怎样看

9.1 一张图,多条文字行

本例 DET 对整张图执行一次。检测完成后,CLS 和 REC 面对的是多条文本行:Batch 控制一次送入多少行,Session 数量控制同时有多少个独立推理器处理批任务。

文本行组批、Session 调度和图片级并发的关系如下:

配置 本文值 控制什么
maximumRecognitionBatch 1 一个识别批次最多包含多少条文本行
CLS/REC Profile 的 maximumBatch 1 声明对应模型允许的最大 Batch,要与实际导出能力匹配
orientationSessionOptions / recognitionSessionOptions 各 1 各阶段独立 Session 的数量
maximumConcurrency 1 同时进入这条流水线的完整 OCR 调用上限
maximumRecognitionPaddingRatio 1.0 宽度组批时允许的补边计算比例

想评估两个独立 CLS/REC 通道,可以将代码顶部的 channels 改为 2。这会创建独立推理会话,增加内存或显存开销;CPU 后端本身也可能使用多线程,因此增加通道不一定更快。

评估批处理时,可以将顶部 batch 从 1 改为 8,Profile 和流水线会使用同一个上限。实际批次还会考虑文本行宽度和补边比例,较短的文字与超长行通常需要分组。本例的补边比例为 1.0,放宽到 1.5 后,可以容纳更多宽度不同的文本行。

我在另一张商品图上测试时,v5 Mobile 使用 batch=8 / paddingRatio=1.5,价格行出现了漏字,改回逐行识别后恢复。因此这里先用逐行配置建立基线。后续调整 Batch 和补边时,把每行文字与基线一起比较,检查补边和归一化是否符合模型要求。

9.2 本文记录的时间包含什么

基础程序分别记录输入准备和流水线执行时间:

字段 包含内容 未包含内容
prepare_ms 创建 OCR 输入时的图片解码、检测前处理和输入准备 模型加载、绘图画布的单独解码
run_ms 等待完整 RunAsync:DET、文本行准备、CLS、REC、解码与结果合并 模型/Session 构建、前面的输入创建、绘图、TXT/JSON/JPG 写出

基础程序打印一次运行的 prepare_ms 和 run_ms;第 8.5 节的对照工程则预热后重复测量完整调用。进一步分析时,可以读取 result.Timing 中的 Detection、OrientationClassification 和 Recognition 阶段信息。

Timing.Details 的部分字段累计了多个批次的准备或推理工作时间。并发阶段会重叠,端到端耗时应单独计时,再计算完整调用的 P50/P95。

比较性能时,固定图片、模型、输入规格、后端、Batch 和通道,先预热,再重复采样,同时比较文字结果。仓库的设备实测文档还记录了其他设备上的 OCR 完整流水线与对应配置:

text 复制代码
Batch、Session 池和并发:
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/articles/batch-session-concurrency.md

设备性能实测:
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/articles/device-performance-benchmarks.md

性能测试方法:
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/articles/performance-benchmarking.md

9.3 长文本、低分行与区域 OCR

遇到超长文本,可以选择动态宽度、宽度分桶、溢出拒绝或重叠长行窗口。低分行可以尝试方向重试、宽度重试和裁剪增强,逐项比较准确率与耗时,选择适合自己数据的组合。

已有文字行框或表格单元格区域时,可以用仅识别流水线直接执行 REC。版面区域通常还需要区域内的文字检测,表格 HTML 则需要结构识别,这些会在 PP-Structure 案例中继续介绍。

十、常见问题与处理方法

现象 优先检查 处理方向
找不到模型、字典或图片 工作目录、所选 variant、第三个参数的模型根目录 按第五节下载所选组合,保留对应标准文件名
原生 DLL 加载失败 x64、OpenCV runtime、ORT CPU 原生包和本机依赖 使用本文同版本组合;缺少 MSVC 运行库时按异常提示补齐
Ubuntu 提示 .so 或版本化依赖找不到 对应 Linux runtime 是否已还原、native 目录是否在进程搜索路径 按第 4.2/8.5 节设置 LD_LIBRARY_PATH,结合 ldd 补齐依赖
输出张量名称不匹配 模型是否来自本文这组 Release,是否替换了导出图 对照实际张量名配置 Profile
REC 类别数不一致或文字乱码 模型和字典是否配套、空格与 blank 的处理 保持字典顺序,确认空格类是否由加载器追加
图片里有字,但没有框 检测输入尺寸、颜色、归一化及 DB 阈值 先用示例模型和图片复现,再针对小字与低对比度逐项调整
文字被截断或长行错误较多 文本框边界、裁剪比例、识别最大宽度与溢出策略 对照原图检查;按需要评估长行窗口或宽度策略
中文在结果图上显示问号 是否使用 OpenCV 内置 Hershey 字体画中文 保留编号,中文在界面或支持中文字库的组件中绘制
加大 Batch 或 Session 后更慢 宽度补边、CPU 线程争抢、模型固定 Batch、内存占用 固定输入比较完整流水线,逐个调整配置
换成 TensorRT/OpenCV 后不能原样运行 Engine、后端注册、Batch/Shape 与适配路径 按具体后端教程准备,先核对精确模型验证状态

排查异常时,先根据完整错误定位到模型加载、裁剪还是解码阶段,再检查对应配置。

text 复制代码
OCR 基础教程:
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/articles/visual-ocr.md

PaddleOCR 三模型流水线:
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/articles/visual-paddle-ocr3.md

模型与后端验证矩阵:
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/model-backend-verification-matrix.md

十一、总结与后续

本篇从创建项目、下载模型开始,完成了 DET → 文本行裁剪 → CLS → REC → 文字与原图区域 的流程。同一份基础代码在 Windows 和 Ubuntu 上运行,Ubuntu 还对照了四种推理后端。

大家可以先用 v5 Mobile 运行示例,再换成自己的图片,比较 v4、v6 或 Server 组合。得到文字和原图坐标后,就可以继续接入界面、业务规则和文档处理。

接下来会展开多后端与离线部署,再进入 PP-Structure 的版面区域、表格结构与 HTML 输出,以及更完整的页面任务组合。

text 复制代码
本文主稿与配图:
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/blog/2.0.0/2026-10/2026-10-28-03-paddleocr-pipeline.md

系列目录:
https://github.com/guojin-yan/DeploySharp/blob/DeploySharpV2.0/docs/blog/2.0.0/README.md

问题反馈:
https://github.com/guojin-yan/DeploySharp/issues

DeploySharp 库代码采用 Apache License 2.0;PaddleOCR 模型、字典和第三方运行库遵循各自许可。反馈问题时请附上组合、后端、环境、输入与最小复现代码,方便定位。

相关推荐
roman_日积跬步-终至千里1 小时前
【数据建模入门】从一家小店看懂数仓与机器学习:先理业务,再串技术
数据仓库·人工智能·机器学习·数据分析·数据建模
昂好的day1 小时前
C# 同步异步与多线程:Thread / Task / 线程池一次讲透(上篇)
开发语言·windows·c#
Java后端的Ai之路1 小时前
01、Java并发编程:学习攻略如何才能学好并发编程?
java·开发语言·学习
BJ_Bonree1 小时前
聊点技术 | Bonree ONE 业务链路观测实战 支付成功率下降,问题究竟出在哪一环?
大数据·人工智能
Bode_20021 小时前
TRIZ 理论知识体系与架构全景图
人工智能
richard_first1 小时前
第21章 训练一个简单的 GPT
人工智能·gpt·深度学习
Highcharts.js1 小时前
Highcharts 集成到 React 或 Vue 项目中时,如何高效地使用 TypeScript技巧?
vue.js·人工智能·react.js·typescript·前端框架·highcharts
小lo想吃棒棒糖1 小时前
把 启元机器人Q1 的训练搬到昇腾 910B:从 MuJoCo 到 PPO 的一次完整迁移
人工智能·python·机器人·昇腾·具身智能
收放扳机1 小时前
PCB自动化整机“末端执行器”与“机械手负载”的匹配选型
人工智能·科技·自动化·制造·pcb工艺