从 Azure 计算机视觉 OCR 迁移到
本指南将引导.NET开发人员使用IronOCR (一个本地 OCR 库,无需云基础架构即可在本地处理文档)替换 Azure 计算机视觉 OCR。 它涵盖了交换NuGet包和命名空间的机械步骤,将 Azure 异步轮询模型转换为同步本地调用,以及处理迁移过程中最需要关注的特定模式——依赖注入连接、表单识别器轮询循环、多页 TIFF 处理和批处理吞吐量。
为什么要从 Azure 计算机视觉 OCR 迁移
移民的理由并非抽象的。 团队通常在生产环境中遇到一个或多个 Azure 计算机视觉的具体操作问题后才会做出决定。
端点和 API 密钥管理永无止境。每个部署环境(开发、测试、生产、灾难恢复)都需要一个已预配的 Azure 认知服务资源、一个端点 URL 和至少一个 API 密钥。 钥匙必须轮换使用。 当资源迁移到其他区域时,端点也会发生变化。 每个环境都需要出站防火墙规则以访问cognitiveservices.azure.com。 操作范围随着团队中每个环境和开发人员的增加而扩大。 IronOCR用一个在应用程序启动时设置一次的字符串许可证密钥取代了所有这些,没有轮换计划,也不需要出站网络。
按页计费会对多页文档造成不利影响。Azure计算机视觉会将每一页 PDF 文件都计为一次单独的交易。 一份20页的合同相当于20次计费电话。 按每 1,000 笔交易 1 美元计算,一个团队每月处理 50,000 份多页文档,平均每份文档 4 页,则会产生 200,000 笔交易——免费期结束后每月 195 美元,每年 2,340 美元。 这是在不到四个月内对IronOCR Lite ($999) 的收支平衡点,之后每额外一个页面都无需成本。
异步传播会遍历整个调用堆栈。Azure计算机视觉无法同步返回结果——云 I/O 存在网络延迟。 AnalyzeAsync上的需求强制所有调用方法都是异步的,将模式从服务层向上传播到控制器、后台工作者以及任何必须重构为适应该模式的同步代码。 Form Recognizer的基于轮询的操作加剧了这一现象:UpdateStatusAsync轮询循环。
每次调用都会将文档从网络传输出去。对于处理受 HIPAA 保护的健康信息、受 ITAR 管制的国防文件、律师与客户之间的特权通信,或任何受数据驻留规则约束的文档类别的团队而言,强制性的云传输是一种架构上的不兼容,而非权衡取舍。 Azure 计算机视觉模式没有可以避免将文档内容传输到微软数据中心的模式。
速率限制会造成吞吐量上限。Azure计算机视觉的 S1 层上限为每秒 10 笔事务。 每小时处理 3600 张图像的批量作业正好达到上限。 超过此限制将返回 HTTP 429 响应,需要在每个调用路径中使用指数退避重试逻辑。IronOCR的吞吐量上限取决于托管硬件——没有服务限制,也不需要重试基础设施。
图像OCR和PDF OCR需要两个单独的服务。标准图像OCR使用Azure.AI.Vision.ImageAnalysis。 完整的PDF处理需要Azure.AI.FormRecognizer.DocumentAnalysis——不同的NuGet包、不同的Azure资源、不同的端点和不同的结果架构。 任何同时处理图像和 PDF 的应用程序都会带来双倍的配置开销。 IronOCR使用OcrInput加载器处理这两者。
基本问题
// Azure: endpoint URL + API key + async + nested block traversal — before a single character
var client = new ImageAnalysisClient(new Uri(endpoint), new AzureKeyCredential(apiKey));
using var stream = File.OpenRead(imagePath);
var result = await client.AnalyzeAsync(BinaryData.FromStream(stream), VisualFeatures.Read);
var text = string.Join("\n", result.Value.Read.Blocks.SelectMany(b => b.Lines).Select(l => l.Text));
// IronOCR: no endpoint, no key rotation, no async, no traversal
var text = new IronTesseract().Read(imagePath).Text;' Azure: endpoint URL + API key + async + nested block traversal — before a single character
Dim client = New ImageAnalysisClient(New Uri(endpoint), New AzureKeyCredential(apiKey))
Using stream = File.OpenRead(imagePath)
Dim result = Await client.AnalyzeAsync(BinaryData.FromStream(stream), VisualFeatures.Read)
Dim text = String.Join(vbLf, result.Value.Read.Blocks.SelectMany(Function(b) b.Lines).Select(Function(l) l.Text))
End Using
' IronOCR: no endpoint, no key rotation, no async, no traversal
Dim text = New IronTesseract().Read(imagePath).TextIronOCR与 Azure 计算机视觉 OCR:功能对比
下表列出了与评估此迁移方案的团队最相关的功能。
| 特征 | Azure 计算机视觉 OCR | IronOCR |
|---|---|---|
| 加工地点 | 微软 Azure 云 | 本地,现场 |
| 需要互联网连接 | 是的,每个请求 | 否 |
| 需要 Azure 订阅 | 是 | 否 |
| 定价模式 | 每笔交易(每1000美元1.00美元) | 永久许可证(来自$999) |
| 多页PDF按页计费 | 是的——每页=1笔交易 | 无每页费用 |
| 免费套餐 | 每月 5,000 笔交易 | 试用模式(带水印) |
| 图像 OCR API | AnalyzeAsync(仅异步) | Read()(同步) |
| PDF OCR | 独立表单识别器服务 | 内置的,相同的Read()调用 |
| 受密码保护的PDF | 通过表单识别器 | input.LoadPdf(path, Password: "x") |
| 可搜索的 PDF 输出 | 手工建造 | result.SaveAsSearchablePdf() |
| 多页 TIFF 文件 | 不支持 | input.LoadImageFrames() |
| 自动图像预处理 | 服务器端不透明,不可配置。 | 校正倾斜、降噪、增强对比度、二值化、锐化、缩放 |
| 深度降噪 | 否 | input.DeepCleanBackgroundNoise() |
| OCR过程中的条形码读取 | 独立图像分析功能 | ocr.Configuration.ReadBarCodes = true |
| 基于区域的OCR | 不是直接裁剪(上传前需要手动裁剪) | OcrInput上 |
| 速率限制 | S1级10 TPS | 仅限硬件限制 |
| 需要重试逻辑 | 是的(HTTP 429、5xx) | 否 |
| 气隙部署 | 不可能 | 完全支持 |
| 支持的语言 | 164+(服务器管理) | 125+(NuGet语言包) |
| 多语言同步 | 是 | 是的(OcrLanguage.French + OcrLanguage.German) |
| 单词边界框 | 多边形(顶点数可变) | 矩形(x,y,宽度,高度) |
| 置信度评分 | 每字浮动值(0.0–1.0) | 单字评分和总分(0-100 分制) |
| hOCR导出 | 否 | result.SaveAsHocrFile() |
| 结构化输出层次结构 | 方块/线条/文字 | 页数/段落数/行数/字数/字符数 |
| .NET兼容性 | .NET Standard 2.0+ | .NET Framework 4.6.2+、. .NET Core、 .NET 5–9 |
| 跨平台 | Windows、Linux、macOS(通过云端) | Windows、Linux、macOS、Docker、ARM64 |
| 商业支持 | Azure 支持计划 | IronOCR支持包含在许可证中 |
快速入门:Azure 计算机视觉 OCR 到IronOCR 的迁移
步骤 1:替换 NuGet 软件包
移除 Azure 计算机视觉软件包:
dotnet remove package Azure.AI.Vision.ImageAnalysis
如果项目还使用了 Form Recognizer 进行 PDF 处理,也请移除该软件包:
dotnet remove package Azure.AI.FormRecognizer
从NuGet安装IronOCR :
步骤 2:更新命名空间
// Before (Azure Computer Vision)
using Azure;
using Azure.AI.Vision.ImageAnalysis;
// For PDF processing:
// using Azure.AI.FormRecognizer.DocumentAnalysis;
// After (IronOCR)
using IronOcr;' Before (Azure Computer Vision)
Imports Azure
Imports Azure.AI.Vision.ImageAnalysis
' For PDF processing:
' Imports Azure.AI.FormRecognizer.DocumentAnalysis
' After (IronOCR)
Imports IronOcr步骤 3:初始化许可证
在应用程序启动时,在任何 OCR 调用之前,添加一次许可证密钥:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"在生产环境中,将密钥存储在环境变量中:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");Imports System
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")代码迁移示例
替换依赖注入的 Azure 客户端配置
遵循推荐的Azure SDK模式的团队在DI容器中使用ImageAnalysisClient。 此接线从appsettings.json中提取端点URL和API密钥,并要求在每个部署环境中进行出站网络配置。
Azure计算机视觉方法:
// appsettings.json binds to this class
public class AzureComputerVisionOptions
{
public string Endpoint { get; set; } // "https://your-resource.cognitiveservices.azure.com/"
public string ApiKey { get; set; } // rotated periodically
}
// Program.cs / Startup.cs
services.Configure<AzureComputerVisionOptions>(
configuration.GetSection("AzureComputerVision"));
services.AddSingleton<ImageAnalysisClient>(sp =>
{
var opts = sp.GetRequiredService<IOptions<AzureComputerVisionOptions>>().Value;
return new ImageAnalysisClient(
new Uri(opts.Endpoint),
new AzureKeyCredential(opts.ApiKey));
});
services.AddScoped<IOcrService, AzureOcrService>();' appsettings.json binds to this class
Public Class AzureComputerVisionOptions
Public Property Endpoint As String ' "https://your-resource.cognitiveservices.azure.com/"
Public Property ApiKey As String ' rotated periodically
End Class
' Program.vb / Startup.vb
services.Configure(Of AzureComputerVisionOptions)(
configuration.GetSection("AzureComputerVision"))
services.AddSingleton(Of ImageAnalysisClient)(Function(sp)
Dim opts = sp.GetRequiredService(Of IOptions(Of AzureComputerVisionOptions))().Value
Return New ImageAnalysisClient(
New Uri(opts.Endpoint),
New AzureKeyCredential(opts.ApiKey))
End Function)
services.AddScoped(Of IOcrService, AzureOcrService)()// AzureOcrService.cs
public class AzureOcrService : IOcrService
{
private readonly ImageAnalysisClient _client;
public AzureOcrService(ImageAnalysisClient client)
{
_client = client;
}
public async Task<string> ReadAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var data = BinaryData.FromStream(stream);
var result = await _client.AnalyzeAsync(data, VisualFeatures.Read);
return string.Join("\n",
result.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
}
}Public Class AzureOcrService
Implements IOcrService
Private ReadOnly _client As ImageAnalysisClient
Public Sub New(client As ImageAnalysisClient)
_client = client
End Sub
Public Async Function ReadAsync(imagePath As String) As Task(Of String) Implements IOcrService.ReadAsync
Using stream = File.OpenRead(imagePath)
Dim data = BinaryData.FromStream(stream)
Dim result = Await _client.AnalyzeAsync(data, VisualFeatures.Read)
Return String.Join(vbLf, result.Value.Read.Blocks.SelectMany(Function(b) b.Lines).Select(Function(l) l.Text))
End Using
End Function
End ClassIronOCR方法:
// Program.cs / Startup.cs
// One-time license key — no endpoint, no credential class, no options binding
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// Register IronTesseract as a singleton — it is thread-safe
services.AddSingleton<IronTesseract>();
services.AddScoped<IOcrService, IronOcrService>();' Program.vb / Startup.vb
' One-time license key — no endpoint, no credential class, no options binding
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
' Register IronTesseract as a singleton — it is thread-safe
services.AddSingleton(Of IronTesseract)()
services.AddScoped(Of IOcrService, IronOcrService)()// IronOcrService.cs
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr)
{
_ocr = ocr;
}
public string Read(string imagePath)
{
return _ocr.Read(imagePath).Text;
}
}' IronOcrService.vb
Public Class IronOcrService
Implements IOcrService
Private ReadOnly _ocr As IronTesseract
Public Sub New(ByVal ocr As IronTesseract)
_ocr = ocr
End Sub
Public Function Read(ByVal imagePath As String) As String Implements IOcrService.Read
Return _ocr.Read(imagePath).Text
End Function
End ClassDI接线从两个配置类(选项+客户端工厂)减少到一个AddSingleton<IronTesseract>()调用。 删除了用于appsettings.json Azure部分、密钥库引用和出站防火墙规则。 有关单例实例上可用的配置选项,请参阅IronTesseract 设置指南。
消除表单识别器轮询循环
Form Recognizer的LongRunningOperation。 WaitUntil.Completed阻止调用线程,直到云作业完成——通常为每份文档2–10秒。 对于非阻塞行为,团队编写一个UpdateStatusAsync轮询循环,在两次轮询之间添加延迟,增加30–50行没有OCR逻辑的基础设施代码。
Azure计算机视觉方法:
// DocumentAnalysisClient — separate from ImageAnalysisClient, separate resource
public class FormRecognizerPdfService
{
private readonly DocumentAnalysisClient _client;
public FormRecognizerPdfService(string endpoint, string apiKey)
{
_client = new DocumentAnalysisClient(
new Uri(endpoint),
new AzureKeyCredential(apiKey));
}
// Blocking wait — thread is held for the duration of cloud processing
public async Task<string> ExtractPdfTextBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Completed, // blocks until Azure finishes
"prebuilt-read",
stream);
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content); // .Content, not .Text
}
}
return sb.ToString();
}
// True async — manual polling loop required
public async Task<string> ExtractPdfTextNonBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Started, // returns immediately, not complete yet
"prebuilt-read",
stream);
// Poll every 500ms until the operation finishes
while (!operation.HasCompleted)
{
await Task.Delay(500);
await operation.UpdateStatusAsync();
}
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content);
}
}
return sb.ToString();
}
}Public Class FormRecognizerPdfService
Private ReadOnly _client As DocumentAnalysisClient
Public Sub New(endpoint As String, apiKey As String)
_client = New DocumentAnalysisClient(
New Uri(endpoint),
New AzureKeyCredential(apiKey))
End Sub
' Blocking wait — thread is held for the duration of cloud processing
Public Async Function ExtractPdfTextBlocking(pdfPath As String) As Task(Of String)
Using stream = File.OpenRead(pdfPath)
Dim operation = Await _client.AnalyzeDocumentAsync(
WaitUntil.Completed, ' blocks until Azure finishes
"prebuilt-read",
stream)
Dim docResult = operation.Value
Dim sb = New StringBuilder()
For Each page In docResult.Pages
For Each line In page.Lines
sb.AppendLine(line.Content) ' .Content, not .Text
Next
Next
Return sb.ToString()
End Using
End Function
' True async — manual polling loop required
Public Async Function ExtractPdfTextNonBlocking(pdfPath As String) As Task(Of String)
Using stream = File.OpenRead(pdfPath)
Dim operation = Await _client.AnalyzeDocumentAsync(
WaitUntil.Started, ' returns immediately, not complete yet
"prebuilt-read",
stream)
' Poll every 500ms until the operation finishes
While Not operation.HasCompleted
Await Task.Delay(500)
Await operation.UpdateStatusAsync()
End While
Dim docResult = operation.Value
Dim sb = New StringBuilder()
For Each page In docResult.Pages
For Each line In page.Lines
sb.AppendLine(line.Content)
Next
Next
Return sb.ToString()
End Using
End Function
End ClassIronOCR方法:
// One class handles both images and PDFs — no second client or second resource
public class IronOcrDocumentService
{
private readonly IronTesseract _ocr;
public IronOcrDocumentService(IronTesseract ocr)
{
_ocr = ocr;
}
// Synchronous — returns immediately when local processing completes
public string ExtractPdfText(string pdfPath)
{
using var input = new OcrInput();
input.LoadPdf(pdfPath);
return _ocr.Read(input).Text;
}
// Specific page range — no per-page billing penalty
public string ExtractPageRange(string pdfPath, int startPage, int endPage)
{
using var input = new OcrInput();
input.LoadPdfPages(pdfPath, startPage, endPage);
return _ocr.Read(input).Text;
}
// If an async signature is required by an interface or controller
public Task<string> ExtractPdfTextAsync(string pdfPath)
{
return Task.Run(() => ExtractPdfText(pdfPath));
}
}Public Class IronOcrDocumentService
Private ReadOnly _ocr As IronTesseract
Public Sub New(ocr As IronTesseract)
_ocr = ocr
End Sub
' Synchronous — returns immediately when local processing completes
Public Function ExtractPdfText(pdfPath As String) As String
Using input As New OcrInput()
input.LoadPdf(pdfPath)
Return _ocr.Read(input).Text
End Using
End Function
' Specific page range — no per-page billing penalty
Public Function ExtractPageRange(pdfPath As String, startPage As Integer, endPage As Integer) As String
Using input As New OcrInput()
input.LoadPdfPages(pdfPath, startPage, endPage)
Return _ocr.Read(input).Text
End Using
End Function
' If an async signature is required by an interface or controller
Public Function ExtractPdfTextAsync(pdfPath As String) As Task(Of String)
Return Task.Run(Function() ExtractPdfText(pdfPath))
End Function
End Class轮询循环及其延迟逻辑完全消失了。 LoadPdfPages处理页面范围选择——没有单独的每页调用,没有事务计数。 PDF 输入指南详细介绍了流输入、字节数组加载和页面范围参数。
将 Azure 词级结果映射到IronOCR结构化输出
Azure 计算机视觉将单词边界框作为多边形返回,顶点数可变——通常为四个,但不保证。 结果层次结构是float。 读取单词位置的代码必须处理多边形顶点数组,并对任何阈值比较的置信度尺度进行归一化。
Azure计算机视觉方法:
public async Task<List<WordResult>> ExtractWordPositionsAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var imageData = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
var words = new List<WordResult>();
foreach (var block in response.Value.Read.Blocks)
{
foreach (var line in block.Lines)
{
foreach (var word in line.Words)
{
// BoundingPolygon is a list of ImagePoint — variable vertex count
var polygon = word.BoundingPolygon;
int minX = polygon.Min(p => p.X);
int minY = polygon.Min(p => p.Y);
int maxX = polygon.Max(p => p.X);
int maxY = polygon.Max(p => p.Y);
words.Add(new WordResult
{
Text = word.Text,
// Azure confidence: 0.0 to 1.0 — multiply by 100 for comparison
Confidence = (double)word.Confidence * 100.0,
X = minX,
Y = minY,
Width = maxX - minX,
Height = maxY - minY
});
}
}
}
return words;
}
public record WordResult(string Text, double Confidence, int X, int Y, int Width, int Height);Public Async Function ExtractWordPositionsAsync(imagePath As String) As Task(Of List(Of WordResult))
Using stream = File.OpenRead(imagePath)
Dim imageData = BinaryData.FromStream(stream)
Dim response = Await _client.AnalyzeAsync(imageData, VisualFeatures.Read)
Dim words = New List(Of WordResult)()
For Each block In response.Value.Read.Blocks
For Each line In block.Lines
For Each word In line.Words
' BoundingPolygon is a list of ImagePoint — variable vertex count
Dim polygon = word.BoundingPolygon
Dim minX As Integer = polygon.Min(Function(p) p.X)
Dim minY As Integer = polygon.Min(Function(p) p.Y)
Dim maxX As Integer = polygon.Max(Function(p) p.X)
Dim maxY As Integer = polygon.Max(Function(p) p.Y)
words.Add(New WordResult With {
.Text = word.Text,
' Azure confidence: 0.0 to 1.0 — multiply by 100 for comparison
.Confidence = CDbl(word.Confidence) * 100.0,
.X = minX,
.Y = minY,
.Width = maxX - minX,
.Height = maxY - minY
})
Next
Next
Next
Return words
End Using
End Function
Public Record WordResult(Text As String, Confidence As Double, X As Integer, Y As Integer, Width As Integer, Height As Integer)IronOCR方法:
public List<WordResult> ExtractWordPositions(string imagePath)
{
var result = new IronTesseract().Read(imagePath);
var words = new List<WordResult>();
foreach (var page in result.Pages)
{
foreach (var line in page.Lines)
{
foreach (var word in line.Words)
{
// Rectangle-based bounding box — no polygon math required
// Confidence is already 0–100, matching the converted Azure scale
words.Add(new WordResult
{
Text = word.Text,
Confidence = word.Confidence, // 0–100, no conversion needed
X = word.X,
Y = word.Y,
Width = word.Width,
Height = word.Height
});
}
}
}
return words;
}
// Filter to only high-confidence words — common post-processing pattern
public IEnumerable<string> ExtractHighConfidenceWords(string imagePath, double threshold = 80.0)
{
var result = new IronTesseract().Read(imagePath);
return result.Words
.Where(w => w.Confidence >= threshold)
.Select(w => w.Text);
}
public record WordResult(string Text, double Confidence, int X, int Y, int Width, int Height);Public Function ExtractWordPositions(ByVal imagePath As String) As List(Of WordResult)
Dim result = New IronTesseract().Read(imagePath)
Dim words = New List(Of WordResult)()
For Each page In result.Pages
For Each line In page.Lines
For Each word In line.Words
' Rectangle-based bounding box — no polygon math required
' Confidence is already 0–100, matching the converted Azure scale
words.Add(New WordResult With {
.Text = word.Text,
.Confidence = word.Confidence, ' 0–100, no conversion needed
.X = word.X,
.Y = word.Y,
.Width = word.Width,
.Height = word.Height
})
Next
Next
Next
Return words
End Function
' Filter to only high-confidence words — common post-processing pattern
Public Function ExtractHighConfidenceWords(ByVal imagePath As String, Optional ByVal threshold As Double = 80.0) As IEnumerable(Of String)
Dim result = New IronTesseract().Read(imagePath)
Return result.Words _
.Where(Function(w) w.Confidence >= threshold) _
.Select(Function(w) w.Text)
End Function
Public Class WordResult
Public Property Text As String
Public Property Confidence As Double
Public Property X As Integer
Public Property Y As Integer
Public Property Width As Integer
Public Property Height As Integer
Public Sub New(ByVal text As String, ByVal confidence As Double, ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer)
Me.Text = text
Me.Confidence = confidence
Me.X = x
Me.Y = y
Me.Width = width
Me.Height = height
End Sub
End Class多边形到矩形的转换消失了。 将 Azure 0.0–1.0 值乘以 100 后,置信度值即可直接匹配——任何现有的阈值逻辑都需要进行这一调整。 结构化数据输出指南记录了完整的层次结构和坐标属性。 有关置信度评分模型的具体信息,请参阅置信度评分指南。
无需云上传即可处理多页 TIFF 文件
Azure Computer Vision的ImageAnalysisClient接受单个图像。 多帧 TIFF 文件(常见于文档扫描工作流程、传真存档和医学成像流程)需要在上传前将 TIFF 文件拆分为单独的图像(每帧一个事务),或者切换到具有单独配置的表单识别器。 两条路都不平坦。
Azure计算机视觉方法:
// Azure does not support multi-frame TIFF directly via ImageAnalysisClient
// Must split frames manually and upload each as a separate transaction
public async Task<string> ExtractMultiFrameTiffAsync(string tiffPath)
{
// Load TIFF using System.Drawing or a third-party library
using var bitmap = new System.Drawing.Bitmap(tiffPath);
int frameCount = bitmap.GetFrameCount(
System.Drawing.Imaging.FrameDimension.Page);
var allText = new StringBuilder();
for (int i = 0; i < frameCount; i++)
{
// Select frame, save to temporary PNG, upload to Azure
bitmap.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
using var ms = new MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Position = 0;
// Each frame = 1 Azure transaction = $0.001
var imageData = BinaryData.FromStream(ms);
var result = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
foreach (var block in result.Value.Read.Blocks)
foreach (var line in block.Lines)
allText.AppendLine(line.Text);
}
return allText.ToString();
}Public Async Function ExtractMultiFrameTiffAsync(tiffPath As String) As Task(Of String)
' Load TIFF using System.Drawing or a third-party library
Using bitmap As New System.Drawing.Bitmap(tiffPath)
Dim frameCount As Integer = bitmap.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page)
Dim allText As New StringBuilder()
For i As Integer = 0 To frameCount - 1
' Select frame, save to temporary PNG, upload to Azure
bitmap.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i)
Using ms As New MemoryStream()
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png)
ms.Position = 0
' Each frame = 1 Azure transaction = $0.001
Dim imageData As BinaryData = BinaryData.FromStream(ms)
Dim result = Await _client.AnalyzeAsync(imageData, VisualFeatures.Read)
For Each block In result.Value.Read.Blocks
For Each line In block.Lines
allText.AppendLine(line.Text)
Next
Next
End Using
Next
Return allText.ToString()
End Using
End FunctionIronOCR方法:
//IronOCRhandles multi-frame TIFF natively — single call, no frame splitting
public string ExtractMultiFrameTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // all frames loaded automatically
var result = new IronTesseract().Read(input);
return result.Text;
}
// Access per-page data for frame-level reporting
public void ExtractTiffWithPageStats(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
var ocr = new IronTesseract();
var result = ocr.Read(input);
Console.WriteLine($"Total frames processed: {result.Pages.Length}");
foreach (var page in result.Pages)
{
Console.WriteLine($"Frame {page.PageNumber}: " +
$"{page.Words.Length} words, " +
$"confidence {page.Confidence:F1}%");
}
}
// Combine with preprocessing for scanned TIFF archives
public string ExtractLowQualityTiffArchive(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
input.Deskew();
input.DeNoise();
input.Contrast();
var result = new IronTesseract().Read(input);
return result.Text;
}'IronOCR handles multi-frame TIFF natively — single call, no frame splitting
Public Function ExtractMultiFrameTiff(tiffPath As String) As String
Using input = New OcrInput()
input.LoadImageFrames(tiffPath) ' all frames loaded automatically
Dim result = New IronTesseract().Read(input)
Return result.Text
End Using
End Function
' Access per-page data for frame-level reporting
Public Sub ExtractTiffWithPageStats(tiffPath As String)
Using input = New OcrInput()
input.LoadImageFrames(tiffPath)
Dim ocr = New IronTesseract()
Dim result = ocr.Read(input)
Console.WriteLine($"Total frames processed: {result.Pages.Length}")
For Each page In result.Pages
Console.WriteLine($"Frame {page.PageNumber}: " +
$"{page.Words.Length} words, " +
$"confidence {page.Confidence:F1}%")
Next
End Using
End Sub
' Combine with preprocessing for scanned TIFF archives
Public Function ExtractLowQualityTiffArchive(tiffPath As String) As String
Using input = New OcrInput()
input.LoadImageFrames(tiffPath)
input.Deskew()
input.DeNoise()
input.Contrast()
Dim result = New IronTesseract().Read(input)
Return result.Text
End Using
End Function每帧交易成本降至零。 彻底消除了System.Drawing帧提取循环及其临时PNG序列化步骤。 对于文档质量参差不齐的传真存档工作流程, TIFF 和 GIF 输入指南涵盖帧选择选项,图像质量校正指南记录了完整的预处理滤镜集。
无速率限制排队的并行批处理
Azure 计算机视觉的 S1 层将吞吐量限制为每秒 10 个事务。 超过此速率的批量作业将收到 HTTP 429 响应。 生产实现需要一个速率限制包装器、信号量或排队层,以保持在限制范围内。IronOCR API设计为线程安全——每个线程创建一个Parallel.ForEach。
Azure计算机视觉方法:
// Must throttle to 10 TPS to avoid 429 errors on S1 tier
public class ThrottledAzureBatchProcessor
{
private readonly ImageAnalysisClient _client;
// Semaphore limits concurrent Azure calls to stay under 10 TPS
private readonly SemaphoreSlim _throttle = new SemaphoreSlim(10, 10);
public async Task<Dictionary<string, string>> ProcessBatchAsync(
IEnumerable<string> imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
var tasks = imagePaths.Select(async path =>
{
await _throttle.WaitAsync();
try
{
using var stream = File.OpenRead(path);
var data = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(data, VisualFeatures.Read);
var text = string.Join("\n",
response.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
results[path] = text;
// Respect 1-second window for the 10 TPS ceiling
await Task.Delay(100);
}
catch (RequestFailedException ex) when (ex.Status == 429)
{
// Rate limited despite throttling — back off and retry
await Task.Delay(2000);
// Re-queue or log failure — simplified here
results[path] = string.Empty;
}
finally
{
_throttle.Release();
}
});
await Task.WhenAll(tasks);
return new Dictionary<string, string>(results);
}
}' Must throttle to 10 TPS to avoid 429 errors on S1 tier
Public Class ThrottledAzureBatchProcessor
Private ReadOnly _client As ImageAnalysisClient
' Semaphore limits concurrent Azure calls to stay under 10 TPS
Private ReadOnly _throttle As New SemaphoreSlim(10, 10)
Public Async Function ProcessBatchAsync(imagePaths As IEnumerable(Of String)) As Task(Of Dictionary(Of String, String))
Dim results = New ConcurrentDictionary(Of String, String)()
Dim tasks = imagePaths.Select(Async Function(path)
Await _throttle.WaitAsync()
Try
Using stream = File.OpenRead(path)
Dim data = BinaryData.FromStream(stream)
Dim response = Await _client.AnalyzeAsync(data, VisualFeatures.Read)
Dim text = String.Join(vbLf, response.Value.Read.Blocks _
.SelectMany(Function(b) b.Lines) _
.Select(Function(l) l.Text))
results(path) = text
' Respect 1-second window for the 10 TPS ceiling
Await Task.Delay(100)
End Using
Catch ex As RequestFailedException When ex.Status = 429
' Rate limited despite throttling — back off and retry
Await Task.Delay(2000)
' Re-queue or log failure — simplified here
results(path) = String.Empty
Finally
_throttle.Release()
End Try
End Function)
Await Task.WhenAll(tasks)
Return New Dictionary(Of String, String)(results)
End Function
End ClassIronOCR方法:
// 否 rate limiting needed — throughput is hardware-bound
public Dictionary<string, string> ProcessBatch(IEnumerable<string> imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
Parallel.ForEach(imagePaths, imagePath =>
{
// Each thread gets its own IronTesseract instance — fully thread-safe
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
});
return new Dictionary<string, string>(results);
}
// With controlled parallelism for memory-constrained environments
public Dictionary<string, string> ProcessBatchControlled(
IEnumerable<string> imagePaths, int maxDegreeOfParallelism = 4)
{
var results = new ConcurrentDictionary<string, string>();
var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism };
Parallel.ForEach(imagePaths, options, imagePath =>
{
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
});
return new Dictionary<string, string>(results);
}' 否 rate limiting needed — throughput is hardware-bound
Public Function ProcessBatch(imagePaths As IEnumerable(Of String)) As Dictionary(Of String, String)
Dim results As New ConcurrentDictionary(Of String, String)()
Parallel.ForEach(imagePaths, Sub(imagePath)
' Each thread gets its own IronTesseract instance — fully thread-safe
Dim ocr As New IronTesseract()
Dim result = ocr.Read(imagePath)
results(imagePath) = result.Text
End Sub)
Return New Dictionary(Of String, String)(results)
End Function
' With controlled parallelism for memory-constrained environments
Public Function ProcessBatchControlled(imagePaths As IEnumerable(Of String), Optional maxDegreeOfParallelism As Integer = 4) As Dictionary(Of String, String)
Dim results As New ConcurrentDictionary(Of String, String)()
Dim options As New ParallelOptions With {.MaxDegreeOfParallelism = maxDegreeOfParallelism}
Parallel.ForEach(imagePaths, options, Sub(imagePath)
Dim ocr As New IronTesseract()
Dim result = ocr.Read(imagePath)
results(imagePath) = result.Text
End Sub)
Return New Dictionary(Of String, String)(results)
End Function信号量、100毫秒延迟和HTTP 429捕获块都被移除。 并行性仅受 CPU 核心数和可用内存的限制,而不受服务层级的限制。 多线程示例通过计时比较展示了完整的模式,速度优化指南涵盖了批量工作负载的引擎配置调整。
预处理 Azure 拒绝的低质量扫描
Azure 计算机视觉执行服务器端图像增强,但它是不透明的且不可配置的。 文档如果倾斜度过大、噪声过大或对比度过低,则会返回置信度低的结果或空白文本,且无法进行干预。 IronOCR直接在OcrInput上公开预处理管道。
Azure计算机视觉方法:
// 否 client-side preprocessing API — must preprocess externally before upload
public async Task<string> ExtractFromLowQualityScanAsync(string imagePath)
{
// Option 1: Accept whatever Azure returns (may be empty or low-quality)
using var stream = File.OpenRead(imagePath);
var imageData = BinaryData.FromStream(stream);
var result = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
// 否 way to know if the server applied enhancement
// 否 confidence on the overall result — only per-word
var text = string.Join("\n",
result.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
if (string.IsNullOrWhiteSpace(text))
{
// Option 2: Apply external preprocessing using System.Drawing, SkiaSharp,
// or ImageMagick, re-serialize to stream, re-upload — second billable transaction
throw new Exception("Azure returned empty result; manual preprocessing needed");
}
return text;
}Public Async Function ExtractFromLowQualityScanAsync(imagePath As String) As Task(Of String)
' Option 1: Accept whatever Azure returns (may be empty or low-quality)
Using stream = File.OpenRead(imagePath)
Dim imageData = BinaryData.FromStream(stream)
Dim result = Await _client.AnalyzeAsync(imageData, VisualFeatures.Read)
' 否 way to know if the server applied enhancement
' 否 confidence on the overall result — only per-word
Dim text = String.Join(vbCrLf, result.Value.Read.Blocks.SelectMany(Function(b) b.Lines).Select(Function(l) l.Text))
If String.IsNullOrWhiteSpace(text) Then
' Option 2: Apply external preprocessing using System.Drawing, SkiaSharp,
' or ImageMagick, re-serialize to stream, re-upload — second billable transaction
Throw New Exception("Azure returned empty result; manual preprocessing needed")
End If
Return text
End Using
End FunctionIronOCR方法:
// Preprocessing is part of the same call — no re-upload, no second transaction
public string ExtractFromLowQualityScan(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath);
// Correct common scanning defects before OCR
input.Deskew(); // Fix rotated documents
input.DeNoise(); // Remove scanner noise
input.Contrast(); // Improve contrast for faded documents
input.Binarize(); // Convert to black and white
var ocr = new IronTesseract();
var result = ocr.Read(input);
Console.WriteLine($"Confidence after preprocessing: {result.Confidence:F1}%");
return result.Text;
}
// For severely degraded documents
public string ExtractFromDegradedDocument(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath);
input.DeepCleanBackgroundNoise(); // Deep learning-based noise removal
input.Deskew();
input.Scale(150); // Upscale for better character resolution
var result = new IronTesseract().Read(input);
return result.Text;
}Public Function ExtractFromLowQualityScan(imagePath As String) As String
Using input = New OcrInput()
input.LoadImage(imagePath)
' Correct common scanning defects before OCR
input.Deskew() ' Fix rotated documents
input.DeNoise() ' Remove scanner noise
input.Contrast() ' Improve contrast for faded documents
input.Binarize() ' Convert to black and white
Dim ocr = New IronTesseract()
Dim result = ocr.Read(input)
Console.WriteLine($"Confidence after preprocessing: {result.Confidence:F1}%")
Return result.Text
End Using
End Function
' For severely degraded documents
Public Function ExtractFromDegradedDocument(imagePath As String) As String
Using input = New OcrInput()
input.LoadImage(imagePath)
input.DeepCleanBackgroundNoise() ' Deep learning-based noise removal
input.Deskew()
input.Scale(150) ' Upscale for better character resolution
Dim result = New IronTesseract().Read(input)
Return result.Text
End Using
End Function外部预处理依赖,如System.Drawing、SkiaSharp或ImageMagick,被移除。 重新上传和二次交易费用消失了。 预处理管道是OcrInput生命周期的一部分,因此在OCR引擎看到图像之前就应用。 图像滤镜教程将逐一介绍每个滤镜,并进行滤镜前后精度对比。
Azure 计算机视觉 OCR API 到IronOCR映射参考
| Azure计算机视觉 | IronOCR当量 |
|---|---|
ImageAnalysisClient | IronTesseract |
new AzureKeyCredential(apiKey) | IronOcr.License.LicenseKey = key |
new Uri(endpoint) | 不要求 |
client.AnalyzeAsync(data, VisualFeatures.Read) | ocr.Read(imagePath) |
BinaryData.FromStream(stream) | input.LoadImage(stream) |
BinaryData.FromBytes(bytes) | input.LoadImage(bytes) |
result.Value.Read.Blocks | result.Pages[i].Paragraphs |
block.Lines | result.Pages[i].Lines |
line.Text | line.Text |
line.Words | line.Words |
word.Text | word.Text |
word.Confidence (0.0–1.0 浮点) | word.Confidence (0–100 浮点) |
word.BoundingPolygon | word.X, word.Y, word.Width, word.Height |
DocumentAnalysisClient | IronTesseract + OcrInput |
AnalyzeDocumentAsync(WaitUntil.Completed, "prebuilt-read", stream) | ocr.Read(input) 和 input.LoadPdf(path) |
operation.Value.Pages | result.Pages |
page.Lines[i].Content | result.Lines[i].Text |
UpdateStatusAsync() 轮询循环 | 不需要 - 同步结果 |
RequestFailedException (状态 429) | 不适用——无费率限制 |
RequestFailedException (状态 5xx) | 不适用——无服务错误 |
VisualFeatures.Read 枚举标志 | 隐式——Read()始终提取文本 |
Form Recognizer prebuilt-read 模型 | 内置OCR引擎(无需选择模型) |
Azure端点URL位于appsettings.json中 | 不要求 |
| API密钥轮换程序 | 不要求 |
常见迁移问题和解决方案
问题 1:无法更改的异步接口契约
Azure Computer Vision:服务接口通常声明Task<string>返回类型,因为Azure要求异步。 调用代码、控制器和后台工作程序都编写为异步方法。 切换到IronOCR可以消除 OCR 层中异步操作的需求,但在大型代码库中更改每个接口签名并不总是可行的。
解决方案:将同步IronOCR调用包装在Task.Run中,以满足现有接口而无需级联重构:
// Existing interface — do not change it
public interface IOcrService
{
Task<string> ReadAsync(string imagePath);
}
// NewIronOCRimplementation — fulfills the contract
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr) => _ocr = ocr;
public Task<string> ReadAsync(string imagePath)
{
// Task.Run offloads to thread pool — no await chain needed
return Task.Run(() => _ocr.Read(imagePath).Text);
}
}' Existing interface — do not change it
Public Interface IOcrService
Function ReadAsync(imagePath As String) As Task(Of String)
End Interface
' NewIronOCRimplementation — fulfills the contract
Public Class IronOcrService
Implements IOcrService
Private ReadOnly _ocr As IronTesseract
Public Sub New(ocr As IronTesseract)
_ocr = ocr
End Sub
Public Function ReadAsync(imagePath As String) As Task(Of String) Implements IOcrService.ReadAsync
' Task.Run offloads to thread pool — no await chain needed
Return Task.Run(Function() _ocr.Read(imagePath).Text)
End Function
End Class这是一个有效的中间步骤。异步 OCR 指南涵盖了IronOCR内置的异步支持,适用于需要完全异步集成的场景。
问题二:置信阈值逻辑产生错误结果
Azure Computer Vision:Azure将字词置信度作为0.0至1.0的word.Confidence > 0.85f的阈值。 迁移后,这些比较结果总是为假,因为IronOCR置信度为 0-100,而不是 0-1。
解决方案:在更新筛选逻辑时,将现有的 Azure 阈值乘以 100:
// Before: Azure threshold (0.0 - 1.0 scale)
var highConfidenceWords = azureWords
.Where(w => w.Confidence > 0.85f)
.Select(w => w.Text);
// After:IronOCRthreshold (0 - 100 scale)
var result = new IronTesseract().Read(imagePath);
var highConfidenceWords = result.Words
.Where(w => w.Confidence > 85.0)
.Select(w => w.Text);
// Overall document confidence — also on 0-100 scale
if (result.Confidence < 70.0)
{
// Document may need preprocessing or manual review
}' Before: Azure threshold (0.0 - 1.0 scale)
Dim highConfidenceWords = azureWords _
.Where(Function(w) w.Confidence > 0.85F) _
.Select(Function(w) w.Text)
' After: IronOCR threshold (0 - 100 scale)
Dim result = New IronTesseract().Read(imagePath)
Dim highConfidenceWords = result.Words _
.Where(Function(w) w.Confidence > 85.0) _
.Select(Function(w) w.Text)
' Overall document confidence — also on 0-100 scale
If result.Confidence < 70.0 Then
' Document may need preprocessing or manual review
End If问题 3:表单识别器预置模型字段提取没有直接的IronOCR等效项
Azure Computer Vision:Form Recognizer预建发票和收据模型会自动提取命名字段——InvoiceDate——无需指定这些字段出现在页面上的位置。 提取逻辑嵌入在 Azure 模型中。
解决方案:用基于区域的OCR替换基于模型的字段提取,使用CropRectangle。 这需要了解文档布局,但大多数实际部署都已经使用了固定的模板:
var ocr = new IronTesseract();
// Define extraction zones for a known invoice template
var headerZone = new CropRectangle(50, 40, 400, 60);
var totalZone = new CropRectangle(350, 600, 250, 50);
var dateZone = new CropRectangle(400, 100, 200, 40);
string header, total, date;
using (var input = new OcrInput())
{
input.LoadImage("invoice.jpg", headerZone);
header = ocr.Read(input).Text.Trim();
}
using (var input = new OcrInput())
{
input.LoadImage("invoice.jpg", totalZone);
total = ocr.Read(input).Text.Trim();
}
using (var input = new OcrInput())
{
input.LoadImage("invoice.jpg", dateZone);
date = ocr.Read(input).Text.Trim();
}Dim ocr = New IronTesseract()
' Define extraction zones for a known invoice template
Dim headerZone = New CropRectangle(50, 40, 400, 60)
Dim totalZone = New CropRectangle(350, 600, 250, 50)
Dim dateZone = New CropRectangle(400, 100, 200, 40)
Dim header As String, total As String, date As String
Using input = New OcrInput()
input.LoadImage("invoice.jpg", headerZone)
header = ocr.Read(input).Text.Trim()
End Using
Using input = New OcrInput()
input.LoadImage("invoice.jpg", totalZone)
total = ocr.Read(input).Text.Trim()
End Using
Using input = New OcrInput()
input.LoadImage("invoice.jpg", dateZone)
date = ocr.Read(input).Text.Trim()
End Using基于区域的 OCR 指南涵盖坐标系统详情和多区域批处理。
问题 4:缺少 hOCR 和结构化导出
Azure计算机视觉: Azure不提供hOCR输出。 需要使用标准化布局数据进行下游文档分析工具的团队,会从 Azure 响应中手动提取边界框,并构建自己的输出格式。
解决方案: IronOCR只需一次调用即可生成符合标准的hOCR图像:
var result = new IronTesseract().Read("document.jpg");
// Write hOCR file — recognized by most document analysis tools
result.SaveAsHocrFile("document.hocr");
// Searchable PDF — alternative for archive and search indexing workflows
result.SaveAsSearchablePdf("document-searchable.pdf");Dim result = New IronTesseract().Read("document.jpg")
' Write hOCR file — recognized by most document analysis tools
result.SaveAsHocrFile("document.hocr")
' Searchable PDF — alternative for archive and search indexing workflows
result.SaveAsSearchablePdf("document-searchable.pdf")问题 5:Azure SDK 版本与其他 Azure 程序包冲突
Azure Computer Vision:使用多个Azure SDK包(Azure.Core传递依赖项之间的版本冲突。 Azure SDK 的 monorepo 版本控制策略有所帮助,但并不能消除所有冲突,尤其是在混合使用 GA 版本和预览版 SDK 时。
解决方案:移除Azure.AI.FormRecognizer消除了这些SDK包的依赖树。 如果项目仅使用Azure进行OCR,则整个Azure.*依赖集将被移除。 如果其他 Azure 服务仍然保留,减少的软件包数量会降低冲突的可能性:
# Remove only the OCR-related Azure packages
dotnet remove package Azure.AI.Vision.ImageAnalysis
dotnet remove package Azure.AI.FormRecognizer
# Verify remaining Azure packages have no new conflicts
dotnet restore
dotnet build
问题 6:扫描文档质量低于 Azure 验收阈值
Azure 计算机视觉:分辨率非常低的图像(低于 ~150 DPI)或严重倾斜的扫描图像,Azure 服务器端管道返回的文本很少或为空,并且没有关于尝试进行何种增强的反馈。 如果没有外部预处理,调用者无法改善结果。
解决方案:使用IronOCR的预处理流程在进行 OCR 之前对图像进行预处理:
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();
input.DeNoise();
input.Scale(200); // Upscale to improve character resolution
input.Contrast();
input.Sharpen();
var result = new IronTesseract().Read(input);
Console.WriteLine($"Extraction confidence: {result.Confidence:F1}%");Using input As New OcrInput()
input.LoadImage("low-quality-scan.jpg")
input.Deskew()
input.DeNoise()
input.Scale(200) ' Upscale to improve character resolution
input.Contrast()
input.Sharpen()
Dim result = New IronTesseract().Read(input)
Console.WriteLine($"Extraction confidence: {result.Confidence:F1}%")
End Using图像方向校正指南专门涵盖了倾斜和旋转检测。
Azure 计算机视觉 OCR 迁移清单
迁移前
在代码库中查找所有 Azure 计算机视觉和表单识别器的使用情况:
# Find all Azure OCR-related using statements
grep -r "Azure.AI.Vision.ImageAnalysis" --include="*.cs" .
grep -r "Azure.AI.FormRecognizer" --include="*.cs" .
grep -r "ImageAnalysisClient" --include="*.cs" .
grep -r "DocumentAnalysisClient" --include="*.cs" .
grep -r "AnalyzeAsync" --include="*.cs" .
grep -r "AnalyzeDocumentAsync" --include="*.cs" .
grep -r "VisualFeatures.Read" --include="*.cs" .
grep -r "WaitUntil.Completed" --include="*.cs" .
grep -r "UpdateStatusAsync" --include="*.cs" .
grep -r "AzureKeyCredential" --include="*.cs" .
识别包含 Azure OCR 终结点和密钥的配置文件:
grep -r "cognitiveservices.azure.com" --include="*.json" .
grep -r "AzureComputerVision\|FormRecognizer" --include="*.json" .
grep -r "ComputerVision\|FormRecognizer" --include="appsettings*.json" .
编码开始前的库存物品清单:
- 实现 Azure OCR 服务模式的类的数量
- 从 OCR 调用传播的异步方法链的数量
- 使用 Azure 0.0–1.0 标度确定任何词语置信度阈值
- 识别表单识别器预置模型用途(发票、收据、身份信息),需要基于区域进行替换
- 识别当前被拆分为逐帧上传的多帧 TIFF 输入文件
- 检查 Docker 和 CI/CD 配置,找出不再需要的出站 Azure 网络规则。
代码迁移
- 从所有使用它的项目中移除
Azure.AI.Vision.ImageAnalysisNuGet包 - 从所有使用它的项目中移除
Azure.AI.FormRecognizerNuGet包 - 在每个受影响的项目中运行
dotnet add package IronOcr - 在应用程序启动时添加
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE") - 替换
using Azure; using Azure.AI.Vision.ImageAnalysis;withusing IronOcr; - 在DI中将
services.AddSingleton<IronTesseract>()) - 移除
AzureComputerVisionOptions或等效配置类; 移除appsettings.jsonAzure OCR部分 - 将所有服务类中的
IronTesseract注入 - 将
OcrInput - 移除所有
WaitUntil.Completed模式; 将IronTesseract+OcrInput.LoadPdf() - 更新词语置信度阈值比较:将所有 Azure 0.0–1.0 值乘以 100
- 用
word.BoundingPolygon.Min/Max) - 用
input.LoadImageFrames(tiffPath)替换多帧TIFF逐帧上传循环 - 将Form Recognizer预建模型字段提取转换为基于
CropRectangle的区域OCR,用于已知的文档模板 - 移除
RequestFailedExceptiontry-catch块用于HTTP 429和5xx; 简化错误处理,仅处理文件系统和输入验证异常。
后迁移
- 验证纯文本提取输出结果是否与 Azure 的结果一致或更优,样本包含 20 多个文档。
- 确认多页 PDF 处理能够为所有页面生成文本,且监控过程中不会出现按页计费的情况
- 测试多帧 TIFF 处理返回的页数与源帧数相同
- 验证词语置信度值是否在 0-100 范围内,以及阈值比较是否正确。
- 验证单词边界框坐标与源图像坐标系是否正确对齐
- 运行批处理路径并确认其顺利完成,没有速率限制错误或信号量争用。
- 在没有出站 Internet 访问的环境中进行测试,以确认不会发生 Azure 终结点调用。
- 确认Docker和容器部署在没有
cognitiveservices.azure.com的出站防火墙规则下成功启动 - 检查DI容器构造成功,
IronTesseract单例正确解析 - 验证所有部署环境中已设置
IRONOCR_LICENSE环境变量,并且OCR生成的输出是已授权的(非水印)
迁移到IronOCR的主要优势
成本变得可预测。Azure计算机视觉的按事务计费方式持续运行。 单月高文档量就可能超过IronOCR许可证的年度费用。 迁移后,OCR预算将成为一个固定的项目。 处理量可以增长——由于业务扩展、批量重处理或临时峰值——而不产生更大的发票。IronOCR许可页面显示从$999的单一开发者许可证到$2,399用于无限开发者的分级选项。
应用程序堆栈变得更简单。移除Azure.AI.FormRecognizer消除了两个NuGet包、两个Azure资源配置、两组凭据以及整个异步轮询基础设施。 先前由于云I/O而需要async Task<string>标识的服务类变为同步方法。 错误处理的范围从网络故障、速率限制和服务可用性缩小到文件系统和输入验证。 未来使用该代码库的开发人员需要理解的代码量会更少。
文档数据仍由组织控制。迁移后,OCR 处理将在本地进行。 文档不会跨越组织边界,不会出现在 Azure 遥测数据中,也不受微软数据保留或处理策略的约束。 受 HIPAA 保护的实体、ITAR 承包商、GDPR 监管的组织以及任何有数据驻留要求的团队都可以处理文档,而无需对云第三方进行合规性审查。 Linux 部署指南和Docker 部署指南展示了如何在受限环境中部署IronOCR 。
批量吞吐量会根据硬件进行扩展。Azure S1 层 10 TPS 的上限是硬性限制,需要通过排队、限速或升级层级来突破。 迁移后,并行OCR作业可利用所有可用CPU核心,而没有任何服务施加的限制。一个四核服务器可以同时运行四个并行的IronTesseract实例。 多线程示例演示了这种模式,并显示了吞吐量随核心数量的扩展。
预处理缺陷可以通过代码解决。Azure计算机视觉的服务器端图像增强功能是一个黑盒。 当扫描返回空白或低置信度输出时,唯一的选择是接受它或在重新上传之前进行外部预处理,这会产生额外费用。IronOCR的OcrInput流水线将去倾斜、去噪、对比度、二值化、缩放、锐化和深度去噪作为一等方法公开。 有问题的扫描类型变成了可调参数。 预处理功能页面列出了完整的过滤器集,并提供了关于哪些过滤器可以解决哪些扫描缺陷的指导。
