C# 如何在 Windows 上安裝 Tesseract OCR
本指南指導.NET開發人員將Google Cloud Vision替換為IronOCR作為即插即用的內部OCR引擎。它涵蓋憑證移除、Protobuf註釋解析替換、批量註釋簡化、多頁文件處理——這四個結構性更改佔據了遷移工作的主要部分。
為什麼從Google Cloud Vision OCR遷移
遷移的決定幾乎總是從兩個認識中的一個開始:合規審核阻止雲文件傳輸,或是管理GCP憑證、GCS儲存桶、異步輪詢和每圖片計費的操作範圍變得比OCR本身更昂貴。
服務帳戶JSON密鑰生命週期。 您的每一次應用部署——開發機、CI/CD管道、預發佈伺服器、生產伺服器、Docker容器、Kubernetes Pod ——都需要同一個服務帳戶JSON密鑰文件。該文件包含一個RSA私鑰。 它絕不能進入源程式碼控制,必須按計劃輪換,必須受文件系統權限保護,並且在輪換發生時必須同時在所有環境中更新。 一個妥協的密鑰將授予API存取權限,直到在GCP控制台手動撤銷。 IronOCR用一次性設置的應用程式啟動時的單個授權密鑰字串替換了整個操作表面。
按請求計費在規模上。 每1,000張影像1.50美元,每PDF頁面0.0015美元,這使得在開發中成本是隱性的,但在生產中卻痛苦難忍。 一個每月處理200,000頁面的文件加工管道,僅API費用就需300美元每月,還不算GCS儲存費和出口成本。 那300美元每月無限期重複。 IronOCR的永久授權將OCR從計量的運營開支轉變為固定的資本項目,在第二年或第三年運行費用為零。
PDF的GCS異步管道。 Google Cloud Vision不接受PDF作為直接API輸入。 完整管道需要第二個NuGet包(Google.Cloud.Storage.V1)、一個已佈建的GCS儲存桶、一次非同步上傳、一次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運行在初始授權驗證後毫無出站網路連接的情況下。
根本問題
在運行OCR程式碼的第一行之前,Google Cloud Vision需要磁碟上的JSON密鑰文件:
// 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 Vision OCR:功能比較
下表直接映射了在為遷移建立商業案例的團隊的特性。
| 功能 | Google Cloud Vision OCR | IronOCR |
|---|---|---|
| 處理地點 | 谷歌雲(遠程) | 本地(在地) |
| 身份驗證 | 服務帳戶JSON密鑰+環境變數 | 授權密鑰字串 |
| PDF 輸入 | 上傳到GCS+異步API | input.LoadPdf()直接 |
| 受密碼保護的 PDF | 不支持 | LoadPdf(path, Password: "...") |
| 多頁TIFF輸入 | 有限 | input.LoadImageFrames() |
| 可搜尋的 PDF 輸出 | 不可用 | result.SaveAsSearchablePdf() |
| 結構化資料存取 | Protobuf: 頁面>塊>段落>單詞>符號 | result.Words(型別化.NET物件) |
| 段落文字屬性 | 否——需要符號串聯 | paragraph.Text直接屬性 |
| 置信分數 | 每符號(需要迴圈) | result.Confidence, word.Confidence |
| 自動圖像預處理 | 無(由機器學習處理) | 糾偏,去噪,對比度,二值化,銳化 |
| 基於區域的OCR | 無本地裁剪 | OcrInput |
| 條碼讀取 | 獨立的API功能 | ocr.Configuration.ReadBarCodes = true |
| 速率限制 | 每分鐘1,800次請求的預設值 | 無(CPU限制) |
| 離線 / 空隙 | 不是 | 是 |
| 按文件費用 | 每1,000張圖片1.50美元; 每PDF頁面0.0015美元 | 無(永久許可) |
| 支持的語言 | ~50 | 125+ |
| FedRAMP授權 | 未授權 | 不適用(本地) |
| HIPAA遵從路徑 | 需要商業夥伴協議 | 無第三方資料處理 |
| .NET Framework 支持 | .NET Standard 2.0+ | .NET Framework 4.6.2+ and .NET 5/6/7/8/9 |
| 需要 NuGet 套件 | 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:更新命名空間
將Google Cloud命名空間替換為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;
// 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
// No JSON files, no environment variables beyond the key, no GCP Console configuration
// No 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
// No JSON files, no environment variables beyond the key, no GCP Console configuration
// No 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
' No JSON files, no environment variables beyond the key, no GCP Console configuration
' No 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——PermissionDenied、ResourceExhausted、Unavailable、DeadlineExceeded和Unauthenticated——每一種代表不同的基礎設施故障模式。 IronOCR的故障模式是IOException(找不到檔案或檔案被鎖定)和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 Feature { Type = Feature.Types.Type.TextDetection },
new Feature { 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 Feature { Type = Feature.Types.Type.TextDetection },
new Feature { 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的Text, X, Y, Width, Confidence作為一等屬性。 讀取結果指南記錄了每個粒度級別提供的完整屬性集。
多頁TIFF處理到可搜索PDF輸出
Google Cloud Vision將多頁TIFF文件視為一系列連續的圖像,每個圖像都需要單獨的API調用。 沒有接受TIFF並為所有幀返回結構化輸出的單一API調用。 從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 Vision has 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 Vision has 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 Vision has 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 Vision has 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中的預處理管道在一個步驟中解決了最常見的質量問題。 查看TIFF和GIF輸入以及可搜尋PDF輸出以獲取完整API。
具有進度跟踪的速率限制批量遷移
在生產規模上,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;
// No rate limits — 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;
// No rate limits — 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
Imports System.IO
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
' No rate limits — 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秒停頓。 碰到配額10次的5000份文件批量會導致10分鐘的閑置等待。 IronOCR版本在所有可用核心上並行化而無需等待。 對於長時間運行的批量,進度跟踪API提供內建的進度回調,無需手動Interlocked.Increment接線。 對於批次掃描中的圖像質量問題,圖像質量修正指南涵蓋可以在每個Read調用之前新增的預處理管道。
Google Cloud Vision OCR API 到 IronOCR 映射參考
| Google Cloud Vision | IronOCR | 注意事項 |
|---|---|---|
ImageAnnotatorClient.Create() |
new IronTesseract() |
客戶端初始化; 不用憑證文件 |
Image.FromFile(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 |
離散int屬性而不是頂點列表 |
word.Confidence |
word.Confidence |
每個單詞的置信度分數 |
page.Confidence |
result.Confidence |
整體結果置信度 |
Feature.Types.Type.DocumentTextDetection |
自動 | IronOCR自動選擇處理模式 |
BatchAnnotateImagesRequest |
new IronTesseract() |
本地並行處理; 無批量大小上限 |
_client.BatchAnnotateImages(requests) |
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:如果未設置環境變數或指向無效的文件,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響應的任何輔助類或服務類中。
解決方案:在程式碼庫中搜索所有w.Symbols模式,並將它們替換為IronOCR結果物件的直接屬性存取。 讀取結果指導指南涵蓋了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:刪除PollUntilCompletedAsync的程式碼將無法編譯。這些程式碼可能會跨多個服務類,通常代表最大的單一塊變更。
解決方案:刪除整個GCS管道。用IronOCR三行等價碼替換50多行的異步方法。 對於保持異步簽名以維持調用相容性的程式碼,用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:任何捕獲StatusCode.ResourceExhausted並實施等待和重試模式的程式碼都是寫來處理每分鐘1,800次請求配額。 這種重試邏輯可能嵌入在中間件、管道步驟或批處理迴圈中。
解決方案:刪除所有與配額錯誤相關的重試邏輯。 IronOCR在本地處理,無需外部配額。 錯誤處理合同從五個RpcException案例更改為兩個:
// Remove: all RpcException handlers for ResourceExhausted, PermissionDenied,
// Unavailable, DeadlineExceeded, Unauthenticated
// IronOCR error 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
// IronOCR error 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
' IronOCR error 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:BoundingPoly頂點計算失效
Google Cloud Vision:從vertices[2].Y - vertices[0].Y。 遷移後,這些表達式在IronOCR中沒有等價物,因為Vertices不存在。
解決方案:用直接的int屬性替換頂點算術。 無需計算:
// 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 all Google Cloud Vision namespace 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 all Google Cloud Vision namespace 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 Google.Cloud.Vision.V1導入替換為using IronOcr - 替換所有
using Google.Cloud.Storage.V1和using Grpc.Core導入 - 將
ImageAnnotatorClient.Create()替換為new IronTesseract() - 刪除所有GCS管道方法(
UploadObjectAsync、異步註釋、輪詢、下載、刪除) - 替換所有PDF處理路徑的
input.LoadPdf()(移除異步GCS協調) - 用直接的
.Text屬性存取替換所有Protobuf符號串聯迴圈 - 將
BoundingPoly.Vertices索引計算替換為word.X、word.Y、word.Width、word.Height - 移除所有針對
ResourceExhausted、PermissionDenied、Unavailable、DeadlineExceeded和Unauthenticated的RpcException捕獲區塊 - 用
input.LoadImageFrames()替換每幀的TIFF迴圈 - 將順序批量迴圈轉換為每執行緒
IronTesseract實例 - 從所有環境配置、CI/CD管道、Docker Compose文件和Kubernetes秘密中移除
GOOGLE_APPLICATION_CREDENTIALS
遷移後
- 驗證錯誤處理程式碼中不再引用
RpcException或GoogleApiException型別 - 確認所有部署環境配置中都沒有
GOOGLE_APPLICATION_CREDENTIALS - 在生產中使用的相同樣本文件集上運行OCR管道並比較文字輸出質量
- 在之前由GCS異步管道處理的文件上測試PDF處理並確認文字輸出相同
- 使用
input.LoadPdf(path, Password: "...")測試受密碼保護的PDF——之前不支持 - 使用
input.LoadImageFrames()測試多頁TIFF處理並驗證所有幀都已處理 - 在代表性樣本上運行批量處理器並確認輸出質量與先前結果相同
- 確認
result.Confidence值在您的文件集內的可接受範圍內 - 使用
result.SaveAsSearchablePdf()驗證可搜尋PDF輸出,之前需要單獨的PDF庫 - 在沒有外部網路連接的環境中運行應用程式並確認OCR正常工作
遷移至IronOCR的主要好處
憑證表面減少至零文件。 遷移後,不再有JSON密鑰文件,無GCS儲存桶配置,無IAM角色,無服務帳戶,也無GOOGLE_APPLICATION_CREDENTIALS環境變數在您的基礎設施中。 整個憑證表面是一個包含許可密鑰字串的環境變數。 在Google Cloud Vision中是必須定期操作的密鑰輪轉不再是一個適用的概念。 對於在多個地區或雲供應商中運行的團隊,部署配置複雜性的減少是即時的。
無需外部依賴的PDF和TIFF處理。 GCS異步管道和System.Drawing TIFF frame迴圈被完全刪除。 input.LoadPdf()和input.LoadImageFrames()是替代品——都同步,都是本地,都從調用到結果僅三行。 受密碼保護的PDF,這在Google Cloud Vision中是不可能的,現在隻需一個額外的參數即可工作。 PDF OCR指南和TIFF輸入指南涵蓋了完整的輸入API。
在CPU速度下的批量處理。 移除每分鐘的1800次配額和強制60秒重試等待這意味著之前受速率限制的批量作業現在以可用處理器核心的速度運行。 一台擁有16個核心的機器能夠同時處理16個文件而無需外部批準。 Parallel.ForEach模式與每執行緒的IronTesseract實例共同是受限順序迴圈的直接替代。速度優化指南涵蓋了引擎配置選擇,這些選擇可以調整特定文件型別的吞吐量。
沒有Protobuf的結構化資料。 每個OcrResult將Text、Confidence、Pages、Paragraphs、Lines、Words和Characters公開為型別化.NET屬性,沒有Protobuf命名空間依賴,沒有符號串聯,也沒有用於邊界框的頂點算術。 需要20行巢狀迴圈來提取段落文字的程式碼已減少到result.Paragraphs.Select(p => p.Text)。 需要單詞級定位以進行文件佈局分析的用例,word.X、word.Y、word.Width和word.Height可直接使用。 OCR結果特徵頁記錄了結果模型中的每個屬性。
內建的可搜索PDF輸出。 Google Cloud Vision僅返回文字——生產可搜索PDF需要單獨的PDF生成庫,這增加了另一個NuGet依賴,另一個API需要學習,並需要評估更多的授權。 IronOCR的result.SaveAsSearchablePdf(outputPath)從任何OCR結果中生產出完全可搜索的PDF。對於文件存檔和法律發現流水線,這消除了整個依賴性。 可搜索PDF範例完整展示了這一模式。
為受監管行業提供資料主權。 使用IronOCR處理的文件從未離開伺服器。 對於HIPAA涵蓋的健康記錄、ITAR控管的技術資料、CMMC範圍的國防承包商材料、律師-客戶特權法律文件和PCI-DSS範圍內的財務記錄,本地架構完全將第三方資料處理器類別從合規範圍中移除。 沒有商業夥伴協議需要協商,沒有資料保護協議需要執行,也不存在需要審查的Google資料保留政策。 IronOCR文件中心涵蓋了在Docker、Linux、Azure和AWS環境中適用資料駐留要求的部署配置。
常見問題
為什麼我要從Google Cloud Vision API遷移到IronOCR?
常見的驅動因素包括消除COM互操作複雜性、更換基於文件的許可證管理、避免按頁收費、啟用Docker/容器部署,以及採取與標準.NET工具整合的NuGet本地工作流。
從Google Cloud Vision API遷移到IronOCR的主要程式碼變更是什麼?
用IronTesseract實例化替換Google Cloud Vision初始化序列,移除COM生命週期管理(顯式的Create/Load/Close模式),並更新結果屬性名稱。結果是顯著減少的樣板程式碼行數。
如何安裝IronOCR以開始遷移?
在套件管理器控制台中運行 'Install-Package IronOcr' 或在CLI中運行 'dotnet add package IronOcr'。語言包是單獨的包:例如 'dotnet add package IronOcr.Languages.French' 安裝法語支持。
IronOCR是否能匹配Google Cloud Vision API在標準商業文件上的OCR準確性?
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。實例化IronTesseract,調用ocr.Read(input),其中input是PDF路徑或OcrPdfInput,並迭代OcrResult頁面。不需要單獨的PDF渲染管道。
IronOCR如何處理大批量處理中的多執行緒?
IronTesseract可以安全地在每個執行緒中實例化。在Parallel.ForEach或Task池中為每個執行緒建立一個實例,並發併運行OCR,當完成時處置每個實例。不需要全域狀態或鎖定。
IronOCR在文字提取後支持哪些輸出格式?
IronOCR返回結構化結果,包括文字、單詞坐標、信心分數和頁面結構。導出選項包括純文字、可搜索PDF以及下游處理的結構化結果物件。
對於擴展工作負載,IronOCR的定價是否比Google Cloud Vision API更可預測?
IronOCR使用固定費率的永久性許可證,沒有按頁或按量收費。無論您處理10000頁還是1000萬頁,許可證費用保持不變。團隊和批量許可證選項可在IronOCR定價頁面上找到。
從Google Cloud Vision API遷移到IronOCR後,我現有的測試會發生什麼變化?
遷移後,斷言提取的文字內容的測試應繼續通過。需要更新的測試是驗證API調用模式或COM物件生命週期,這需要反映IronOCR較簡單的初始化和結果模型。

