如何用 C# 在 Windows 上安装 Tesseract OCR
本指南将引导.NET开发人员使用IronOCR(一款即插即用的本地 OCR 引擎)替换 Google Cloud Vision。指南涵盖了凭据移除、Protobuf 注释解析替换、批量注释简化以及多页文档处理——这四项结构性变更构成了迁移工作的大部分。
为什么要从Google Cloud Vision OCR迁移?
迁移的决定几乎总是始于以下两种情况之一:合规性审计阻止了云文档传输,或者管理 GCP 凭证、GCS 存储桶、异步轮询和按图像计费的操作成本比 OCR 本身还要高。
服务帐户 JSON 密钥生命周期。应用程序的每次部署——开发机器、CI/CD 流水线、预发布服务器、生产服务器、Docker 容器、Kubernetes Pod——都需要同一个服务帐户 JSON 密钥文件。该文件包含一个 RSA 私钥。 它绝不能进入源代码控制,必须按计划轮换,必须使用文件系统权限进行保护,并且在轮换发生时必须在所有环境中同时更新。 一个泄露的密钥可以授予 API 访问权限,直到在 GCP 控制台中手动撤销该密钥为止。 IronOCR用一个在应用程序启动时设置一次的单个许可证密钥字符串取代了整个操作界面。
大规模按请求计费。每1000张图片收费1.50美元,每页PDF收费0.0015美元,开发阶段成本几乎看不见,但生产阶段成本却很高。 每月处理 20 万页文档的文档处理管道,仅 API 费用每月就需 300 美元,还不包括 GCS 存储费用和出口费用。 这300美元每个月都会重复出现,永无止境。IronOCR的永久许可将 OCR 从一项计量运营支出转变为一项固定资本项目,在第二年或第三年无需任何运营成本。
GCS 异步管道支持 PDF 文件。Google Cloud Vision 不接受 PDF 文件作为直接 API 输入。 完整的管道需要一个第二个NuGet包(AsyncBatchAnnotateFilesAsync调用、一个轮询循环、从GCS中解析JSON输出以及一个清理步骤。在提取任何文本之前,该管道跨越50多行代码。 IronOCR可以同步地分三行读取 PDF 文件,无需任何外部依赖。
Protobuf符号连接。 DOCUMENT_TEXT_DETECTION响应在Protobuf的页面、块、段落、词语和符号层次结构中以符号级别存储文本。 读取段落文本需要迭代五个嵌套循环并调用.SelectMany(w => w.Symbols).Select(s => s.Text)。 IronOCR以paragraph.Text返回段落文本——一种类型化的字符串属性。
每分钟1,800个请求的默认配额。 超过默认配额的批量工作负载会收到StatusCode.ResourceExhausted响应,使管道在每次超出时暂停60秒。 增加配额需要通过 GCP 控制台提出请求并获得 Google 的批准。 IronOCR以可用 CPU 核心的速度在本地进行处理——无需管理配额,无需寻求批准,也无需重试逻辑进行速率限制。
不支持离线或物理隔离环境。Google Cloud Vision 需要连接到 Google 的终端节点。 物理隔离网络、机密数据中心和工业控制系统在任何架构复杂程度上都无法使用它。 IronOCR在初始许可证验证后,无需任何出站网络连接即可运行。
基本问题
Google Cloud Vision 需要磁盘上存在一个 JSON 密钥文件,然后才能运行第一行 OCR 代码:
// Google Cloud Vision: JSON key file deployed to every server before this line works
// GOOGLE_APPLICATION_CREDENTIALS="/etc/secrets/service-account.json" must be set
// Key contains RSA private key — rotate manually, revoke if compromised
_client = ImageAnnotatorClient.Create();
var image = Image.FromFile("document.jpg");
var response = _client.DetectText(image); // document leaves your infrastructure
string text = response[0].Description;
// Google Cloud Vision: JSON key file deployed to every server before this line works
// GOOGLE_APPLICATION_CREDENTIALS="/etc/secrets/service-account.json" must be set
// Key contains RSA private key — rotate manually, revoke if compromised
_client = ImageAnnotatorClient.Create();
var image = Image.FromFile("document.jpg");
var response = _client.DetectText(image); // document leaves your infrastructure
string text = response[0].Description;
' Google Cloud Vision: JSON key file deployed to every server before this line works
' GOOGLE_APPLICATION_CREDENTIALS="/etc/secrets/service-account.json" must be set
' Key contains RSA private key — rotate manually, revoke if compromised
_client = ImageAnnotatorClient.Create()
Dim image = Image.FromFile("document.jpg")
Dim response = _client.DetectText(image) ' document leaves your infrastructure
Dim text As String = response(0).Description
IronOCR从一个字符串开始,完全在本地计算机上运行:
// IronOCR: one string at startup, no key files, no environment variables
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
string text = new IronTesseract().Read("document.jpg").Text; // local, no cloud
// IronOCR: one string at startup, no key files, no environment variables
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
string text = new IronTesseract().Read("document.jpg").Text; // local, no cloud
Imports IronOcr
' IronOCR: one string at startup, no key files, no environment variables
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim text As String = New IronTesseract().Read("document.jpg").Text ' local, no cloud
IronOCR与Google Cloud VisionOCR:功能对比
下表直接列出了团队在构建迁移业务案例时需要用到的功能。
| 特征 | Google Cloud Vision OCR | IronOCR |
|---|---|---|
| 加工地点 | Google Cloud(远程) | 本地部署 |
| 验证 | 服务帐户 JSON 密钥 + 环境变量 | 许可证密钥字符串 |
| PDF 输入 | 上传到 GCS + 异步 API | input.LoadPdf() 直接 |
| 受密码保护的PDF | 不支持 | LoadPdf(path, Password: "...") |
| 多页 TIFF 输入 | 有限的 | input.LoadImageFrames() |
| 可搜索的 PDF 输出 | 不可用 | result.SaveAsSearchablePdf() |
| 结构化数据访问 | Protobuf:页面 > 块 > 段落 > 单词 > 符号 | result.Paragraphs, result.Lines, result.Words (类型化的.NET对象) |
| 段落文本属性 | 不——需要符号连接 | paragraph.Text 直接属性 |
| 置信度评分 | 每个符号(需要循环) | result.Confidence, word.Confidence |
| 自动图像预处理 | 无(机器学习处理) | 校正倾斜、降噪、对比度、二值化、锐化 |
| 基于区域的OCR | 没有本地作物 | CropRectangle 在 OcrInput 上 |
| 条形码读取 | 独立的 API 功能 | ocr.Configuration.ReadBarCodes = true |
| 速率限制 | 默认值为每分钟 1800 次请求。 | 无(CPU密集型) |
| 离线/物理隔离 | 否 | 是 |
| 单份文件成本 | 每1000张图片1.50美元; 每页PDF文件0.0015美元 | 无(永久许可) |
| 支持的语言 | 约50 | 125+ |
| FedRAMP 授权 | 未经授权 | 不适用(现场) |
| HIPAA合规路径 | 需要签署业务伙伴协议 | 不涉及第三方数据处理 |
| .NET Framework支持 | .NET Standard 2.0+ | .NET Framework 4.6.2+ 和.NET 5/6/7/8/9 |
| 需要NuGet包 | Google.Cloud.Vision.V1 + Google.Cloud.Storage.V1 |
IronOcr 仅 |
| 定价模式 | 按请求计量计费 | 永久($999 Lite/ $1,499 Plus / $2,999 Professional / $5,999 Unlimited) |
快速入门:Google Cloud Vision OCR 到IronOCR 的迁移
步骤 1:替换 NuGet 软件包
移除 Google Cloud 软件包:
dotnet remove package Google.Cloud.Vision.V1
dotnet remove package Google.Cloud.Storage.V1
dotnet remove package Google.Cloud.Vision.V1
dotnet remove package Google.Cloud.Storage.V1
从NuGet包页面安装IronOCR :
dotnet add package IronOcr
步骤 2:更新命名空间
用IronOCR命名空间替换Google Cloud命名空间:
// Before (Google Cloud Vision)
using Google.Cloud.Vision.V1;
using Google.Cloud.Storage.V1;
using Google.Protobuf;
using Grpc.Core;
// After (IronOCR)
using IronOcr;
// Before (Google Cloud Vision)
using Google.Cloud.Vision.V1;
using Google.Cloud.Storage.V1;
using Google.Protobuf;
using Grpc.Core;
// After (IronOCR)
using IronOcr;
Imports IronOcr
步骤 3:初始化许可证
在应用程序启动时添加一次许可证初始化,然后再创建任何IronTesseract实例:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
在生产环境中,从环境变量或密钥管理器中读取密钥:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable not set.");
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable not set.");
Imports System
Imports IronOcr
IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE"), Throw New InvalidOperationException("IRONOCR_LICENSE environment variable not set."))
代码迁移示例
取消服务帐户凭据配置
Google Cloud Vision 客户端初始化看起来只有一行代码,但实际上需要大量的先决基础设施。 加入项目的每个开发人员、每个部署环境和每个 CI/CD 管道都需要完整的凭证配置,构造函数才能顺利完成而不抛出异常。
Google Cloud Vision 方法:
using Google.Cloud.Vision.V1;
// Prerequisites before this class can be instantiated:
// 1. GCP project created and Vision API enabled in GCP Console
// 2. Service account created with roles/cloudvision.user IAM role
// 3. JSON key file downloaded to every server that runs this code
// 4. GOOGLE_APPLICATION_CREDENTIALS env var pointing to the JSON file
// 5. JSON file excluded from source control via .gitignore
// 6. Key rotation schedule established (recommended: 90 days)
// 7. Separate credentials per environment (dev/staging/prod)
public class DocumentOcrService
{
private readonly ImageAnnotatorClient _client;
private readonly string _projectId;
public DocumentOcrService(string projectId)
{
_projectId = projectId;
// Throws RpcException(StatusCode.PermissionDenied) if any prerequisite is missing
_client = ImageAnnotatorClient.Create();
}
public string ReadDocument(string imagePath)
{
var image = Image.FromFile(imagePath);
var response = _client.DetectText(image);
return response.Count > 0 ? response[0].Description : string.Empty;
}
}
using Google.Cloud.Vision.V1;
// Prerequisites before this class can be instantiated:
// 1. GCP project created and Vision API enabled in GCP Console
// 2. Service account created with roles/cloudvision.user IAM role
// 3. JSON key file downloaded to every server that runs this code
// 4. GOOGLE_APPLICATION_CREDENTIALS env var pointing to the JSON file
// 5. JSON file excluded from source control via .gitignore
// 6. Key rotation schedule established (recommended: 90 days)
// 7. Separate credentials per environment (dev/staging/prod)
public class DocumentOcrService
{
private readonly ImageAnnotatorClient _client;
private readonly string _projectId;
public DocumentOcrService(string projectId)
{
_projectId = projectId;
// Throws RpcException(StatusCode.PermissionDenied) if any prerequisite is missing
_client = ImageAnnotatorClient.Create();
}
public string ReadDocument(string imagePath)
{
var image = Image.FromFile(imagePath);
var response = _client.DetectText(image);
return response.Count > 0 ? response[0].Description : string.Empty;
}
}
Imports Google.Cloud.Vision.V1
' Prerequisites before this class can be instantiated:
' 1. GCP project created and Vision API enabled in GCP Console
' 2. Service account created with roles/cloudvision.user IAM role
' 3. JSON key file downloaded to every server that runs this code
' 4. GOOGLE_APPLICATION_CREDENTIALS env var pointing to the JSON file
' 5. JSON file excluded from source control via .gitignore
' 6. Key rotation schedule established (recommended: 90 days)
' 7. Separate credentials per environment (dev/staging/prod)
Public Class DocumentOcrService
Private ReadOnly _client As ImageAnnotatorClient
Private ReadOnly _projectId As String
Public Sub New(projectId As String)
_projectId = projectId
' Throws RpcException(StatusCode.PermissionDenied) if any prerequisite is missing
_client = ImageAnnotatorClient.Create()
End Sub
Public Function ReadDocument(imagePath As String) As String
Dim image = Image.FromFile(imagePath)
Dim response = _client.DetectText(image)
Return If(response.Count > 0, response(0).Description, String.Empty)
End Function
End Class
IronOCR方法:
using IronOcr;
// Prerequisites: set the license key once at app startup
// 否 JSON files, no environment variables beyond the key, no GCP Console configuration
// 否 key rotation, no IAM roles, no per-environment credential sets
public class DocumentOcrService
{
private readonly IronTesseract _ocr;
public DocumentOcrService()
{
// IronTesseract is ready immediately — no external validation required
_ocr = new IronTesseract();
}
public string ReadDocument(string imagePath)
{
return _ocr.Read(imagePath).Text;
}
}
using IronOcr;
// Prerequisites: set the license key once at app startup
// 否 JSON files, no environment variables beyond the key, no GCP Console configuration
// 否 key rotation, no IAM roles, no per-environment credential sets
public class DocumentOcrService
{
private readonly IronTesseract _ocr;
public DocumentOcrService()
{
// IronTesseract is ready immediately — no external validation required
_ocr = new IronTesseract();
}
public string ReadDocument(string imagePath)
{
return _ocr.Read(imagePath).Text;
}
}
Imports IronOcr
' Prerequisites: set the license key once at app startup
' 否 JSON files, no environment variables beyond the key, no GCP Console configuration
' 否 key rotation, no IAM roles, no per-environment credential sets
Public Class DocumentOcrService
Private ReadOnly _ocr As IronTesseract
Public Sub New()
' IronTesseract is ready immediately — no external validation required
_ocr = New IronTesseract()
End Sub
Public Function ReadDocument(imagePath As String) As String
Return _ocr.Read(imagePath).Text
End Function
End Class
操作上的区别是具体的:Google Cloud Vision在运行时生成五类RpcException—— DeadlineExceeded 和 Unauthenticated——每个代表不同的基础设施故障模式。 IronOCR的故障模式是OcrException(处理失败)。 有关配置选项,请参阅IronTesseract 设置指南;有关许可详情,请参阅IronOCR产品页面。
用多种要素类型替换批量标注请求
Google Cloud Vision支持将多个图像批处理到单个DOCUMENT_TEXT_DETECTION结果时,或在提交多张图像以最小化来回开销时。 Protobuf响应需要通过索引将每个AnnotateImageResponse匹配回其原始请求。
Google Cloud Vision 方法:
using Google.Cloud.Vision.V1;
using System.Collections.Generic;
public class BatchAnnotationService
{
private readonly ImageAnnotatorClient _client;
public BatchAnnotationService()
{
_client = ImageAnnotatorClient.Create();
}
public List<string> BatchAnnotateImages(string[] imagePaths)
{
// Build one request per image with TEXT_DETECTION feature
var requests = imagePaths.Select(path => new AnnotateImageRequest
{
Image = Image.FromFile(path),
Features =
{
new 特征 { Type = Feature.Types.Type.TextDetection },
new 特征 { Type = Feature.Types.Type.DocumentTextDetection }
}
}).ToList();
// Single round-trip for all images in the batch
var batchResponse = _client.BatchAnnotateImages(requests);
// Match responses back to requests by index
var results = new List<string>();
for (int i = 0; i < batchResponse.Responses.Count; i++)
{
var response = batchResponse.Responses[i];
if (response.Error != null)
{
// Per-item error in batch — must handle individually
results.Add($"Error on {imagePaths[i]}: {response.Error.Message}");
continue;
}
// Prefer DOCUMENT_TEXT_DETECTION full text if available
var fullText = response.FullTextAnnotation?.Text
?? response.TextAnnotations.FirstOrDefault()?.Description
?? string.Empty;
results.Add(fullText);
}
return results;
}
}
using Google.Cloud.Vision.V1;
using System.Collections.Generic;
public class BatchAnnotationService
{
private readonly ImageAnnotatorClient _client;
public BatchAnnotationService()
{
_client = ImageAnnotatorClient.Create();
}
public List<string> BatchAnnotateImages(string[] imagePaths)
{
// Build one request per image with TEXT_DETECTION feature
var requests = imagePaths.Select(path => new AnnotateImageRequest
{
Image = Image.FromFile(path),
Features =
{
new 特征 { Type = Feature.Types.Type.TextDetection },
new 特征 { Type = Feature.Types.Type.DocumentTextDetection }
}
}).ToList();
// Single round-trip for all images in the batch
var batchResponse = _client.BatchAnnotateImages(requests);
// Match responses back to requests by index
var results = new List<string>();
for (int i = 0; i < batchResponse.Responses.Count; i++)
{
var response = batchResponse.Responses[i];
if (response.Error != null)
{
// Per-item error in batch — must handle individually
results.Add($"Error on {imagePaths[i]}: {response.Error.Message}");
continue;
}
// Prefer DOCUMENT_TEXT_DETECTION full text if available
var fullText = response.FullTextAnnotation?.Text
?? response.TextAnnotations.FirstOrDefault()?.Description
?? string.Empty;
results.Add(fullText);
}
return results;
}
}
Imports Google.Cloud.Vision.V1
Imports System.Collections.Generic
Imports System.Linq
Public Class BatchAnnotationService
Private ReadOnly _client As ImageAnnotatorClient
Public Sub New()
_client = ImageAnnotatorClient.Create()
End Sub
Public Function BatchAnnotateImages(imagePaths As String()) As List(Of String)
' Build one request per image with TEXT_DETECTION feature
Dim requests = imagePaths.Select(Function(path) New AnnotateImageRequest With {
.Image = Image.FromFile(path),
.Features = {
New Feature With {.Type = Feature.Types.Type.TextDetection},
New Feature With {.Type = Feature.Types.Type.DocumentTextDetection}
}
}).ToList()
' Single round-trip for all images in the batch
Dim batchResponse = _client.BatchAnnotateImages(requests)
' Match responses back to requests by index
Dim results = New List(Of String)()
For i As Integer = 0 To batchResponse.Responses.Count - 1
Dim response = batchResponse.Responses(i)
If response.Error IsNot Nothing Then
' Per-item error in batch — must handle individually
results.Add($"Error on {imagePaths(i)}: {response.Error.Message}")
Continue For
End If
' Prefer DOCUMENT_TEXT_DETECTION full text if available
Dim fullText = If(response.FullTextAnnotation?.Text, response.TextAnnotations.FirstOrDefault()?.Description, String.Empty)
results.Add(fullText)
Next
Return results
End Function
End Class
IronOCR方法:
using IronOcr;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
public class BatchAnnotationService
{
public List<string> BatchAnnotateImages(string[] imagePaths)
{
var results = new ConcurrentDictionary<int, string>();
// Parallel processing — no batch size limit, no network round-trips
Parallel.For(0, imagePaths.Length, i =>
{
var ocr = new IronTesseract(); // thread-safe: one instance per thread
results[i] = ocr.Read(imagePaths[i]).Text;
});
// Reconstruct in original order
return Enumerable.Range(0, imagePaths.Length)
.Select(i => results[i])
.ToList();
}
public OcrResult BatchAsDocument(string[] imagePaths)
{
// Load all images into a single OcrInput for combined document output
using var input = new OcrInput();
foreach (var path in imagePaths)
input.LoadImage(path);
return new IronTesseract().Read(input);
}
}
using IronOcr;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
public class BatchAnnotationService
{
public List<string> BatchAnnotateImages(string[] imagePaths)
{
var results = new ConcurrentDictionary<int, string>();
// Parallel processing — no batch size limit, no network round-trips
Parallel.For(0, imagePaths.Length, i =>
{
var ocr = new IronTesseract(); // thread-safe: one instance per thread
results[i] = ocr.Read(imagePaths[i]).Text;
});
// Reconstruct in original order
return Enumerable.Range(0, imagePaths.Length)
.Select(i => results[i])
.ToList();
}
public OcrResult BatchAsDocument(string[] imagePaths)
{
// Load all images into a single OcrInput for combined document output
using var input = new OcrInput();
foreach (var path in imagePaths)
input.LoadImage(path);
return new IronTesseract().Read(input);
}
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading.Tasks
Public Class BatchAnnotationService
Public Function BatchAnnotateImages(imagePaths As String()) As List(Of String)
Dim results = New ConcurrentDictionary(Of Integer, String)()
' Parallel processing — no batch size limit, no network round-trips
Parallel.For(0, imagePaths.Length, Sub(i)
Dim ocr = New IronTesseract() ' thread-safe: one instance per thread
results(i) = ocr.Read(imagePaths(i)).Text
End Sub)
' Reconstruct in original order
Return Enumerable.Range(0, imagePaths.Length) _
.Select(Function(i) results(i)) _
.ToList()
End Function
Public Function BatchAsDocument(imagePaths As String()) As OcrResult
' Load all images into a single OcrInput for combined document output
Using input As New OcrInput()
For Each path In imagePaths
input.LoadImage(path)
Next
Return New IronTesseract().Read(input)
End Using
End Function
End Class
IronOCR在 CPU 核心上并行运行批处理,网络开销为零。 没有BatchSize上限、没有响应索引匹配,也没有针对网络或凭证失败的逐项错误处理。 对于将多个图像组合成单个逻辑文档的工作负载——例如,将扫描的多页表单作为单独的JPEG提交——result.Pages被索引到每一个输入图像。 多线程示例展示了并行处理的性能基准。
迁移 Protobuf 词级标注提取
Google Cloud Vision 的词级边界框和置信度数据需要遍历整个 Protobuf 层次结构:页面,然后是块,然后是段落,然后是单词,最后是符号。 提取单词文本需要连接每个单词的符号——.Text属性。 边界框坐标以Vertices列表,而不是离散的X、Y、宽度、高度字段。
Google Cloud Vision 方法:
using Google.Cloud.Vision.V1;
using System.Collections.Generic;
public record WordAnnotation(string Text, int X, int Y, int Width, int Height, float Confidence);
public class WordLevelExtractor
{
private readonly ImageAnnotatorClient _client;
public WordLevelExtractor()
{
_client = ImageAnnotatorClient.Create();
}
public List<WordAnnotation> ExtractWordAnnotations(string imagePath)
{
var image = Image.FromFile(imagePath);
var annotation = _client.DetectDocumentText(image);
var words = new List<WordAnnotation>();
// Navigate: Pages -> Blocks -> Paragraphs -> Words
foreach (var page in annotation.Pages)
{
foreach (var block in page.Blocks)
{
foreach (var paragraph in block.Paragraphs)
{
foreach (var word in paragraph.Words)
{
// Word.Text does not exist — must concatenate Symbols
var text = string.Concat(word.Symbols.Select(s => s.Text));
// BoundingPoly has Vertices, not X/Y/Width/Height
var vertices = word.BoundingBox.Vertices;
int x = vertices[0].X;
int y = vertices[0].Y;
int width = vertices.Count > 1 ? vertices[1].X - vertices[0].X : 0;
int height = vertices.Count > 2 ? vertices[2].Y - vertices[0].Y : 0;
words.Add(new WordAnnotation(text, x, y, width, height, word.Confidence));
}
}
}
}
return words;
}
}
using Google.Cloud.Vision.V1;
using System.Collections.Generic;
public record WordAnnotation(string Text, int X, int Y, int Width, int Height, float Confidence);
public class WordLevelExtractor
{
private readonly ImageAnnotatorClient _client;
public WordLevelExtractor()
{
_client = ImageAnnotatorClient.Create();
}
public List<WordAnnotation> ExtractWordAnnotations(string imagePath)
{
var image = Image.FromFile(imagePath);
var annotation = _client.DetectDocumentText(image);
var words = new List<WordAnnotation>();
// Navigate: Pages -> Blocks -> Paragraphs -> Words
foreach (var page in annotation.Pages)
{
foreach (var block in page.Blocks)
{
foreach (var paragraph in block.Paragraphs)
{
foreach (var word in paragraph.Words)
{
// Word.Text does not exist — must concatenate Symbols
var text = string.Concat(word.Symbols.Select(s => s.Text));
// BoundingPoly has Vertices, not X/Y/Width/Height
var vertices = word.BoundingBox.Vertices;
int x = vertices[0].X;
int y = vertices[0].Y;
int width = vertices.Count > 1 ? vertices[1].X - vertices[0].X : 0;
int height = vertices.Count > 2 ? vertices[2].Y - vertices[0].Y : 0;
words.Add(new WordAnnotation(text, x, y, width, height, word.Confidence));
}
}
}
}
return words;
}
}
Imports Google.Cloud.Vision.V1
Imports System.Collections.Generic
Imports System.Linq
Public Class WordAnnotation
Public Property Text As String
Public Property X As Integer
Public Property Y As Integer
Public Property Width As Integer
Public Property Height As Integer
Public Property Confidence As Single
Public Sub New(text As String, x As Integer, y As Integer, width As Integer, height As Integer, confidence As Single)
Me.Text = text
Me.X = x
Me.Y = y
Me.Width = width
Me.Height = height
Me.Confidence = confidence
End Sub
End Class
Public Class WordLevelExtractor
Private ReadOnly _client As ImageAnnotatorClient
Public Sub New()
_client = ImageAnnotatorClient.Create()
End Sub
Public Function ExtractWordAnnotations(imagePath As String) As List(Of WordAnnotation)
Dim image = Image.FromFile(imagePath)
Dim annotation = _client.DetectDocumentText(image)
Dim words As New List(Of WordAnnotation)()
' Navigate: Pages -> Blocks -> Paragraphs -> Words
For Each page In annotation.Pages
For Each block In page.Blocks
For Each paragraph In block.Paragraphs
For Each word In paragraph.Words
' Word.Text does not exist — must concatenate Symbols
Dim text = String.Concat(word.Symbols.Select(Function(s) s.Text))
' BoundingPoly has Vertices, not X/Y/Width/Height
Dim vertices = word.BoundingBox.Vertices
Dim x As Integer = vertices(0).X
Dim y As Integer = vertices(0).Y
Dim width As Integer = If(vertices.Count > 1, vertices(1).X - vertices(0).X, 0)
Dim height As Integer = If(vertices.Count > 2, vertices(2).Y - vertices(0).Y, 0)
words.Add(New WordAnnotation(text, x, y, width, height, word.Confidence))
Next
Next
Next
Next
Return words
End Function
End Class
IronOCR方法:
using IronOcr;
using System.Collections.Generic;
public record WordAnnotation(string Text, int X, int Y, int Width, int Height, double Confidence);
public class WordLevelExtractor
{
private readonly IronTesseract _ocr;
public WordLevelExtractor()
{
_ocr = new IronTesseract();
}
public List<WordAnnotation> ExtractWordAnnotations(string imagePath)
{
var result = _ocr.Read(imagePath);
// Words are a flat collection — no hierarchy traversal, no symbol concatenation
return result.Words.Select(w => new WordAnnotation(
Text: w.Text, // direct string property
X: w.X,
Y: w.Y,
Width: w.Width,
Height: w.Height,
Confidence: w.Confidence // double, no conversion needed
)).ToList();
}
}
using IronOcr;
using System.Collections.Generic;
public record WordAnnotation(string Text, int X, int Y, int Width, int Height, double Confidence);
public class WordLevelExtractor
{
private readonly IronTesseract _ocr;
public WordLevelExtractor()
{
_ocr = new IronTesseract();
}
public List<WordAnnotation> ExtractWordAnnotations(string imagePath)
{
var result = _ocr.Read(imagePath);
// Words are a flat collection — no hierarchy traversal, no symbol concatenation
return result.Words.Select(w => new WordAnnotation(
Text: w.Text, // direct string property
X: w.X,
Y: w.Y,
Width: w.Width,
Height: w.Height,
Confidence: w.Confidence // double, no conversion needed
)).ToList();
}
}
Imports IronOcr
Imports System.Collections.Generic
Public Class WordAnnotation
Public Property Text As String
Public Property X As Integer
Public Property Y As Integer
Public Property Width As Integer
Public Property Height As Integer
Public Property Confidence As Double
Public Sub New(text As String, x As Integer, y As Integer, width As Integer, height As Integer, confidence As Double)
Me.Text = text
Me.X = x
Me.Y = y
Me.Width = width
Me.Height = height
Me.Confidence = confidence
End Sub
End Class
Public Class WordLevelExtractor
Private ReadOnly _ocr As IronTesseract
Public Sub New()
_ocr = New IronTesseract()
End Sub
Public Function ExtractWordAnnotations(imagePath As String) As List(Of WordAnnotation)
Dim result = _ocr.Read(imagePath)
' Words are a flat collection — no hierarchy traversal, no symbol concatenation
Return result.Words.Select(Function(w) New WordAnnotation(
Text:=w.Text, ' direct string property
X:=w.X,
Y:=w.Y,
Width:=w.Width,
Height:=w.Height,
Confidence:=w.Confidence ' double, no conversion needed
)).ToList()
End Function
End Class
Google Cloud Vision 版本中的符号连接循环并非设计选择,而是 Protobuf 架构所要求的。 Word.Text 不是响应对象中的属性。 每个使用单词级API的团队都会编写一个等效的循环。IronOCR的Height 和 Confidence 公开为一流属性。 读取结果指南记录了每个粒度级别可用的完整属性集。
将多页 TIFF 处理为可搜索 PDF 输出
Google Cloud Vision 将多页 TIFF 文件视为一系列连续的图像,每张图像都需要单独调用 API。 目前没有一个 API 调用可以接受 TIFF 文件并返回所有帧的结构化输出。 从Google Cloud Vision结果生成可搜索的 PDF 需要单独的 PDF 生成库——API 只返回文本。
Google Cloud Vision 方法:
using Google.Cloud.Vision.V1;
using System.Collections.Generic;
using System.Drawing; // for TIFF frame extraction
using System.Drawing.Imaging;
public class TiffProcessingService
{
private readonly ImageAnnotatorClient _client;
public TiffProcessingService()
{
_client = ImageAnnotatorClient.Create();
}
public List<string> ProcessMultiPageTiff(string tiffPath)
{
var pageTexts = new List<string>();
// Load TIFF and extract frames manually using System.Drawing
using var tiff = System.Drawing.Image.FromFile(tiffPath);
var frameDimension = new FrameDimension(tiff.FrameDimensionsList[0]);
int frameCount = tiff.GetFrameCount(frameDimension);
for (int i = 0; i < frameCount; i++)
{
tiff.SelectActiveFrame(frameDimension, i);
// Save each frame to a temp file — Vision API does not accept TIFF frames directly
var tempPath = Path.Combine(Path.GetTempPath(), $"tiff-frame-{i}.jpg");
tiff.Save(tempPath, ImageFormat.Jpeg);
// One API call per frame — each call = one unit of quota
var visionImage = Google.Cloud.Vision.V1.Image.FromFile(tempPath);
var response = _client.DetectText(visionImage);
pageTexts.Add(response.FirstOrDefault()?.Description ?? string.Empty);
File.Delete(tempPath);
}
// Producing a searchable PDF requires a separate library (e.g., iTextSharp)
//Google Cloud Visionhas no PDF output capability
return pageTexts;
}
}
using Google.Cloud.Vision.V1;
using System.Collections.Generic;
using System.Drawing; // for TIFF frame extraction
using System.Drawing.Imaging;
public class TiffProcessingService
{
private readonly ImageAnnotatorClient _client;
public TiffProcessingService()
{
_client = ImageAnnotatorClient.Create();
}
public List<string> ProcessMultiPageTiff(string tiffPath)
{
var pageTexts = new List<string>();
// Load TIFF and extract frames manually using System.Drawing
using var tiff = System.Drawing.Image.FromFile(tiffPath);
var frameDimension = new FrameDimension(tiff.FrameDimensionsList[0]);
int frameCount = tiff.GetFrameCount(frameDimension);
for (int i = 0; i < frameCount; i++)
{
tiff.SelectActiveFrame(frameDimension, i);
// Save each frame to a temp file — Vision API does not accept TIFF frames directly
var tempPath = Path.Combine(Path.GetTempPath(), $"tiff-frame-{i}.jpg");
tiff.Save(tempPath, ImageFormat.Jpeg);
// One API call per frame — each call = one unit of quota
var visionImage = Google.Cloud.Vision.V1.Image.FromFile(tempPath);
var response = _client.DetectText(visionImage);
pageTexts.Add(response.FirstOrDefault()?.Description ?? string.Empty);
File.Delete(tempPath);
}
// Producing a searchable PDF requires a separate library (e.g., iTextSharp)
//Google Cloud Visionhas no PDF output capability
return pageTexts;
}
}
Imports Google.Cloud.Vision.V1
Imports System.Collections.Generic
Imports System.Drawing ' for TIFF frame extraction
Imports System.Drawing.Imaging
Imports System.IO
Public Class TiffProcessingService
Private ReadOnly _client As ImageAnnotatorClient
Public Sub New()
_client = ImageAnnotatorClient.Create()
End Sub
Public Function ProcessMultiPageTiff(tiffPath As String) As List(Of String)
Dim pageTexts As New List(Of String)()
' Load TIFF and extract frames manually using System.Drawing
Using tiff As System.Drawing.Image = System.Drawing.Image.FromFile(tiffPath)
Dim frameDimension As New FrameDimension(tiff.FrameDimensionsList(0))
Dim frameCount As Integer = tiff.GetFrameCount(frameDimension)
For i As Integer = 0 To frameCount - 1
tiff.SelectActiveFrame(frameDimension, i)
' Save each frame to a temp file — Vision API does not accept TIFF frames directly
Dim tempPath As String = Path.Combine(Path.GetTempPath(), $"tiff-frame-{i}.jpg")
tiff.Save(tempPath, ImageFormat.Jpeg)
' One API call per frame — each call = one unit of quota
Dim visionImage As Google.Cloud.Vision.V1.Image = Google.Cloud.Vision.V1.Image.FromFile(tempPath)
Dim response = _client.DetectText(visionImage)
pageTexts.Add(If(response.FirstOrDefault()?.Description, String.Empty))
File.Delete(tempPath)
Next
End Using
' Producing a searchable PDF requires a separate library (e.g., iTextSharp)
' Google Cloud Vision has no PDF output capability
Return pageTexts
End Function
End Class
IronOCR方法:
using IronOcr;
public class TiffProcessingService
{
private readonly IronTesseract _ocr;
public TiffProcessingService()
{
_ocr = new IronTesseract();
}
public string ProcessMultiPageTiff(string tiffPath)
{
using var input = new OcrInput();
// LoadImageFrames handles all TIFF frames in one call — no temp files, no frame loop
input.LoadImageFrames(tiffPath);
var result = _ocr.Read(input);
return result.Text;
}
public void ProcessMultiPageTiffToSearchablePdf(string tiffPath, string outputPdfPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
var result = _ocr.Read(input);
//Google Cloud Visionhas no equivalent — this single call produces a searchable PDF
result.SaveAsSearchablePdf(outputPdfPath);
}
public void ProcessLowQualityTiff(string tiffPath, string outputPdfPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
// Preprocessing before OCR improves accuracy on degraded scans
input.Deskew();
input.DeNoise();
input.Contrast();
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPdfPath);
}
}
using IronOcr;
public class TiffProcessingService
{
private readonly IronTesseract _ocr;
public TiffProcessingService()
{
_ocr = new IronTesseract();
}
public string ProcessMultiPageTiff(string tiffPath)
{
using var input = new OcrInput();
// LoadImageFrames handles all TIFF frames in one call — no temp files, no frame loop
input.LoadImageFrames(tiffPath);
var result = _ocr.Read(input);
return result.Text;
}
public void ProcessMultiPageTiffToSearchablePdf(string tiffPath, string outputPdfPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
var result = _ocr.Read(input);
//Google Cloud Visionhas no equivalent — this single call produces a searchable PDF
result.SaveAsSearchablePdf(outputPdfPath);
}
public void ProcessLowQualityTiff(string tiffPath, string outputPdfPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
// Preprocessing before OCR improves accuracy on degraded scans
input.Deskew();
input.DeNoise();
input.Contrast();
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPdfPath);
}
}
Imports IronOcr
Public Class TiffProcessingService
Private ReadOnly _ocr As IronTesseract
Public Sub New()
_ocr = New IronTesseract()
End Sub
Public Function ProcessMultiPageTiff(tiffPath As String) As String
Using input As New OcrInput()
' LoadImageFrames handles all TIFF frames in one call — no temp files, no frame loop
input.LoadImageFrames(tiffPath)
Dim result = _ocr.Read(input)
Return result.Text
End Using
End Function
Public Sub ProcessMultiPageTiffToSearchablePdf(tiffPath As String, outputPdfPath As String)
Using input As New OcrInput()
input.LoadImageFrames(tiffPath)
Dim result = _ocr.Read(input)
' Google Cloud Vision has no equivalent — this single call produces a searchable PDF
result.SaveAsSearchablePdf(outputPdfPath)
End Using
End Sub
Public Sub ProcessLowQualityTiff(tiffPath As String, outputPdfPath As String)
Using input As New OcrInput()
input.LoadImageFrames(tiffPath)
' Preprocessing before OCR improves accuracy on degraded scans
input.Deskew()
input.DeNoise()
input.Contrast()
Dim result = _ocr.Read(input)
result.SaveAsSearchablePdf(outputPdfPath)
End Using
End Sub
End Class
Google Cloud Vision 版本需要 System.Drawing 来进行帧提取,将临时 JPEG 文件写入磁盘,每帧调用一次 API(消耗的配额与帧数成正比),并且需要一个单独的 PDF 库来生成除纯文本之外的任何输出。 IronOCR通过SaveAsSearchablePdf生成可搜索的PDF输出。 对于扫描的档案TIFF文件,在ProcessLowQualityTiff中的预处理管道可以一次性处理最常见的质量问题。 有关完整的 API,请参阅TIFF 和 GIF 输入以及可搜索 PDF 输出。
带进度跟踪的限速批量迁移
在生产规模下,Google Cloud Vision 每分钟 1800 个请求的默认配额需要进行限流或使用指数退避的重试逻辑。 一夜之间处理 5000 份文件将多次超出配额。 每次超出配额都会阻塞管道,强制等待 60 秒。 IronOCR没有速率限制——管道仅受 CPU 核心和可用线程的限制。
Google Cloud Vision 方法:
using Google.Cloud.Vision.V1;
using Grpc.Core;
using System.Collections.Generic;
public class ThrottledBatchProcessor
{
private readonly ImageAnnotatorClient _client;
private const int MaxRequestsPerMinute = 1800;
private const int RetryDelayMs = 60_000;
public ThrottledBatchProcessor()
{
_client = ImageAnnotatorClient.Create();
}
public async Task<Dictionary<string, string>> ProcessWithThrottlingAsync(
string[] imagePaths,
IProgress<(int completed, int total)> progress)
{
var results = new Dictionary<string, string>();
int completed = 0;
foreach (var path in imagePaths)
{
bool succeeded = false;
while (!succeeded)
{
try
{
var image = Google.Cloud.Vision.V1.Image.FromFile(path);
var response = _client.DetectText(image);
results[path] = response.FirstOrDefault()?.Description ?? string.Empty;
succeeded = true;
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.ResourceExhausted)
{
// Rate limit exceeded — wait and retry
await Task.Delay(RetryDelayMs);
}
}
progress.Report((++completed, imagePaths.Length));
}
return results;
}
}
using Google.Cloud.Vision.V1;
using Grpc.Core;
using System.Collections.Generic;
public class ThrottledBatchProcessor
{
private readonly ImageAnnotatorClient _client;
private const int MaxRequestsPerMinute = 1800;
private const int RetryDelayMs = 60_000;
public ThrottledBatchProcessor()
{
_client = ImageAnnotatorClient.Create();
}
public async Task<Dictionary<string, string>> ProcessWithThrottlingAsync(
string[] imagePaths,
IProgress<(int completed, int total)> progress)
{
var results = new Dictionary<string, string>();
int completed = 0;
foreach (var path in imagePaths)
{
bool succeeded = false;
while (!succeeded)
{
try
{
var image = Google.Cloud.Vision.V1.Image.FromFile(path);
var response = _client.DetectText(image);
results[path] = response.FirstOrDefault()?.Description ?? string.Empty;
succeeded = true;
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.ResourceExhausted)
{
// Rate limit exceeded — wait and retry
await Task.Delay(RetryDelayMs);
}
}
progress.Report((++completed, imagePaths.Length));
}
return results;
}
}
Imports Google.Cloud.Vision.V1
Imports Grpc.Core
Imports System.Collections.Generic
Imports System.Threading.Tasks
Public Class ThrottledBatchProcessor
Private ReadOnly _client As ImageAnnotatorClient
Private Const MaxRequestsPerMinute As Integer = 1800
Private Const RetryDelayMs As Integer = 60000
Public Sub New()
_client = ImageAnnotatorClient.Create()
End Sub
Public Async Function ProcessWithThrottlingAsync(
imagePaths As String(),
progress As IProgress(Of (completed As Integer, total As Integer))) As Task(Of Dictionary(Of String, String))
Dim results As New Dictionary(Of String, String)()
Dim completed As Integer = 0
For Each path In imagePaths
Dim succeeded As Boolean = False
While Not succeeded
Try
Dim image = Google.Cloud.Vision.V1.Image.FromFile(path)
Dim response = _client.DetectText(image)
results(path) = If(response.FirstOrDefault()?.Description, String.Empty)
succeeded = True
Catch ex As RpcException When ex.StatusCode = StatusCode.ResourceExhausted
' Rate limit exceeded — wait and retry
Await Task.Delay(RetryDelayMs)
End Try
End While
progress.Report((Threading.Interlocked.Increment(completed), imagePaths.Length))
Next
Return results
End Function
End Class
IronOCR方法:
using IronOcr;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class BatchProcessor
{
public Dictionary<string, string> ProcessBatch(
string[] imagePaths,
IProgress<(int completed, int total)> progress)
{
var results = new ConcurrentDictionary<string, string>();
int completed = 0;
// 无速率限制 — full parallelism, no retry logic needed
Parallel.ForEach(imagePaths, imagePath =>
{
var ocr = new IronTesseract();
results[imagePath] = ocr.Read(imagePath).Text;
progress.Report((Interlocked.Increment(ref completed), imagePaths.Length));
});
return new Dictionary<string, string>(results);
}
public void ProcessBatchToSearchablePdfs(
string[] imagePaths,
string outputDirectory)
{
Parallel.ForEach(imagePaths, imagePath =>
{
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
var outputPath = Path.Combine(
outputDirectory,
Path.GetFileNameWithoutExtension(imagePath) + "-searchable.pdf");
result.SaveAsSearchablePdf(outputPath);
});
}
}
using IronOcr;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class BatchProcessor
{
public Dictionary<string, string> ProcessBatch(
string[] imagePaths,
IProgress<(int completed, int total)> progress)
{
var results = new ConcurrentDictionary<string, string>();
int completed = 0;
// 无速率限制 — full parallelism, no retry logic needed
Parallel.ForEach(imagePaths, imagePath =>
{
var ocr = new IronTesseract();
results[imagePath] = ocr.Read(imagePath).Text;
progress.Report((Interlocked.Increment(ref completed), imagePaths.Length));
});
return new Dictionary<string, string>(results);
}
public void ProcessBatchToSearchablePdfs(
string[] imagePaths,
string outputDirectory)
{
Parallel.ForEach(imagePaths, imagePath =>
{
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
var outputPath = Path.Combine(
outputDirectory,
Path.GetFileNameWithoutExtension(imagePath) + "-searchable.pdf");
result.SaveAsSearchablePdf(outputPath);
});
}
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Public Class BatchProcessor
Public Function ProcessBatch(imagePaths As String(), progress As IProgress(Of (completed As Integer, total As Integer))) As Dictionary(Of String, String)
Dim results = New ConcurrentDictionary(Of String, String)()
Dim completed As Integer = 0
' 无速率限制 — full parallelism, no retry logic needed
Parallel.ForEach(imagePaths, Sub(imagePath)
Dim ocr = New IronTesseract()
results(imagePath) = ocr.Read(imagePath).Text
progress.Report((Interlocked.Increment(completed), imagePaths.Length))
End Sub)
Return New Dictionary(Of String, String)(results)
End Function
Public Sub ProcessBatchToSearchablePdfs(imagePaths As String(), outputDirectory As String)
Parallel.ForEach(imagePaths, Sub(imagePath)
Dim ocr = New IronTesseract()
Dim result = ocr.Read(imagePath)
Dim outputPath = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(imagePath) & "-searchable.pdf")
result.SaveAsSearchablePdf(outputPath)
End Sub)
End Sub
End Class
Google Cloud Vision 版本采用顺序执行,因为并行请求会成倍增加速率限制的风险。 每个ResourceExhausted异常都会增加完整的60秒停滞。 一批 5000 份文件达到配额 10 次,就会增加 10 分钟的空闲等待时间。IronOCR的版本可以在所有可用核心上并行运行,无需等待。 对于长时间运行的批处理,进度跟踪API提供内置的进度回调,而无需手动Interlocked.Increment连线。 对于批量扫描中的图像质量问题,图像质量校正指南涵盖了可在每个Read 调用之前添加的预处理管道。
Google Cloud Vision OCRAPI 与IronOCR地图参考
| Google Cloud Vision | IronOCR | 备注 |
|---|---|---|
ImageAnnotatorClient.Create() |
new IronTesseract() |
客户端初始化; 无需凭证文件 |
Image.FromFile(path) |
_ocr.Read(path) 或 input.LoadImage(path) |
直接路径读取可用于 IronTesseract |
_client.DetectText(image) |
_ocr.Read(path).Text |
TEXT_DETECTION 等效 |
_client.DetectDocumentText(image) |
_ocr.Read(path) |
DOCUMENT_TEXT_DETECTION 等效; 模式为自动 |
response[0].Description |
result.Text |
完整文档文本 |
TextAnnotation |
OcrResult |
顶级结果容器 |
annotation.Text |
result.Text |
完整文本字符串 |
annotation.Pages[i] |
result.Pages[i] |
每页访问 |
page.Blocks[i].Paragraphs[j] |
result.Paragraphs[i] |
IronOCR将段落显示为一个扁平的集合。 |
paragraph.Words.SelectMany(w => w.Symbols).Select(s => s.Text) |
paragraph.Text |
直接字符串属性; 无符号迭代 |
word.BoundingBox.Vertices |
word.X, word.Y, word.Width, word.Height |
使用离散整数属性而不是顶点列表 |
word.Confidence |
word.Confidence |
逐词置信度得分 |
page.Confidence |
result.Confidence |
总体结果信心 |
Feature.Types.Type.DocumentTextDetection |
自动翻译 | IronOCR自动选择处理模式 |
BatchAnnotateImagesRequest |
Parallel.ForEach + new IronTesseract() 每个线程 |
并行本地处理; 无批量大小限制 |
_client.BatchAnnotateImages(requests) |
new IronTesseract().Read(input) 具有多图像 OcrInput |
单次调用即可输入多幅图像 |
AsyncBatchAnnotateFilesAsync() |
input.LoadPdf(); _ocr.Read(input) |
PDF处理是同步的; 无需GCS |
StorageClient.Create() |
不需要 | 无 GCS 依赖 |
storageClient.UploadObjectAsync() |
不需要 | PDF 直接从本地路径或流加载 |
operation.PollUntilCompletedAsync() |
不需要 | 处理过程是同步的 |
RpcException (StatusCode.ResourceExhausted) |
不适用 | 无速率限制 |
RpcException (StatusCode.PermissionDenied) |
不适用 | 无运行时身份验证 |
GOOGLE_APPLICATION_CREDENTIALS 环境变量 |
IronOcr.License.LicenseKey |
字符串赋值,而非文件路径 |
常见迁移问题和解决方案
问题 1:构造函数缺少 GOOGLE_APPLICATION_CREDENTIALS 参数时抛出异常
Google Cloud Vision: ImageAnnotatorClient.Create() 异常抛出 RpcException 并且带有 StatusCode.Unauthenticated 或 StatusCode.PermissionDenied 如果环境变量未设置或指向无效文件。此故障在启动时发生,而不是第一次API调用,这意味着如果任何一个环境中缺少凭据,整个应用程序将无法初始化。
解决方案:删除Google Cloud Vision包后,从您的环境配置、CI/CD流水线秘密、Kubernetes秘密和Docker Compose文件中删除所有对GOOGLE_APPLICATION_CREDENTIALS的引用。 用单个IRONOCR_LICENSE 环境变量替换:
// Remove this from every deployment environment:
// GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
// Add this once at application startup:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable is required.");
// Remove this from every deployment environment:
// GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
// Add this once at application startup:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable is required.");
' Remove this from every deployment environment:
' GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
' Add this once at application startup:
IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE"), Throw New InvalidOperationException("IRONOCR_LICENSE environment variable is required."))
问题 2:命名空间移除后 Protobuf 符号连接代码失效
Google Cloud Vision: 在您的代码库中所有使用Google.Cloud.Vision.V1命名空间被移除后产生编译错误。 这些调用分布在接受 API 响应的任何辅助类或服务类中。
解决方案:搜索代码库中的所有SelectMany 和 w.Symbols模式,并用IronOCR结果对象的直接属性访问替换它们。 读取结果指南涵盖了OcrResult.Line 和 OcrResult.Word 上的每个可用属性:
# Find all Protobuf symbol concatenation patterns
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .
grep -rn "w\.Symbols\.Select" --include="*.cs" .
# Find all Protobuf symbol concatenation patterns
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .
grep -rn "w\.Symbols\.Select" --include="*.cs" .
替换所有出现的项:
// Before: symbol concatenation required by Protobuf schema
var text = string.Join("", paragraph.Words.SelectMany(w => w.Symbols).Select(s => s.Text));
// After: direct property on OcrResult.Paragraph
var text = paragraph.Text;
// Before: symbol concatenation required by Protobuf schema
var text = string.Join("", paragraph.Words.SelectMany(w => w.Symbols).Select(s => s.Text));
// After: direct property on OcrResult.Paragraph
var text = paragraph.Text;
' Before: symbol concatenation required by Protobuf schema
Dim text = String.Join("", paragraph.Words.SelectMany(Function(w) w.Symbols).Select(Function(s) s.Text))
' After: direct property on OcrResult.Paragraph
Dim text = paragraph.Text
问题 3:移除 Storage.V1 后,PDF 处理代码无法编译
Google Cloud Vision:删除Google.Cloud.Storage.V1 后,所有引用GcsDestination 和 PollUntilCompletedAsync的代码将无法编译。此代码可能跨多个服务类,通常代表最大的一块更改。
解决方案:删除整个 GCS 流水线。将 50Plus行的异步方法替换为IronOCR 的三行等效代码。 对于保持异步签名以兼容调用者的代码,使用Task.Run 包裹。
// Delete: StorageClient, GCS upload, AsyncBatchAnnotateFilesAsync,
// PollUntilCompletedAsync, output download, DeleteObjectAsync
// Replace with:
public async Task<string> ProcessPdfAsync(string pdfPath)
{
return await Task.Run(() =>
{
using var input = new OcrInput();
input.LoadPdf(pdfPath);
return new IronTesseract().Read(input).Text;
});
}
// Delete: StorageClient, GCS upload, AsyncBatchAnnotateFilesAsync,
// PollUntilCompletedAsync, output download, DeleteObjectAsync
// Replace with:
public async Task<string> ProcessPdfAsync(string pdfPath)
{
return await Task.Run(() =>
{
using var input = new OcrInput();
input.LoadPdf(pdfPath);
return new IronTesseract().Read(input).Text;
});
}
Imports System.Threading.Tasks
Public Async Function ProcessPdfAsync(pdfPath As String) As Task(Of String)
Return Await Task.Run(Function()
Using input As New OcrInput()
input.LoadPdf(pdfPath)
Return New IronTesseract().Read(input).Text
End Using
End Function)
End Function
对于新代码,使用本地异步OCR支持,而不是Task.Run 包装。 PDF 输入指南涵盖页面范围选择和受密码保护的 PDF 加载。
问题 4:不再需要速率限制重试逻辑
Google Cloud Vision: 任何捕获RpcException 带有StatusCode.ResourceExhausted 的代码并实现等待重试模式的代码是为了处理每分钟1,800个请求的配额撰写的。 这种重试逻辑可以嵌入到中间件、管道步骤或批处理循环中。
解决方案:移除所有与配额错误相关的重试逻辑。 IronOCR在本地进行处理,无需外部配额。 错误处理器协议从五个RpcException案例变为两个:
// Remove: all RpcException handlers for ResourceExhausted, PermissionDenied,
// Unavailable, DeadlineExceeded, Unauthenticated
//IronOCRerror surface:
try
{
var result = new IronTesseract().Read(imagePath);
if (result.Confidence < 50)
input.DeNoise(); // add preprocessing for low-confidence results
return result.Text;
}
catch (IOException ex)
{
// File not found or locked
throw new InvalidOperationException($"Cannot read: {imagePath}", ex);
}
catch (IronOcr.Exceptions.OcrException ex)
{
// Processing failure — not a transient network error
throw new InvalidOperationException($"OCR failed: {ex.Message}", ex);
}
// Remove: all RpcException handlers for ResourceExhausted, PermissionDenied,
// Unavailable, DeadlineExceeded, Unauthenticated
//IronOCRerror surface:
try
{
var result = new IronTesseract().Read(imagePath);
if (result.Confidence < 50)
input.DeNoise(); // add preprocessing for low-confidence results
return result.Text;
}
catch (IOException ex)
{
// File not found or locked
throw new InvalidOperationException($"Cannot read: {imagePath}", ex);
}
catch (IronOcr.Exceptions.OcrException ex)
{
// Processing failure — not a transient network error
throw new InvalidOperationException($"OCR failed: {ex.Message}", ex);
}
Imports IronOcr
Imports System.IO
' Remove: all RpcException handlers for ResourceExhausted, PermissionDenied,
' Unavailable, DeadlineExceeded, Unauthenticated
'IronOCRerror surface:
Try
Dim result = New IronTesseract().Read(imagePath)
If result.Confidence < 50 Then
input.DeNoise() ' add preprocessing for low-confidence results
End If
Return result.Text
Catch ex As IOException
' File not found or locked
Throw New InvalidOperationException($"Cannot read: {imagePath}", ex)
Catch ex As IronOcr.Exceptions.OcrException
' Processing failure — not a transient network error
Throw New InvalidOperationException($"OCR failed: {ex.Message}", ex)
End Try
问题 5:多页 TIFF 文件需要帧提取循环
Google Cloud Vision:现有的TIFF处理代码可能使用System.Drawing.Image提取帧,将每个帧保存为JPEG到一个临时目录,将每个JPEG作为单独的API调用提交,随后删除临时文件。 此模式每帧消耗一个配额单位,并且可能会在崩溃时留下孤立的临时文件。
解决方案:用input.LoadImageFrames() 替换帧提取循环和临时文件管理。 整个 System.Drawing 框架循环被删除:
// Remove: System.Drawing frame extraction, temp file writes, per-frame API calls
// Replace with:
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // all frames, no temp files
var result = new IronTesseract().Read(input);
// Remove: System.Drawing frame extraction, temp file writes, per-frame API calls
// Replace with:
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // all frames, no temp files
var result = new IronTesseract().Read(input);
Imports IronOcr
Dim input As New OcrInput()
input.LoadImageFrames(tiffPath) ' all frames, no temp files
Dim result = (New IronTesseract()).Read(input)
有关多帧处理选项(包括帧范围选择),请参阅TIFF 和 GIF 输入指南。
问题 6:边界多边形顶点计算中断
Google Cloud Vision: 从vertices[0].X 用于X,vertices[1].X - vertices[0].X 用于宽度,vertices[2].Y - vertices[0].Y 用于高度。 迁移后,这些表达式在IronOCR中没有等效项,因为Vertices 不存在。
解决方案:用直接整数属性替换顶点运算。 无需计算:
// Before: vertex index arithmetic
int x = word.BoundingBox.Vertices[0].X;
int y = word.BoundingBox.Vertices[0].Y;
int width = word.BoundingBox.Vertices[1].X - word.BoundingBox.Vertices[0].X;
int height = word.BoundingBox.Vertices[2].Y - word.BoundingBox.Vertices[0].Y;
// After: direct properties
int x = word.X;
int y = word.Y;
int width = word.Width;
int height = word.Height;
// Before: vertex index arithmetic
int x = word.BoundingBox.Vertices[0].X;
int y = word.BoundingBox.Vertices[0].Y;
int width = word.BoundingBox.Vertices[1].X - word.BoundingBox.Vertices[0].X;
int height = word.BoundingBox.Vertices[2].Y - word.BoundingBox.Vertices[0].Y;
// After: direct properties
int x = word.X;
int y = word.Y;
int width = word.Width;
int height = word.Height;
' Before: vertex index arithmetic
Dim x As Integer = word.BoundingBox.Vertices(0).X
Dim y As Integer = word.BoundingBox.Vertices(0).Y
Dim width As Integer = word.BoundingBox.Vertices(1).X - word.BoundingBox.Vertices(0).X
Dim height As Integer = word.BoundingBox.Vertices(2).Y - word.BoundingBox.Vertices(0).Y
' After: direct properties
Dim x As Integer = word.X
Dim y As Integer = word.Y
Dim width As Integer = word.Width
Dim height As Integer = word.Height
Google Cloud Vision OCR迁移清单
迁移前
在进行任何更改之前,请审核代码库以确定每个Google Cloud Vision依赖项:
# Find allGoogle Cloud Visionnamespace imports
grep -rn "using Google.Cloud.Vision" --include="*.cs" .
grep -rn "using Google.Cloud.Storage" --include="*.cs" .
grep -rn "using Grpc.Core" --include="*.cs" .
# Find ImageAnnotatorClient usage
grep -rn "ImageAnnotatorClient" --include="*.cs" .
# Find GCS pipeline code
grep -rn "StorageClient\|UploadObjectAsync\|DeleteObjectAsync" --include="*.cs" .
grep -rn "AsyncBatchAnnotateFilesAsync\|PollUntilCompleted" --include="*.cs" .
# Find Protobuf symbol concatenation
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .
# Find BoundingPoly vertex calculations
grep -rn "BoundingPoly\|BoundingBox\.Vertices" --include="*.cs" .
# Find rate limit retry handlers
grep -rn "ResourceExhausted\|StatusCode\." --include="*.cs" .
# Find environment variable references
grep -rn "GOOGLE_APPLICATION_CREDENTIALS" .
# Find allGoogle Cloud Visionnamespace imports
grep -rn "using Google.Cloud.Vision" --include="*.cs" .
grep -rn "using Google.Cloud.Storage" --include="*.cs" .
grep -rn "using Grpc.Core" --include="*.cs" .
# Find ImageAnnotatorClient usage
grep -rn "ImageAnnotatorClient" --include="*.cs" .
# Find GCS pipeline code
grep -rn "StorageClient\|UploadObjectAsync\|DeleteObjectAsync" --include="*.cs" .
grep -rn "AsyncBatchAnnotateFilesAsync\|PollUntilCompleted" --include="*.cs" .
# Find Protobuf symbol concatenation
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .
# Find BoundingPoly vertex calculations
grep -rn "BoundingPoly\|BoundingBox\.Vertices" --include="*.cs" .
# Find rate limit retry handlers
grep -rn "ResourceExhausted\|StatusCode\." --include="*.cs" .
# Find environment variable references
grep -rn "GOOGLE_APPLICATION_CREDENTIALS" .
开始工作前需要填写的库存清单:
- 列出所有为 OCR 输入/输出创建的 GCS 存储桶 — 安排迁移后的清理工作
- 记录服务帐户电子邮件地址,以便在迁移后在 GCP 控制台中禁用它。
- 确认所有配置了
GOOGLE_APPLICATION_CREDENTIALS的环境 - 注意任何从配置中读取 GCP 项目 ID 或存储桶名称的代码——迁移后请将其删除
代码迁移
- 从所有项目中删除
Google.Cloud.Vision.V1NuGet包 - 从所有项目中删除
Google.Cloud.Storage.V1NuGet包 - 在所有执行OCR的项目中安装
IronOcrNuGet包 - 在应用程序启动时添加
IronOcr.License.LicenseKey初始化 - 将所有
using IronOcr - 替换所有
using Google.Cloud.Storage.V1和using Grpc.Core导入 - 将
ImageAnnotatorClient.Create()替换为new IronTesseract() - 删除所有GCS管道方法 (
UploadObjectAsync、异步注释、轮询、下载、删除) - 为所有PDF处理路径替换
input.LoadPdf()(移除异步GCS编排) - 将所有Protobuf符号连接循环替换为
OcrResult属性访问 - 将
word.Height - 删除所有用于
RpcException捕获块 - 用
input.LoadImageFrames()替换每帧TIFF循环 - 将顺序批处理循环转为
IronTesseract实例 - 从所有环境配置、CI/CD管道、Docker Compose文件和Kubernetes秘密中删除
GOOGLE_APPLICATION_CREDENTIALS
后迁移
- 验证错误处理代码中没有剩余的
GoogleApiException类型引用 - 确认所有部署环境配置中不存在
GOOGLE_APPLICATION_CREDENTIALS - 在生产环境中使用的同一组样本文档上运行 OCR 流程,并比较文本输出质量。
- 对之前由 GCS 异步管道处理的文档进行 PDF 处理测试,并确认文本输出结果相同。
- 用
input.LoadPdf(path, Password: "...")测试密码保护的PDF——以前不支持 - 使用
input.LoadImageFrames()测试多页TIFF处理,并验证所有帧被处理 - 对代表性样本运行批处理程序,并确认输出质量与之前的结果一致
- 确认
result.Confidence值在您的文档语料库的可接受范围内 - 对于之前需要单独PDF库的文档,使用
result.SaveAsSearchablePdf()验证可搜索的PDF输出 - 在没有外部互联网连接的环境下运行应用程序,并确认 OCR 功能正常。
迁移到IronOCR的主要优势
凭据表面减少到零文件。 迁移后,您的基础设施中没有JSON密钥文件、GCS存储桶配置、IAM角色、服务帐户和GOOGLE_APPLICATION_CREDENTIALS环境变量。 整个凭证界面就是一个包含许可证密钥字符串的环境变量。 密钥轮换是Google Cloud Vision的一项强制性定期操作,但现在已经不再适用。 对于在多个地区或云提供商处运营的团队来说,部署配置复杂性的降低是立竿见影的。
无需外部依赖即可处理 PDF 和 TIFF 文件。GCS异步管道和 System.Drawing TIFF 帧循环已被完全移除。 input.LoadImageFrames()是替代品——都是同步的,都是本地的,从调用到结果的三行代码。 使用Google Cloud Vision无法实现的密码保护 PDF,现在只需一个额外的参数即可实现。 PDF OCR 指南和TIFF 输入指南涵盖了完整的输入 API。
以 CPU 速度进行批量处理。取消每分钟 1800 次请求的配额和强制性的 60 秒重试等待时间,意味着以前受速率限制的批量作业现在可以以可用处理器核心的速度运行。 一台拥有 16 个核心的机器可以同时处理 16 份文件,无需任何外部审批。 IronTesseract实例是限制顺序循环的直接替代。快速优化指南涵盖了针对特定文档类型调整吞吐量的引擎配置选项。
没有Protobuf的结构化数据。 每个OcrResult 公开Words 和Characters作为类型化的.NET属性,没有Protobuf命名空间依赖,没有符号连接,也没有用于边界框的顶点算术。 过去需要20行嵌套循环提取段落文本的代码减少到result.Paragraphs.Select(p => p.Text)。 对于需要单词级定位以进行文档布局分析的用例,word.Height可以直接使用。 OCR 结果页面文档包含了结果模型中的每个属性。
内置可搜索 PDF 输出。Google Cloud Vision 仅返回文本——生成可搜索 PDF 需要单独的 PDF 生成库,这意味着需要添加另一个NuGet依赖项、学习另一个 API 以及评估额外的许可。 IronOCR的result.SaveAsSearchablePdf(outputPath)从任何OCR结果生成一个完全可搜索的PDF,仅需一行代码。对于文档归档工作流和法律发现管道,这消除了一个完整的依赖项。 可搜索的 PDF 示例完整地展示了该模式。
受IronOCR行业的数据主权。IronOCR处理的文档绝不会离开服务器。 对于 HIPAA 涵盖的医疗记录、ITAR 控制的技术数据、CMMC 范围内的国防承包商材料、律师-客户特权法律文件以及 PCI-DSS 范围内的财务记录,本地部署架构完全将第三方数据处理者类别从合规范围中移除。 无需协商商业伙伴协议,无需签署数据处理协议,也无需审查谷歌数据保留政策。 IronOCR文档中心涵盖了 Docker、Linux、Azure 和 AWS 环境的部署配置,这些环境都适用数据驻留要求。
常见问题解答
我为什么要从 Google Cloud Vision API 迁移到 IronOCR?
常见的驱动因素包括消除 COM 互操作的复杂性、取代基于文件的许可证管理、避免按页计费、启用 Docker/容器部署以及采用与标准 .NET 工具集成的 NuGet 原生工作流程。
从 Google Cloud Vision API 迁移到 IronOCR 时,主要的代码变更有哪些?
将 Google Cloud Vision 初始化序列替换为 IronTesseract 实例化,移除 COM 生命周期管理(显式的创建/加载/关闭模式),并更新结果属性名称。这样可以显著减少样板代码行数。
我该如何安装 IronOCR 以开始迁移?
在软件包管理器控制台中运行“Install-Package IronOcr”,或在命令行界面中运行“dotnet add package IronOcr”。语言包是单独的软件包:例如,法语语言包需要运行“dotnet add package IronOcr.Languages.French”。
IronOCR 对标准商业文档的 OCR 准确率是否能与 Google Cloud Vision API 相媲美?
IronOCR 对标准商业内容(包括发票、合同、收据和打印表格)的识别准确率很高。图像预处理滤镜(例如去倾斜、去噪、对比度增强)可进一步提高对质量较差输入文件的识别率。
IronOCR 如何处理 Google Cloud Vision API 单独安装的语言数据?
IronOCR 中的语言数据以 NuGet 包的形式分发。“dotnet add package IronOcr.Languages.German”命令用于安装德语支持。无需手动放置文件或指定目录路径。
从 Google Cloud Vision API 迁移到 IronOCR 是否需要更改部署基础架构?
IronOCR 所需的架构变更比 Google Cloud Vision API 少。它无需 SDK 二进制文件路径、许可证文件放置位置或许可证服务器配置。NuGet 包包含完整的 OCR 引擎,许可证密钥是应用程序代码中设置的一个字符串。
迁移后如何配置 IronOCR 许可?
在应用程序启动代码中,将 IronOcr.License.LicenseKey 赋值为 "YOUR-KEY"。在 Docker 或 Kubernetes 中,将此密钥存储为环境变量,并在启动时读取。使用 License.IsValidLicense 在接受流量之前进行验证。
IronOCR 能否像 Google Cloud Vision 那样处理 PDF 文件?
是的。IronOCR 可以读取原生 PDF 和扫描版 PDF。实例化 IronTesseract,调用 ocr.Read(input) 方法(其中 input 可以是 PDF 路径或 OcrPdfInput),然后遍历 OcrResult 页面。无需单独的 PDF 渲染流程。
IronOCR 如何处理大批量处理中的线程问题?
IronTesseract 可以安全地按线程实例化。在 Parallel.ForEach 或 Task 池中为每个线程启动一个实例,并发运行 OCR,并在完成后释放每个实例。无需全局状态或锁定。
IronOCR在提取文本后支持哪些输出格式?
IronOCR 返回结构化结果,包括文本、词坐标、置信度评分和页面结构。导出选项包括纯文本、可搜索 PDF 和用于下游处理的结构化结果对象。
IronOCR 的定价是否比 Google Cloud Vision API 在扩展工作负载方面更具可预测性?
IronOCR采用永久统一费率许可模式,不收取任何页数或数量费用。无论您处理1万页还是1000万页,许可费用都保持不变。批量许可和团队许可选项请参见IronOCR定价页面。
从 Google Cloud Vision API 迁移到 IronOCR 后,我现有的测试会发生什么变化?
迁移后,对提取的文本内容进行断言的测试应该继续通过。验证 API 调用模式或 COM 对象生命周期的测试需要更新,以反映 IronOCR 更简化的初始化和结果模型。

