Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,13 @@ $RECYCLE.BIN/

# VisualStudioCode
.vs
Test
Test

# Local publish artifacts
dist/

# Local standalone schedule generator
/schedule_generator/

# Keep the reproducible standalone Windows publish profile in source control.
!RapidOCRWinform/Properties/PublishProfiles/StandaloneWinX64.pubxml
Binary file added Assets/mobao-avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets/mobao.ico
Binary file not shown.
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ If you find this project helpful, please consider giving us a ⭐ Star. Your sup

---

## Local schedule-image organizer

`RapidOCRWinform` now includes a complete four-image schedule workflow. Select exactly four structurally identical schedule images, choose the target year/month, run OCR, review the merged table, edit any date or name, and export the six required columns to `.xlsx`. Saturdays and Sundays are ignored by default; the checkbox can retain them, and that preference persists in `config/settings.json` beside the application.

Records are merged by calendar date rather than upload order. Missing workdays, duplicate dates, invalid dates/weekdays, weekday mismatches, empty positions, multiple months, ambiguous columns, and low-confidence OCR are shown explicitly for manual review. The default layout uses columns 1–4 plus the final two columns; the two right-side column indices can be changed without rerunning OCR.

```powershell
dotnet run --project .\RapidOCRWinform\RapidOCRWinform.csproj
dotnet test .\RapidOCRSchedule.Tests\RapidOCRSchedule.Tests.csproj
```

---

## 👉 Quick Start

The following example demonstrates how to get started using a **.NET Console Application**.
Expand Down Expand Up @@ -130,7 +143,61 @@ Console.ReadKey();
| ------------ | --------------- | ------------------------------------------------------------- |
| Core Library | RapidOCRLib | Core C# OCR wrapper library with sync/async APIs |
| Console App | RapidOCRConsole | .NET console demo with built-in PP-OCRv5 model |
| WinForms App | RapidOCRWinform | WinForms demo (model not included; copy from console project) |
| WinForms App | RapidOCRWinform | Schedule-image OCR desktop app with embedded PP-OCRv5 models |

## 📦 Offline Windows x64 Packaging

`RapidOCRWinform` can be published as either a framework-dependent or self-contained application:

- **Framework-dependent (smaller):** the target computer must have the .NET 10 Desktop Runtime installed.
- **Self-contained (no .NET installation):** the package includes the .NET runtime and is larger, but can run offline immediately after extraction.

Framework-dependent publish command:

```powershell
dotnet publish .\RapidOCRWinform\RapidOCRWinform.csproj `
-c Release `
-r win-x64 `
--self-contained false `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:EnableCompressionInSingleFile=true
```

Fully self-contained publish command:

```powershell
dotnet publish .\RapidOCRWinform\RapidOCRWinform.csproj `
-c Release `
-r win-x64 `
--self-contained true `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:EnableCompressionInSingleFile=true
```

The repository also includes a reusable publish profile:

```powershell
dotnet publish .\RapidOCRWinform\RapidOCRWinform.csproj `
-p:PublishProfile=StandaloneWinX64 `
-o .\dist\RapidOCRSchedule-standalone-win-x64
```

The publish directory only needs one distributable file:

```text
RapidOCRSchedule.exe
```

The executable contains the .NET Desktop runtime, native OCR libraries, all three ONNX models, and the dictionary. It does not require Python, Anaconda, CUDA, Office, an installed .NET runtime, or an online service, but it is specific to Windows x64. On first launch it verifies and materializes the embedded models under `%LOCALAPPDATA%\RapidOCRSchedule\model-cache`; the .NET single-file host also extracts native libraries to the user's temporary directory. These are automatically managed runtime caches, not deployment prerequisites.

Run the headless end-to-end check to verify model extraction, ONNX Runtime, OpenCV, and OCR inference:

```powershell
$test = Start-Process .\RapidOCRSchedule.exe -ArgumentList --self-test -Wait -PassThru
if ($test.ExitCode -ne 0) { throw "Standalone package self-test failed" }
```

---

Expand Down
81 changes: 79 additions & 2 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ RapidOCR 是一款完全开源免费、支持离线快速部署的多平台多

.Neter们,如果您觉得本项目对您的工作或学习有所帮助,恳请您不吝赐予一颗 ⭐ Star,给予我们宝贵的支持与鼓励!

## 本地排班图片整理

`RapidOCRWinform` 已提供面向排班表的完整桌面流程:

1. 一次选择正好 4 张结构相同的排班图片(上传顺序不影响结果)。
2. 选择目标年月;程序从每张图片提取日期、星期、前两个岗位以及右侧两个指定岗位。
3. 4 张图片的记录按实际日期合并排序;默认忽略周六和周日,也可在“整理设置”中取消勾选以保留周末。该选择保存在程序目录的 `config/settings.json`,再次启动仍会沿用。未忽略日期中的缺失记录会自动生成待填写行。
4. 在结果表格中检查并修改日期、星期和姓名,然后导出为 `.xlsx`。

程序以界面中选择的目标年月为权威日历。OCR 完成后,会根据四张图片各自从上到下每隔 4 天的排布规律,以及可识别的日期/星期锚点确定四组顺序,再统一用目标年月计算并回填最终日期和星期;OCR 的日期和星期只用于定位、核对,姓名仍取自 OCR。像 `817`、缺少斜杠或星期只识别成“星期”等情况都会被日历结果纠正;如果四组对应关系存在歧义,则不会盲目改写,并保留待人工检查。原图和 OCR 坐标诊断保留在“原图与识别诊断”页。

默认列映射为从左到右第 1~4 列,以及倒数第 2、倒数第 1 列。若右侧目标区域不是表格最后两列,可在左侧把“岗位三列”“岗位四列”改为实际列序号;修改后会直接重新整理缓存的 OCR 结果,无需再次识别图片。

```powershell
dotnet run --project .\RapidOCRWinform\RapidOCRWinform.csproj
```

三个 ONNX 模型和 `ppocrv5_dict.txt` 已编译进 WinForms 程序;首次启动会校验并释放到用户模型缓存并自动初始化,不需要选择模型目录,也不需要在程序旁放置 `models` 目录。核心日期/几何规则与 Excel OOXML 导出测试可用以下命令运行:

```powershell
dotnet test .\RapidOCRSchedule.Tests\RapidOCRSchedule.Tests.csproj
```

### 👉快速入门
> 下面以控制台应用来演示如何快速入门
#### 1.增加一个控制台应用
Expand Down Expand Up @@ -92,7 +115,61 @@ Console.ReadKey();
|-------|-------|--------|
|核心库|RapidOCRLib|C#的RapidOCR核心封装库,提供众多同步、异步的Detection方法|
|console应用|RapidOCRConsole|.net OCR的Console演示,自带PP-OCRv5轻量模型,可以直接运行,引用了上面的核心库RapidOCRLib,学习者以此项目为主|
|winform应用|RapidOCRWinform|.net OCR的winform演示,可以直接运行,但不带模型,学习者可以从RapidOCRConsole目录拷贝模型|
|winform应用|RapidOCRWinform|排班图片 OCR 桌面应用,内嵌 PP-OCRv5 模型,可发布为独立单文件 EXE|

### 📦 Windows x64 离线打包

`RapidOCRWinform` 可以发布为“框架依赖”或“自包含”应用:

- **框架依赖包(体积较小)**:目标电脑必须安装 .NET 10 Desktop Runtime。
- **自包含包(无需安装 .NET)**:包内携带 .NET 运行时,体积更大,但解压后即可离线运行。

框架依赖发布命令:

```powershell
dotnet publish .\RapidOCRWinform\RapidOCRWinform.csproj `
-c Release `
-r win-x64 `
--self-contained false `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:EnableCompressionInSingleFile=true
```

完全自包含发布命令:

```powershell
dotnet publish .\RapidOCRWinform\RapidOCRWinform.csproj `
-c Release `
-r win-x64 `
--self-contained true `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:EnableCompressionInSingleFile=true
```

也可以直接使用仓库内已固化的发布配置:

```powershell
dotnet publish .\RapidOCRWinform\RapidOCRWinform.csproj `
-p:PublishProfile=StandaloneWinX64 `
-o .\dist\RapidOCRSchedule-standalone-win-x64
```

发布目录只需要一个文件:

```text
RapidOCRSchedule.exe
```

该 EXE 已包含 .NET 桌面运行时、OCR 原生库、三个 ONNX 模型和字典,不依赖 Python、Anaconda、CUDA、Office、已安装的 .NET 或联网服务,但限定为 Windows x64。首次启动会把模型校验后释放到 `%LOCALAPPDATA%\RapidOCRSchedule\model-cache`,.NET 单文件宿主也会把原生 DLL 释放到用户临时目录;这些都是自动管理的运行时缓存,不是需要随 EXE 分发的外部环境。

可用无界面自检确认模型、ONNX Runtime 和 OpenCV 均能工作:

```powershell
$test = Start-Process .\RapidOCRSchedule.exe -ArgumentList --self-test -Wait -PassThru
if ($test.ExitCode -ne 0) { throw "独立包自检失败" }
```


### ❓ 参数说明及调参
Expand Down Expand Up @@ -141,4 +218,4 @@ public async Task<OcrResult> DetectAsync(string imgFile, int padding = 50, int m

### 🎉 联系方式

[QQ群](https://rapidai.github.io/RapidOCRDocs/main/communicate/#qq)
[QQ群](https://rapidai.github.io/RapidOCRDocs/main/communicate/#qq)
42 changes: 42 additions & 0 deletions RapidOCRCSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RapidOCRWinform", "RapidOCR
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RapidOCRLib.Tests", "RapidOCRLib.Tests\RapidOCRLib.Tests.csproj", "{F09F59AD-C0B1-4F96-9F86-FE6FA25EE12F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RapidOCRSchedule.Core", "RapidOCRSchedule.Core\RapidOCRSchedule.Core.csproj", "{51786420-E70F-4451-B177-A8AB1B9DAD55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RapidOCRSchedule.Excel", "RapidOCRSchedule.Excel\RapidOCRSchedule.Excel.csproj", "{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RapidOCRSchedule.Tests", "RapidOCRSchedule.Tests\RapidOCRSchedule.Tests.csproj", "{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -69,6 +75,42 @@ Global
{F09F59AD-C0B1-4F96-9F86-FE6FA25EE12F}.Release|x64.Build.0 = Release|Any CPU
{F09F59AD-C0B1-4F96-9F86-FE6FA25EE12F}.Release|x86.ActiveCfg = Release|Any CPU
{F09F59AD-C0B1-4F96-9F86-FE6FA25EE12F}.Release|x86.Build.0 = Release|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Debug|x64.ActiveCfg = Debug|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Debug|x64.Build.0 = Debug|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Debug|x86.ActiveCfg = Debug|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Debug|x86.Build.0 = Debug|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Release|Any CPU.Build.0 = Release|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Release|x64.ActiveCfg = Release|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Release|x64.Build.0 = Release|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Release|x86.ActiveCfg = Release|Any CPU
{51786420-E70F-4451-B177-A8AB1B9DAD55}.Release|x86.Build.0 = Release|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Debug|x64.ActiveCfg = Debug|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Debug|x64.Build.0 = Debug|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Debug|x86.ActiveCfg = Debug|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Debug|x86.Build.0 = Debug|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Release|Any CPU.Build.0 = Release|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Release|x64.ActiveCfg = Release|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Release|x64.Build.0 = Release|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Release|x86.ActiveCfg = Release|Any CPU
{B3BE055E-35B5-4250-B4B8-7F8A9A9DD3A5}.Release|x86.Build.0 = Release|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Debug|x64.ActiveCfg = Debug|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Debug|x64.Build.0 = Debug|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Debug|x86.ActiveCfg = Debug|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Debug|x86.Build.0 = Debug|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Release|Any CPU.Build.0 = Release|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Release|x64.ActiveCfg = Release|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Release|x64.Build.0 = Release|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Release|x86.ActiveCfg = Release|Any CPU
{9D6FC8E1-0767-40AE-95B5-20C9CCD74B7F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
9 changes: 4 additions & 5 deletions RapidOCRConsole/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
};

await ocrEngin.InitModels();
//get the image will be ocr.
var demoWillOCRFile = Path.Combine(AppContext.BaseDirectory, "Assets", "demo.png");
// Use the first command-line argument when supplied; otherwise run the bundled demo.
var demoWillOCRFile = args.Length > 0
? Path.GetFullPath(args[0])
: Path.Combine(AppContext.BaseDirectory, "Assets", "demo.png");
//ocr detect
var result = ocrEngin.Detect(demoWillOCRFile, 50);
if (result != null)
Expand All @@ -41,6 +43,3 @@
Console.WriteLine("显示strResult");
Console.WriteLine(result.StrRes);
}


Console.ReadKey();
29 changes: 24 additions & 5 deletions RapidOCRLib/AngleNet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace RapidOCRLib
{
class AngleNet
class AngleNet : IDisposable
{
private readonly float[] MeanValues = { 127.5F, 127.5F, 127.5F };
private readonly float[] NormValues = { 1.0F / 127.5F, 1.0F / 127.5F, 1.0F / 127.5F };
Expand All @@ -26,7 +26,19 @@ public AngleNet() { }

~AngleNet()
{
angleNet.Dispose();
Dispose(false);
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
{
angleNet?.Dispose();
angleNet = null;
}

public async Task InitModel(string path, int numThread)
Expand All @@ -40,10 +52,12 @@ public async Task InitModel(string path, int numThread)
angleNet = new InferenceSession(path, op);
inputNames = angleNet.InputMetadata.Keys.ToList();

// 从 ONNX 模型 metadata 读取输入尺寸,兼容 v2 (192x48) 和 v5 (160x80)
// Read static dimensions from the model when available. Some PaddleOCR
// classifier exports expose both spatial dimensions as dynamic (-1),
// in which case use the standard PP-OCR classifier input size.
var dims = angleNet.InputMetadata.First().Value.Dimensions;
_dstHeight = dims[2];
_dstWidth = dims[3];
_dstHeight = dims[2] > 0 ? dims[2] : 48;
_dstWidth = dims[3] > 0 ? dims[3] : 192;

await Task.CompletedTask;
}
Expand Down Expand Up @@ -109,6 +123,11 @@ public List<Angle> GetAngles(List<Mat> partImgs, bool doAngle, bool mostAngle)

private Angle GetAngle(Mat src)
{
if (src == null || src.Rows <= 0 || src.Cols <= 0)
{
throw new ArgumentException("Angle classifier received an empty image.", nameof(src));
}

Angle angle = new Angle();
Mat angleImg = new Mat();
CvInvoke.Resize(src, angleImg, new Size(_dstWidth, _dstHeight));
Expand Down
16 changes: 14 additions & 2 deletions RapidOCRLib/CrnnNet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace RapidOCRLib
{
class CrnnNet
class CrnnNet : IDisposable
{
private readonly float[] MeanValues = { 127.5F, 127.5F, 127.5F };
private readonly float[] NormValues = { 1.0F / 127.5F, 1.0F / 127.5F, 1.0F / 127.5F };
Expand All @@ -26,8 +26,20 @@ class CrnnNet
public CrnnNet() { }

~CrnnNet()
{
Dispose(false);
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
{
crnnNet?.Dispose();
crnnNet = null;
}

public async Task InitModel(string path, string keysPath, int numThread)
Expand Down Expand Up @@ -149,4 +161,4 @@ private TextLine ScoreToTextLine(float[] srcData, int h, int w)
}

}
}
}
Loading