Przejdź do treści stopki
FILMY

Jak zainstalować Tesseract OCR w systemie Windows w języku C#

Ten przewodnik przeprowadza programistów .NET przez proces zastępowaniaGoogle Cloud Visionsilnikiem IronOCR jako gotowym do użycia lokalnym silnikiem OCR. Obejmuje on usuwanie danych uwierzytelniających, zastąpienie parsowania adnotacji Protobuf, uproszczenie adnotacji wsadowych oraz przetwarzanie dokumentów wielostronicowych — cztery zmiany strukturalne, które stanowią większość prac związanych z migracją.

Dlaczego warto przejść zGoogle Cloud VisionOCR

Decyzja o migracji prawie zawsze wynika z jednego z dwóch powodów: audyt zgodności blokuje przesyłanie dokumentów w chmurze lub zarządzanie danymi uwierzytelniającymi GCP, zasobnikami GCS, asynchronicznym odpytywaniem i rozliczaniem za każdy obraz staje się droższe niż samo OCR.

Cykl życia klucza JSON konta usługi. Każde wdrożenie aplikacji — maszyna programistyczna, potok CI/CD, serwer stagingowy, serwer produkcyjny, kontener Docker, pod Kubernetes — wymaga tego samego pliku klucza JSON konta usługi. Plik zawiera klucz prywatny RSA. Nie może on nigdy trafić do systemu kontroli wersji, musi być rotowany zgodnie z harmonogramem, musi być chroniony uprawnieniami systemu plików i musi być aktualizowany we wszystkich środowiskach jednocześnie w momencie rotacji. Jeden skompromitowany klucz zapewnia dostęp do API do momentu ręcznego cofnięcia go w konsoli GCP.IronOCR zastępuje całą tę powierzchnię operacyjną pojedynczym ciągiem klucza licencyjnego ustawianym jednorazowo podczas uruchamiania aplikacji.

Rozliczanie na żądanie na dużą skalę. Przy cenie 1,50 USD za 1000 obrazów i 0,0015 USD za stronę PDF koszty są niewidoczne podczas tworzenia oprogramowania, a w fazie produkcyjnej stają się bolesne. Pipeline przetwarzania dokumentów obsługujący 200 000 stron miesięcznie kosztuje 300 USD miesięcznie samych opłat za API, nie licząc opłat za przechowywanie w GCS i kosztów przesyłu wychodzącego. Ta kwota 300 dolarów powtarza się co miesiąc bezterminowo. Licencja wieczysta IronOCR przekształca OCR z kosztu operacyjnego rozliczanego według rzeczywistego zużycia w pozycję kapitału trwałego, której eksploatacja w drugim lub trzecim roku nic nie kosztuje.

GCS Async Pipeline dla plików PDF.Google Cloud Visionnie akceptuje plików PDF jako bezpośredniego danych wejściowych API. Pelny potok przetwarzania wymaga drugiego pakietu NuGet (Google.Cloud.Storage.V1), skonfigurowanego kubełka GCS, asynchronicznego przesyłania, wywołania AsyncBatchAnnotateFilesAsync, pętli oczekującej, parsowania wyniku JSON z GCS i kroku sprzątającego. Ten potok obejmuje ponad 50 linii kodu zanim jakikolwiek tekst zostanie wyodrębniony.IronOCR odczytuje plik PDF w trzech wierszach, synchronicznie, bez żadnych zewnętrznych zależności.

Złączenie symboli Protobuf. Odpowiedź DOCUMENT_TEXT_DETECTION przechowuje tekst na poziomie symbolu w hierarchii Protobuf zawierającej strony, bloki, akapity, słowa i symbole. Odczyt tekstu z akapitu wymaga iteracji pięciu zagnieżdżonych pętli i wywołania .SelectMany(w => w.Symbols).Select(s => s.Text).IronOCR zwraca tekst akapitu jako paragraph.Text — typowaną właściwość ciągu znaków.

Domyślny limit 1 800 żądań na minutę. Przetwarzane wsady, które przekraczają domyślny limit, otrzymują odpowiedzi StatusCode.ResourceExhausted, które opóźniają przetwarzanie o 60 sekund za każde przekroczenie. Zwiększenie limitu wymaga złożenia wniosku w konsoli GCP i uzyskania zgody Google.IronOCR przetwarza dane lokalnie z prędkością dostępnych rdzeni procesora — nie ma żadnych limitów do zarządzania, nie trzeba czekać na zatwierdzenie, a logika ponownych prób nie jest wymagana do ograniczania przepustowości.

Brak obsługi trybu offline lub izolacji sieciowej. UsługaGoogle Cloud Visionwymaga połączenia internetowego z punktami końcowymi Google. Sieci z izolacją fizyczną, tajne centra danych i przemysłowe systemy sterowania nie mogą z niego korzystać na żadnym poziomie złożoności architektury.IronOCR działa bez połączenia z siecią po wstępnej weryfikacji licencji.

Podstawowy problem

Google Cloud Vision wymaga pliku klucza JSON na dysku przed uruchomieniem pierwszej linii kodu OCR:

// Google Cloud Vision: JSON key file deployed to every server before this line works
// GOOGLE_APPLICATION_CREDENTIALS="/etc/secrets/service-account.json" must be set
// Key contains RSA private key — rotate manually, revoke if compromised
_client = ImageAnnotatorClient.Create();
var image = Image.FromFile("document.jpg");
var response = _client.DetectText(image);   // document leaves your infrastructure
string text = response[0].Description;
// Google Cloud Vision: JSON key file deployed to every server before this line works
// GOOGLE_APPLICATION_CREDENTIALS="/etc/secrets/service-account.json" must be set
// Key contains RSA private key — rotate manually, revoke if compromised
_client = ImageAnnotatorClient.Create();
var image = Image.FromFile("document.jpg");
var response = _client.DetectText(image);   // document leaves your infrastructure
string text = response[0].Description;
' Google Cloud Vision: JSON key file deployed to every server before this line works
' GOOGLE_APPLICATION_CREDENTIALS="/etc/secrets/service-account.json" must be set
' Key contains RSA private key — rotate manually, revoke if compromised
_client = ImageAnnotatorClient.Create()
Dim image = Image.FromFile("document.jpg")
Dim response = _client.DetectText(image)   ' document leaves your infrastructure
Dim text As String = response(0).Description
$vbLabelText   $csharpLabel

IronOCR uruchamia się na podstawie jednego ciągu znaków i działa całkowicie na komputerze lokalnym:

// 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
$vbLabelText   $csharpLabel

IronOCR aGoogle Cloud VisionOCR: porównanie funkcji

Poniższa tabela przedstawia funkcje w sposób bezpośredni dla zespołów opracowujących uzasadnienie biznesowe migracji.

Funkcja Google Cloud Vision OCR IronOCR
Miejsce przetwarzania Google Cloud (praca zdalna) Lokalnie (na miejscu)
Uwierzytelnianie Klucz JSON konta usługowego + zmienna środowiskowa Ciąg klucza licencyjnego
Plik wejściowy PDF Przesyłanie do GCS + asynchroniczne API input.LoadPdf() bezpośredni
Plik PDF chroniony hasłem Nieobsługiwane LoadPdf(path, Password: "...")
Wielostronicowy plik wejściowy w formacie TIFF Ograniczone input.LoadImageFrames()
Wynik w formacie PDF z możliwością wyszukiwania Niedostępne result.SaveAsSearchablePdf()
Dostęp do danych strukturalnych Protobuf: Strony > Bloki > Akapity > WORDy > Symbole result.Paragraphs, result.Lines, result.Words (typowane obiekty .NET)
Właściwości tekstu akapitu Nie — wymaga łączenia symboli paragraph.Text właściwość bezpośrednia
Wyniki pewności Na symbol (wymaga pętli) result.Confidence, word.Confidence
Automatyczne przetwarzanie wstępne obrazów Brak (obsługuje to ML) Wyrównanie, usuwanie szumów, kontrast, binarizacja, wyostrzanie
OCR oparte na regionie Brak natywnego przycięcia CropRectangle na OcrInput
Odczytywanie BarCode Oddzielna funkcja API ocr.Configuration.ReadBarCodes = true
Limity szybkości 1800 żądań na minutę (domyślnie) Brak (zależne od procesora)
Tryb offline / izolacja sieciowa Nie Tak
Koszt za dokument 1,50 USD za 1000 obrazów; 0,0015 USD/strona PDF Brak (Licencja wieczysta)
Obsługiwane języki ~50 125+
Autoryzacja FedRAMP Brak uprawnień Nie dotyczy (lokalnie)
Ścieżka zgodności z HIPAA Wymagana umowa o współpracy biznesowej Brak przetwarzania danych stron trzecich
Obsługa platformy .NET Framework .NET Standard 2.0+ .NET Framework 4.6.2+ oraz .NET 5/6/7/8/9
Wymagane pakiety NuGet Google.Cloud.Vision.V1 + Google.Cloud.Storage.V1 IronOcr tylko
Model cenowy Rozliczenie według liczby żądań Perpetual ($999 Lite / $1,499 Plus / $2,999 Professional / $5,999 Unlimited)

Szybki start: Migracja zGoogle Cloud Vision OCRdo IronOCR

Krok 1: Zastąp pakiet NuGet

Usuń pakiety 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
SHELL

Zainstaluj IronOCR ze strony pakietu NuGet:

dotnet add package IronOcr

Krok 2: Aktualizacja przestrzeni nazw

Zastąp przestrzenie nazw Google Cloud przestrzenią nazw 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
$vbLabelText   $csharpLabel

Krok 3: Inicjalizacja licencji

Dodaj inicjalizację licencji raz przy starcie aplikacji, przed utworzeniem jakiejkolwiek instancji IronTesseract:

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

W środowisku produkcyjnym odczytaj klucz ze zmiennej środowiskowej lub menedżera sekretów:

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."))
$vbLabelText   $csharpLabel

Przykłady migracji kodu

Eliminacja konfiguracji poświadczeń konta usługowego

Inicjalizacja klientaGoogle Cloud Visionwygląda jak pojedynczy wiersz, ale wymaga rozbudowanej infrastruktury. Każdy programista dołączający do projektu, każde środowisko wdrożeniowe i każdy potok CI/CD wymaga pełnej konfiguracji poświadczeń przed zakończeniem działania konstruktóra bez generowania błędu.

Podejście 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
$vbLabelText   $csharpLabel

Podejście IronOCR:

using IronOcr;

// Prerequisites: set the license key once at app startup
// Nie JSON files, no environment variables beyond the key, no GCP Console configuration
// Nie 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
// Nie JSON files, no environment variables beyond the key, no GCP Console configuration
// Nie 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
' Nie JSON files, no environment variables beyond the key, no GCP Console configuration
' Nie 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
$vbLabelText   $csharpLabel

Operacyjna różnica jest konkretna:Google Cloud Visiongeneruje pięć kategorii RpcException w czasie wykonywania — PermissionDenied, ResourceExhausted, Unavailable, DeadlineExceeded i Unauthenticated — każda reprezentująca inny tryb awarii infrastruktury. Tryby awarii IronOCR to IOException (plik nie znaleziony lub zablokowany) i OcrException (błąd przetwarzania). Opcje konfiguracyjne można znaleźć w przewodniku konfiguracji IronTesseract, a szczegóły dotyczące licencji — na stronie produktu IronOCR.

Zastąpienie żądań adnotacji wsadowych różnymi typami funkcji

Google Cloud Vision obsługuje przetwarzanie wielu obrazów w jednym BatchAnnotateImagesRequest, każdy obraz skonfigurowany z listą typów Feature. Ten wzorzec używany jest, gdy jedno wywołanie musi zebrać zarówno wyniki TEXT_DETECTION, jak i DOCUMENT_TEXT_DETECTION, lub gdy wiele obrazów jest przesyłanych, aby zminimalizować obciążenie dla odbioru. Odpowiedź Protobuf wymaga dopasowania każdego AnnotateImageResponse z powrotem do oryginalnego zapytania na podstawie indeksu.

Podejście 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 Funkcja { Type = Feature.Types.Type.TextDetection },
                new Funkcja { 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 Funkcja { Type = Feature.Types.Type.TextDetection },
                new Funkcja { 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
$vbLabelText   $csharpLabel

Podejście 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
$vbLabelText   $csharpLabel

IronOCR uruchamia partię zadań równolegle na rdzeniach procesora bez obciążania sieci. Nie ma limitu BatchSize, brak dopasowania indeksu odpowiedzi i brak obsługi błędów dla poszczególnych elementów z powodu niepowodzeń sieciowych lub poświadczeniowych. W przypadku zadania przetwarzania wielu obrazów w jeden logiczny dokument — np. zeskanowanych wielostronicowych formularzy przesłanych jako pojedyncze JPEGi — BatchAsDocument ładuje wszystkie obrazy do jednego OcrInput i zwraca scalony OcrResult z result.Pages zindeksowanymi do każdego obrazu źródłowego. Przykład wielowątkowości przedstawia wyniki testów wydajnościowych przetwarzania równoległego.

Migracja ekstrakcji adnotacji na poziomie słów w Protobuf WORD

Dane dotyczące ramki ograniczającej na poziomie słowa i poziomu pewności wGoogle Cloud Visionwymagają poruszania się po pełnej hierarchii Protobuf: Strony, następnie Bloki, następnie Akapity, następnie Słowa, a na końcu Symbole. Wyodrębnienie tekstu ze słowa wymaga konkatynacji Symboli z każdego Słowa — obiekt Word nie posiada bezpośredniej właściwości .Text. Koordynaty prostokątu ograniczającego są przechowywane jako BoundingPoly z listą Vertices zamiast dyskretnych pól X, Y, Szerokości, Wysokości.

Podejście 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
$vbLabelText   $csharpLabel

Podejście 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
$vbLabelText   $csharpLabel

Pętla konkatenacji symboli w wersjiGoogle Cloud Visionnie jest wyborem projektowym — jest wymagana przez schemat Protobuf. Word.Text nie jest właściwością w obiekcie odpowiedzi. Każdy zespół wykorzystujący API na poziomie słowa pisze równoważną pętlę. Kolekcja OcrResult.WordsIronOCR udostępnia jako pierwszorzędne właściwości: Text, X, Y, Width, Height i Confidence. Przewodnik po wynikach odczytu dokumentuje kompletny zestaw właściwości dostępnych na każdym poziomie szczegółowości.

Przetwarzanie wielostronicowych plików TIFF do formatu PDF z możliwością wyszukiwania

Google Cloud Vision traktuje wielostronicowe pliki TIFF jako sekwencyjny ciąg obrazów, z których każdy wymaga oddzielnego wywołania API. Nie ma pojedynczego wywołania API, które akceptuje plik TIFF i zwraca ustrukturyzowany wynik dla wszystkich klatek. Wygenerowanie pliku PDF z możliwością wyszukiwania na podstawie wynikówGoogle Cloud Visionwymaga osobnej biblioteki do generowania plików PDF — API zwraca wyłącznie tekst.

Podejście Google Cloud Vision:

using Google.Cloud.Vision.V1;
using System.Collections.Generic;
using System.Drawing;          // for TIFF frame extraction
using System.Drawing.Imaging;

public class TiffProcessingService
{
    private readonly ImageAnnotatorClient _client;

    public TiffProcessingService()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public List<string> ProcessMultiPageTiff(string tiffPath)
    {
        var pageTexts = new List<string>();

        // Load TIFF and extract frames manually using System.Drawing
        using var tiff = System.Drawing.Image.FromFile(tiffPath);
        var frameDimension = new FrameDimension(tiff.FrameDimensionsList[0]);
        int frameCount = tiff.GetFrameCount(frameDimension);

        for (int i = 0; i < frameCount; i++)
        {
            tiff.SelectActiveFrame(frameDimension, i);

            // Save each frame to a temp file — Vision API does not accept TIFF frames directly
            var tempPath = Path.Combine(Path.GetTempPath(), $"tiff-frame-{i}.jpg");
            tiff.Save(tempPath, ImageFormat.Jpeg);

            // One API call per frame — each call = one unit of quota
            var visionImage = Google.Cloud.Vision.V1.Image.FromFile(tempPath);
            var response = _client.DetectText(visionImage);
            pageTexts.Add(response.FirstOrDefault()?.Description ?? string.Empty);

            File.Delete(tempPath);
        }

        // Producing a searchable PDF requires a separate library (e.g., iTextSharp)
        //Google Cloud Visionhas no PDF output capability
        return pageTexts;
    }
}
using Google.Cloud.Vision.V1;
using System.Collections.Generic;
using System.Drawing;          // for TIFF frame extraction
using System.Drawing.Imaging;

public class TiffProcessingService
{
    private readonly ImageAnnotatorClient _client;

    public TiffProcessingService()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public List<string> ProcessMultiPageTiff(string tiffPath)
    {
        var pageTexts = new List<string>();

        // Load TIFF and extract frames manually using System.Drawing
        using var tiff = System.Drawing.Image.FromFile(tiffPath);
        var frameDimension = new FrameDimension(tiff.FrameDimensionsList[0]);
        int frameCount = tiff.GetFrameCount(frameDimension);

        for (int i = 0; i < frameCount; i++)
        {
            tiff.SelectActiveFrame(frameDimension, i);

            // Save each frame to a temp file — Vision API does not accept TIFF frames directly
            var tempPath = Path.Combine(Path.GetTempPath(), $"tiff-frame-{i}.jpg");
            tiff.Save(tempPath, ImageFormat.Jpeg);

            // One API call per frame — each call = one unit of quota
            var visionImage = Google.Cloud.Vision.V1.Image.FromFile(tempPath);
            var response = _client.DetectText(visionImage);
            pageTexts.Add(response.FirstOrDefault()?.Description ?? string.Empty);

            File.Delete(tempPath);
        }

        // Producing a searchable PDF requires a separate library (e.g., iTextSharp)
        //Google Cloud Visionhas no PDF output capability
        return pageTexts;
    }
}
Imports Google.Cloud.Vision.V1
Imports System.Collections.Generic
Imports System.Drawing          ' for TIFF frame extraction
Imports System.Drawing.Imaging
Imports System.IO

Public Class TiffProcessingService
    Private ReadOnly _client As ImageAnnotatorClient

    Public Sub New()
        _client = ImageAnnotatorClient.Create()
    End Sub

    Public Function ProcessMultiPageTiff(tiffPath As String) As List(Of String)
        Dim pageTexts As New List(Of String)()

        ' Load TIFF and extract frames manually using System.Drawing
        Using tiff As System.Drawing.Image = System.Drawing.Image.FromFile(tiffPath)
            Dim frameDimension As New FrameDimension(tiff.FrameDimensionsList(0))
            Dim frameCount As Integer = tiff.GetFrameCount(frameDimension)

            For i As Integer = 0 To frameCount - 1
                tiff.SelectActiveFrame(frameDimension, i)

                ' Save each frame to a temp file — Vision API does not accept TIFF frames directly
                Dim tempPath As String = Path.Combine(Path.GetTempPath(), $"tiff-frame-{i}.jpg")
                tiff.Save(tempPath, ImageFormat.Jpeg)

                ' One API call per frame — each call = one unit of quota
                Dim visionImage As Google.Cloud.Vision.V1.Image = Google.Cloud.Vision.V1.Image.FromFile(tempPath)
                Dim response = _client.DetectText(visionImage)
                pageTexts.Add(If(response.FirstOrDefault()?.Description, String.Empty))

                File.Delete(tempPath)
            Next
        End Using

        ' Producing a searchable PDF requires a separate library (e.g., iTextSharp)
        ' Google Cloud Vision has no PDF output capability
        Return pageTexts
    End Function
End Class
$vbLabelText   $csharpLabel

Podejście IronOCR:

using IronOcr;

public class TiffProcessingService
{
    private readonly IronTesseract _ocr;

    public TiffProcessingService()
    {
        _ocr = new IronTesseract();
    }

    public string ProcessMultiPageTiff(string tiffPath)
    {
        using var input = new OcrInput();
        // LoadImageFrames handles all TIFF frames in one call — no temp files, no frame loop
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);
        return result.Text;
    }

    public void ProcessMultiPageTiffToSearchablePdf(string tiffPath, string outputPdfPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);

        //Google Cloud Visionhas no equivalent — this single call produces a searchable PDF
        result.SaveAsSearchablePdf(outputPdfPath);
    }

    public void ProcessLowQualityTiff(string tiffPath, string outputPdfPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        // Preprocessing before OCR improves accuracy on degraded scans
        input.Deskew();
        input.DeNoise();
        input.Contrast();

        var result = _ocr.Read(input);
        result.SaveAsSearchablePdf(outputPdfPath);
    }
}
using IronOcr;

public class TiffProcessingService
{
    private readonly IronTesseract _ocr;

    public TiffProcessingService()
    {
        _ocr = new IronTesseract();
    }

    public string ProcessMultiPageTiff(string tiffPath)
    {
        using var input = new OcrInput();
        // LoadImageFrames handles all TIFF frames in one call — no temp files, no frame loop
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);
        return result.Text;
    }

    public void ProcessMultiPageTiffToSearchablePdf(string tiffPath, string outputPdfPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);

        //Google Cloud Visionhas no equivalent — this single call produces a searchable PDF
        result.SaveAsSearchablePdf(outputPdfPath);
    }

    public void ProcessLowQualityTiff(string tiffPath, string outputPdfPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        // Preprocessing before OCR improves accuracy on degraded scans
        input.Deskew();
        input.DeNoise();
        input.Contrast();

        var result = _ocr.Read(input);
        result.SaveAsSearchablePdf(outputPdfPath);
    }
}
Imports IronOcr

Public Class TiffProcessingService
    Private ReadOnly _ocr As IronTesseract

    Public Sub New()
        _ocr = New IronTesseract()
    End Sub

    Public Function ProcessMultiPageTiff(tiffPath As String) As String
        Using input As New OcrInput()
            ' LoadImageFrames handles all TIFF frames in one call — no temp files, no frame loop
            input.LoadImageFrames(tiffPath)

            Dim result = _ocr.Read(input)
            Return result.Text
        End Using
    End Function

    Public Sub ProcessMultiPageTiffToSearchablePdf(tiffPath As String, outputPdfPath As String)
        Using input As New OcrInput()
            input.LoadImageFrames(tiffPath)

            Dim result = _ocr.Read(input)

            ' Google Cloud Vision has no equivalent — this single call produces a searchable PDF
            result.SaveAsSearchablePdf(outputPdfPath)
        End Using
    End Sub

    Public Sub ProcessLowQualityTiff(tiffPath As String, outputPdfPath As String)
        Using input As New OcrInput()
            input.LoadImageFrames(tiffPath)

            ' Preprocessing before OCR improves accuracy on degraded scans
            input.Deskew()
            input.DeNoise()
            input.Contrast()

            Dim result = _ocr.Read(input)
            result.SaveAsSearchablePdf(outputPdfPath)
        End Using
    End Sub
End Class
$vbLabelText   $csharpLabel

WersjaGoogle Cloud Visionwymaga biblioteki System.Drawing do wyodrębniania klatek, tymczasowych plików JPEG zapisywanych na dysku, jednego wywołania API na klatkę (zużywającego limit proporcjonalnie do liczby klatek) oraz oddzielnej biblioteki PDF do generowania wyników innych niż zwykły tekst.IronOCR obsługuje wieloklatkowe TIFF-y natywnie poprzez LoadImageFrames, przetwarza wszystkie klatki w pojedynczym wywołaniu Read i produkuje przeszukiwalny wynik PDF poprzez SaveAsSearchablePdf. Dla zeskanowanych archiwalnych dokumentów TIFF, potok przetwarzania w ProcessLowQualityTiff rozwiązuje najczęstsze problemy jakościowe w jednym przejściu. Pełny opis API znajduje się w sekcji dotyczącej plików wejściowych TIFF i GIF oraz plików wyjściowych PDF z możliwością wyszukiwania.

Migracja partii z ograniczeniem szybkości i śledzeniem postępów

W skali produkcyjnej domyślny limit 1800 żądań na minutę wGoogle Cloud Visionwymaga zastosowania logiki ograniczania przepustowości lub ponawiania prób z wykładniczym opóźnieniem. Przetworzenie 5000 dokumentów w ramach jednego zadania nocnego wielokrotnie przekroczy limit. Każde przekroczenie limitu blokuje potok na obowiązkowe 60 sekund oczekiwania.IronOCR nie ma ograniczeń szybkości — przepustowość jest ograniczona jedynie przez rdzenie procesora i dostępne wątki.

Podejście 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
$vbLabelText   $csharpLabel

Podejście 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;

        // Brak limitów stawek — 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;

        // Brak limitów stawek — full parallelism, no retry logic needed
        Parallel.ForEach(imagePaths, imagePath =>
        {
            var ocr = new IronTesseract();
            results[imagePath] = ocr.Read(imagePath).Text;
            progress.Report((Interlocked.Increment(ref completed), imagePaths.Length));
        });

        return new Dictionary<string, string>(results);
    }

    public void ProcessBatchToSearchablePdfs(
        string[] imagePaths,
        string outputDirectory)
    {
        Parallel.ForEach(imagePaths, imagePath =>
        {
            var ocr = new IronTesseract();
            var result = ocr.Read(imagePath);

            var outputPath = Path.Combine(
                outputDirectory,
                Path.GetFileNameWithoutExtension(imagePath) + "-searchable.pdf");

            result.SaveAsSearchablePdf(outputPath);
        });
    }
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Public Class BatchProcessor
    Public Function ProcessBatch(imagePaths As String(), progress As IProgress(Of (completed As Integer, total As Integer))) As Dictionary(Of String, String)
        Dim results As New ConcurrentDictionary(Of String, String)()
        Dim completed As Integer = 0

        ' Brak limitów stawek — full parallelism, no retry logic needed
        Parallel.ForEach(imagePaths, Sub(imagePath)
                                         Dim ocr As 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 As 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
$vbLabelText   $csharpLabel

WersjaGoogle Cloud Visionjest sekwencyjna, ponieważ żądania równoległe zwiększyłyby narażenie na limit częstotliwości. Każdy wyjątek ResourceExhausted dodaje pełną, 60-sekundową przerwę. Partia 5000 dokumentów, która 10 razy przekracza limit, powoduje 10 minut bezczynnego oczekiwania. Wersja IronOCR działa równolegle na wszystkich dostępnych rdzeniach bez czekania. Dla długotrwałych wsadów, API śledzenia postępu zapewnia wbudowane wywołania zwrotne postępu bez konieczności ręcznego okablowania Interlocked.Increment. Dla problemów z jakością obrazu w skanowanych wsadach, przewodnik korekcji jakości obrazu opisuje potok przetwarzania, który można dodać przed każdym wywołaniem Read.

MapowanieGoogle Cloud Vision OCRAPI do IronOCR– dokumentacja API

Google Cloud Vision IronOCR Uwagi
ImageAnnotatorClient.Create() new IronTesseract() Inicjalizacja klienta; brak potrzeby pliku uwierzytelniającego
Image.FromFile(path) _ocr.Read(path) lub input.LoadImage(path) Dostępne bezpośrednie odczyty ścieżek na IronTesseract
_client.DetectText(image) _ocr.Read(path).Text TEXT_DETECTION równoważnik
_client.DetectDocumentText(image) _ocr.Read(path) DOCUMENT_TEXT_DETECTION równoważnik; tryb jest automatyczny
response[0].Description result.Text Pełny tekst dokumentu
TextAnnotation OcrResult Kontener wyników najwyższego poziomu
annotation.Text result.Text Pełny tekst
annotation.Pages[i] result.Pages[i] Dostęp na stronę
page.Blocks[i].Paragraphs[j] result.Paragraphs[i] IronOCR udostępnia akapity jako płaską kolekcję
paragraph.Words.SelectMany(w => w.Symbols).Select(s => s.Text) paragraph.Text Właściwość bezpośredniego ciągu znaków; brak iteracji symboli
word.BoundingBox.Vertices word.X, word.Y, word.Width, word.Height Dyskretne właściwości int zamiast listy wierzchołków
word.Confidence word.Confidence Wskaźnik pewności dla poszczególnych słów
page.Confidence result.Confidence Ogólna pewność wyniku
Feature.Types.Type.DocumentTextDetection Automatyczne IronOCR automatycznie wybiera tryb przetwarzania
BatchAnnotateImagesRequest Parallel.ForEach + new IronTesseract() na wątek Równoległe przetwarzanie lokalne; brak limitu rozmiaru partii
_client.BatchAnnotateImages(requests) new IronTesseract().Read(input) z wieloobrazowym OcrInput Pojedyncze wywołanie dla wielu obrazów
AsyncBatchAnnotateFilesAsync() input.LoadPdf(); _ocr.Read(input) Przetwarzanie plików PDF odbywa się synchronicznie; nie jest wymagany GCS
StorageClient.Create() Nie jest potrzebne Brak zależności od GCS
storageClient.UploadObjectAsync() Nie jest potrzebne Pliki PDF są ładowane bezpośrednio ze ścieżki lokalnej lub strumienia
operation.PollUntilCompletedAsync() Nie jest potrzebne Przetwarzanie jest synchroniczne
RpcException (StatusCode.ResourceExhausted) Nie dotyczy Brak limitów stawek
RpcException (StatusCode.PermissionDenied) Nie dotyczy Brak uwierzytelniania w czasie wykonywania
GOOGLE_APPLICATION_CREDENTIALS zmienna środowiskowa IronOcr.License.LicenseKey Przypisanie ciągu znaków, a nie ścieżki do pliku

Typowe problemy związane z migracją i ich rozwiązania

Problem 1: Konstruktor generuje wyjątek bez GOOGLE_APPLICATION_CREDENTIALS

Google Cloud Vision: ImageAnnotatorClient.Create() wyrzuca RpcException z StatusCode.Unauthenticated lub StatusCode.PermissionDenied, jeśli zmienna środowiskowa nie jest ustawiona lub wskazuje na nieprawidłowy plik. Ten błąd występuje podczas uruchamiania, a nie przy pierwszym wywołaniu API, co oznacza, że ​​cała aplikacja nie zostanie zainicjowana, jeśli w danym środowisku brakuje poświadczeń.

Rozwiązanie: Po usunięciu pakietów Google Cloud Vision, usuń wszystkie odniesienia do GOOGLE_APPLICATION_CREDENTIALS z konfiguracji środowiska, tajemnic potoku CI/CD, tajemnic Kubernetes i plików Docker Compose. Zastąp pojedynczą zmienną środowiskową 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."))
$vbLabelText   $csharpLabel

Problem 2: Kod konkatenacji symboli Protobuf przestaje działać po usunięciu przestrzeni nazw

Google Cloud Vision: Każde miejsce w twoim kodzie, gdzie tekst akapitu lub słowa był wyodrębniony przy użyciu .SelectMany(w => w.Symbols).Select(s => s.Text) spowoduje błędy kompilacji po usunięciu przestrzeni nazw Google.Cloud.Vision.V1. Te wywołania są rozłożone na wszystkie klasy pomocnicze lub usługowe, które wykorzystują odpowiedź API.

Rozwiązanie: Wyszukaj wszystkie wzorce SelectMany i w.Symbols w swoim kodzie i zastąp je bezpośrednim dostępem do właściwości obiektów wyników IronOCR. Przewodnik jak czytać wyniki obejmuje każdą dostępną właściwość na OcrResult, OcrResult.Page, OcrResult.Paragraph, OcrResult.Line i 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" .
SHELL

Zastąp każde wystąpienie:

// 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
$vbLabelText   $csharpLabel

Problem 3: Kod przetwarzania plików PDF nie kompiluje się po usunięciu pliku Storage.V1

Google Cloud Vision: Po usunięciu Google.Cloud.Storage.V1 cały kod, który odnosi się do StorageClient, UploadObjectAsync, DeleteObjectAsync, AsyncAnnotateFileRequest, GcsSource, GcsDestination i PollUntilCompletedAsync, nie będzie się kompilować. Ten kod może obejmować wiele klas serwisowych i zazwyczaj reprezentuje największy pojedynczy blok zmian.

Rozwiązanie: Usuń cały potok GCS. Zastąp metodę asynchroniczną liczącą ponad 50 wierszy jej trzywierszowym odpowiednikiem w IronOCR. Dla kodu, który utrzymywał asynchroniczną sygnaturę dla zgodności z wywołującym, opakuj to w 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
$vbLabelText   $csharpLabel

Dla nowego kodu użyj natywnego wsparcia Asynchronicznego OCR zamiast otoczki Task.Run. Przewodnik dotyczący plików PDF obejmuje wybór zakresu stron oraz ładowanie plików PDF chronionych hasłem.

Problem 4: Logika ponownych prób w ramach limitu szybkości nie jest już potrzebna

Google Cloud Vision: Każdy kod, który przechwytuje RpcException z StatusCode.ResourceExhausted i implementuje wzorzec oczekiwania i ponawiania, był napisany w celu obsługi limitu 1 800 żądań na minutę. Ta logika ponownej próby może być wbudowana w oprogramowanie pośredniczące, etapy potoku lub pętle przetwarzania wsadowego.

Rozwiązanie: Usuń całą logikę ponownych prób związaną z błędami limitów.IronOCR przetwarza dane lokalnie bez zewnętrznych limitów. Kontrakt obsługi błędów zmienia się z pięciu przypadków RpcException na dwa:

// 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
$vbLabelText   $csharpLabel

Problem 5: Wielostronicowy plik TIFF wymaga pętli wyodrębniania ramek

Google Cloud Vision: Istniejący kod przetwarzający TIFF prawdopodobnie wyodrębnia klatki za pomocą System.Drawing.Image, zapisuje każdą klatkę jako JPEG w katalogu tymczasowym, przesyła każdy JPEG jako osobne wywołanie API, a następnie usuwa tymczasowe pliki. Ten wzorzec zużywa jedną jednostkę limitu na klatkę i może pozostawić osierocone pliki tymczasowe w przypadku awarii.

Rozwiązanie: Zastąp pętlę ekstrakcji klatki i zarządzanie plikami tymczasowymi za pomocą input.LoadImageFrames(). Cała pętla ramkowa System.Drawing została usunięta:

// 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)
$vbLabelText   $csharpLabel

Zapoznaj się z instrukcją dotyczącą plików TIFF i GIF, aby poznać opcje przetwarzania wielu klatek, w tym wybór zakresu klatek.

Problem 6: Błąd w obliczeniach wierzchołków BoundingPoly

Google Cloud Vision: Kod, który odczytywał współrzędne prostokąta ograniczającego z annotation.BoundingPoly.Vertices, obliczał X, Y, Szerokość i Wysokość za pomocą arytmetyki indeksu wierzchołka: vertices[0].X dla X, vertices[1].X - vertices[0].X dla Szerokości, vertices[2].Y - vertices[0].Y dla Wysokości. Po migracji, te wyrażenia nie mają równoważnika w IronOCR, ponieważ Vertices nie istnieje.

Rozwiązanie: Zastąp arytmetykę wierzchołków bezpośrednimi właściwościami typu int. Nie są wymagane żadne obliczenia:

// 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
$vbLabelText   $csharpLabel

Lista kontrolna migracjiGoogle Cloud VisionOCR

Przed migracją

Przed wprowadzeniem jakichkolwiek zmian należy przeprowadzić audyt kodu źródłowego w celu zidentyfikowania wszystkich zależności od Google Cloud Vision:

# Find allGoogle Cloud Visionnamespace imports
grep -rn "using Google.Cloud.Vision" --include="*.cs" .
grep -rn "using Google.Cloud.Storage" --include="*.cs" .
grep -rn "using Grpc.Core" --include="*.cs" .

# Find ImageAnnotatorClient usage
grep -rn "ImageAnnotatorClient" --include="*.cs" .

# Find GCS pipeline code
grep -rn "StorageClient\|UploadObjectAsync\|DeleteObjectAsync" --include="*.cs" .
grep -rn "AsyncBatchAnnotateFilesAsync\|PollUntilCompleted" --include="*.cs" .

# Find Protobuf symbol concatenation
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .

# Find BoundingPoly vertex calculations
grep -rn "BoundingPoly\|BoundingBox\.Vertices" --include="*.cs" .

# Find rate limit retry handlers
grep -rn "ResourceExhausted\|StatusCode\." --include="*.cs" .

# Find environment variable references
grep -rn "GOOGLE_APPLICATION_CREDENTIALS" .
# Find allGoogle Cloud Visionnamespace imports
grep -rn "using Google.Cloud.Vision" --include="*.cs" .
grep -rn "using Google.Cloud.Storage" --include="*.cs" .
grep -rn "using Grpc.Core" --include="*.cs" .

# Find ImageAnnotatorClient usage
grep -rn "ImageAnnotatorClient" --include="*.cs" .

# Find GCS pipeline code
grep -rn "StorageClient\|UploadObjectAsync\|DeleteObjectAsync" --include="*.cs" .
grep -rn "AsyncBatchAnnotateFilesAsync\|PollUntilCompleted" --include="*.cs" .

# Find Protobuf symbol concatenation
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .

# Find BoundingPoly vertex calculations
grep -rn "BoundingPoly\|BoundingBox\.Vertices" --include="*.cs" .

# Find rate limit retry handlers
grep -rn "ResourceExhausted\|StatusCode\." --include="*.cs" .

# Find environment variable references
grep -rn "GOOGLE_APPLICATION_CREDENTIALS" .
SHELL

Uwagi dotyczące przygotowania przed rozpoczęciem:

  • Wymień wszystkie zasoby GCS utworzone dla danych wejściowych/wyjściowych OCR — zaplanuj czyszczenie po migracji
  • Zapisz adres e-mail konta usługowego, aby można było je wyłączyć w konsoli GCP po migracji
  • Zidentyfikuj wszystkie środowiska, w których GOOGLE_APPLICATION_CREDENTIALS jest skonfigurowane
  • Należy odnotować każdy kod, który odczytuje identyfikatory projektów GCP lub nazwy zasobników z konfiguracji — należy go usunąć po migracji

Migracja kodu

  1. Usuń pakiet NuGet Google.Cloud.Vision.V1 ze wszystkich projektów
  2. Usuń pakiet NuGet Google.Cloud.Storage.V1 ze wszystkich projektów
  3. Zainstaluj pakiet NuGet IronOcr we wszystkich projektach, które wykonują OCR
  4. Dodaj inicjalizację IronOcr.License.LicenseKey przy starcie aplikacji
  5. Zastąp wszystkie importy using Google.Cloud.Vision.V1 importami using IronOcr
  6. Zastąp wszystkie importy using Google.Cloud.Storage.V1 i using Grpc.Core
  7. Zastąp ImageAnnotatorClient.Create() przez new IronTesseract()
  8. Usuń wszystkie metody potoku GCS (StorageClient, UploadObjectAsync, adnotacja asynchroniczna, polling, pobieranie, usuwanie)
  9. Zastąp input.LoadPdf() dla wszystkich ścieżek przetwarzania PDF (usuwając asynchroniczną orkiestrację GCS)
  10. Zastąp wszystkie pętle złączania symboli Protobuf bezpośrednim dostępem do właściwości .Text na obiektach OcrResult
  11. Zastąp obliczenia indeksu BoundingPoly.Vertices przez word.X, word.Y, word.Width, word.Height
  12. Usuń wszystkie bloki przechwytywania RpcException dla ResourceExhausted, PermissionDenied, Unavailable, DeadlineExceeded i Unauthenticated
  13. Zastąp pętlę po klatkach TIFF za pomocą input.LoadImageFrames()
  14. Przekształć sekwencyjne pętle wsadowe na Parallel.ForEach z instancjami IronTesseract przypadającymi na wątek
  15. Usuń GOOGLE_APPLICATION_CREDENTIALS ze wszystkich konfiguracji środowiska, potoków CI/CD, plików Docker Compose i tajemnic Kubernetes

Po migracji

  • Zweryfikuj, że żaden z typów RpcException lub GoogleApiException nie jest nadal referencjonowany w kodzie obsługi błędów
  • Potwierdź, że GOOGLE_APPLICATION_CREDENTIALS nie występuje we wszystkich konfiguracjach środowiska wdrożeniowego
  • Uruchom proces OCR na tym samym zestawie przykładowych dokumentów, co w środowisku produkcyjnym, i porównaj jakość wygenerowanego tekstu
  • Przetestuj przetwarzanie plików PDF na dokumentach, które wcześniej były obsługiwane przez asynchroniczny potok GCS, i sprawdź, czy wynik tekstowy jest identyczny
  • Testuj PDF-y zabezpieczone hasłem za pomocą input.LoadPdf(path, Password: "...") — wcześniej nieobsługiwane
  • Testuj przetwarzanie wielostronicowych TIFF-ów używając input.LoadImageFrames() i potwierdź, że wszystkie klatki są przetwarzane
  • Uruchom procesor wsadowy na reprezentatywnej próbce i sprawdź, czy jakość wyników odpowiada poprzednim wynikom
  • Potwierdź, że wartości result.Confidence mieszczą się w akceptowalnym zakresie dla twojego korpusu dokumentu
  • Zweryfikuj, że przeszukiwalny wynik PDF używając result.SaveAsSearchablePdf() dla dokumentów, które wcześniej wymagały oddzielnej biblioteki PDF
  • Uruchom aplikację w środowisku bez połączenia z Internetem i sprawdź, czy OCR działa poprawnie

Kluczowe korzyści z migracji do IronOCR

Powierzchnia poświadczeń zredukowana do zera plików. Po migracji nie ma plików kluczy JSON, konfiguracji kubełków GCS, rol IAM, kont serwisowych i zmiennych środowiskowych GOOGLE_APPLICATION_CREDENTIALS w twojej infrastrukturze. Cała powierzchnia poświadczeń to jedna zmienna środowiskowa zawierająca ciąg znaków klucza licencyjnego. Rotacja kluczy, która była obowiązkową operacją okresową w Google Cloud Vision, nie jest już stosowana. W przypadku zespołów działających w wielu regionach lub korzystających z usług wielu dostawców chmury, zmniejszenie złożoności konfiguracji wdrożenia jest natychmiastowe.

Przetwarzanie plików PDF i TIFF bez zewnętrznych zależności. Asynchroniczny potok GCS oraz pętla ramek TIFF w System.Drawing zostały całkowicie usunięte. input.LoadPdf() i input.LoadImageFrames() są zamiennikami — oba synchroniczne, oba lokalne, oba zajmują trzy linie od wywołania do wyniku. Pliki PDF chronione hasłem, których przetwarzanie było niemożliwe w Google Cloud Vision, działają po dodaniu jednego dodatkowego parametru. Przewodnik dotyczący OCR plików PDF oraz przewodnik dotyczący danych wejściowych w formacie TIFF obejmują pełny interfejs API danych wejściowych.

Przetwarzanie wsadowe z prędkością procesora. Zniesienie limitu 1800 żądań na minutę oraz obowiązkowego 60-sekundowego czasu oczekiwania na ponowną próbę oznacza, że zadania wsadowe, które wcześniej podlegały ograniczeniom szybkości, teraz działają z prędkością dostępnych rdzeni procesora. Komputer z 16 rdzeniami przetwarza 16 dokumentów jednocześnie bez żadnej zewnętrznej akceptacji. Wzorzec Parallel.ForEach z instancjami IronTesseract przypadającymi na wątek jest bezpośrednim zamiennikiem dla ograniczonej pętli sekwencyjnej. Przewodnik optymalizacji szybkości opisuje opcje konfiguracji silnika, które dostosowują wydajność do określonych typów dokumentów.

Strukturalne dane bez Protobuf. Każdy OcrResult udostępnia Text, Confidence, Pages, Paragraphs, Lines, Words i Characters jako typowane właściwości .NET bez zależności od przestrzeni nazw Protobuf, bez konkatynacji symboli i bez arytmetyki wierzchołków dla prostokątów ograniczających. Kod, który wcześniej wymagał zagnieżdżonych pętli z 20 liniami do wyodrębnienia tekstu akapitu, zmniejsza się do result.Paragraphs.Select(p => p.Text). Dla przypadków użycia, które wymagają pozycjonowania na poziomie słowa do analizy układu dokumentu, dostępne są bezpośrednio word.X, word.Y, word.Width i word.Height. Strona wyników OCR zawiera wszystkie właściwości modelu wyników.

Wbudowana funkcja tworzenia plików PDF z możliwością wyszukiwania.Google Cloud Visionzwraca wyłącznie tekst — tworzenie plików PDF z możliwością wyszukiwania wymagało osobnej biblioteki do generowania plików PDF, co wiązało się z dodaniem kolejnej zależności NuGet, kolejnego API do opanowania oraz dodatkowej licencji do oceny. IronOCR's result.SaveAsSearchablePdf(outputPath) produkuje w pełni przeszukiwalny PDF z dowolnego wyniku OCR w jednej linii. Dla przepływów pracy archiwizacji dokumentów i procesów odkrywania prawnych, to eliminuje całą zależność. Przykładowy plik PDF z funkcją wyszukiwania ilustruje ten schemat od początku do końca.

Suwerenność danych dla branż podlegających regulacjom. Dokumenty przetwarzane przez IronOCR nigdy nie opuszczają serwera. W przypadku dokumentacji medycznej objętej HIPAA, danych technicznych podlegających kontroli ITAR, materiałów wykonawców z sektóra obronnego objętych CMMC, dokumentów prawnych objętych tajemnicą adwokacką oraz dokumentacji finansowej objętej PCI-DSS architektura lokalna całkowicie wyklucza kategorię zewnętrznych podmiotów przetwarzających dane z zakresu zgodności. Nie ma umowy o współpracy biznesowej do wynegocjowania, nie ma umowy o przetwarzaniu danych (DPA) do podpisania ani polityki Google dotyczącej przechowywania danych do przejrzenia. Centrum dokumentacji IronOCR obejmuje konfiguracje wdrożeniowe dla środowisk Docker, Linux, Azure i AWS, w których obowiązują wymagania dotyczące lokalizacji danych.

Zwróć uwagęGoogle Cloud Vision, Tesseract i iText są zarejestrowanymi znakami towarowymi ich właścicieli. Ta strona nie jest związana, popierana ani sponsorowana przez Google lub iText Group. Wszystkie nazwy produktów, loga i marki są własnością ich właścicieli. Porównania mają charakter wyłącznie informacyjny i odzwierciedlają informacje dostępne publicznie w momencie pisania.

Często Zadawane Pytania

Dlaczego warto przejść z Google Cloud Vision API na IronOCR?

Typowe czynniki motywujące to eliminacja złożoności interoperacyjności COM, zastąpienie zarządzania licencjami opartego na plikach, uniknięcie rozliczeń za stronę, umożliwienie wdrażania w Dockerze/kontenerach oraz przyjęcie natywnego dla NuGet przepływu pracy, który integruje się ze standardowymi narzędziami .NET.

Jakie są główne zmiany w kodzie podczas migracji z Google Cloud Vision API do IronOCR?

Zastąp sekwencje inicjalizacji Google Cloud Vision instancjonowaniem IronTesseract, usuń zarządzanie cyklem życia COM (jawne wzorce Create/Load/Close) i zaktualizuj nazwy właściwości wyników. W rezultacie znacznie zmniejsza się liczba powtarzających się linii kodu.

Jak zainstalować IronOCR, aby rozpocząć migrację?

Uruchom polecenie „Install-Package IronOcr” w konsoli menedżera pakietów lub „dotnet add package IronOcr” w interfejsie CLI. Pakiety językowe są oddzielnymi pakietami: na przykład „dotnet add package IronOcr.Languages.French” dla języka francuskiego.

Czy IronOCR dorównuje dokładnością OCR interfejsowi API Google Cloud Vision w przypadku standardowych dokumentów biznesowych?

IronOCR zapewnia wysoką dokładność w przypadku standardowych treści biznesowych, w tym faktur, umów, paragonów i formularzy wypełnionych na komputerze. Filtry przetwarzania wstępnego obrazu (prostowanie, usuwanie szumów, wzmacnianie kontrastu) dodatkowo poprawiają rozpoznawanie w przypadku pogorszonej jakości danych wejściowych.

W jaki sposób IronOCR obsługuje dane językowe, które Google Cloud Vision API instaluje oddzielnie?

Dane językowe w IronOCR są dystrybuowane jako pakiety NuGet. Polecenie „dotnet add package IronOcr.Languages.German” instaluje obsługę języka niemiećkiego. Nie wymaga to ręcznego umieszczania plików ani podawania ścieżek katalogów.

Czy migracja z Google Cloud Vision API do IronOCR wymaga zmian w infrastrukturze wdrożeniowej?

IronOCR wymaga mniej zmian w infrastrukturze niż Google Cloud Vision API. Nie ma ścieżek binarnych SDK, lokalizacji plików licencyjnych ani konfiguracji serwerów licencyjnych. Pakiet NuGet zawiera kompletny silnik OCR, a klucz licencyjny jest ciągiem znaków ustawionym w kodzie aplikacji.

Jak skonfigurować licencjonowanie IronOCR po migracji?

W kodzie uruchamiającym aplikację przypisz IronOcr.License.LicenseKey = „YOUR-KEY”. W Dockerze lub Kubernetesie zapisz klucz jako zmienną środowiskową i odczytaj go podczas uruchamiania. Użyj License.IsValidLicense do sprawdzenia ważności przed przyjęciem ruchu.

Czy IronOCR może przetwarzać pliki PDF w taki sam sposób jak Google Cloud Vision?

Tak. IronOCR odczytuje zarówno natywne, jak i zeskanowane pliki PDF. Należy utworzyć instancję IronTesseract, wywołać ocr.Read(input), gdzie input jest ścieżką do pliku PDF lub obiektem OcrPdfInput, a następnie iterować strony OcrResult. Nie jest wymagany oddzielny proces renderowania plików PDF.

W jaki sposób IronOCR radzi sobie z wątkami podczas przetwarzania dużych ilości danych?

IronTesseract można bezpiecznie instancjonować dla każdego wątku. Uruchom jedną instancję na wątek w Parallel.ForEach lub puli zadań, uruchom OCR równolegle i usuń każdą instancję po zakończeniu. Nie jest wymagany żaden stan globalny ani blokowanie.

Jakie formaty wyjściowe obsługuje IronOCR po wyodrębnieniu tekstu?

IronOCR zwraca ustrukturyzowane wyniki, w tym tekst, współrzędne słów, wyniki pewności i strukturę strony. Opcje eksportu obejmują zwykły tekst, PDF z możliwością wyszukiwania oraz obiekty wyników ustrukturyzowanych do dalszego przetwarzania.

Czy ceny IronOCR są bardziej przewidywalne niż Google Cloud Vision API w przypadku skalowania obciążeń?

IronOCR stosuje licencję wieczystą z opłatą ryczałtową, bez opłat za stronę lub wolumen. Niezależnie od tego, czy przetwarzasz 10 000, czy 10 milionów stron, koszt licencji pozostaje stały. Opcje licencji wolumenowych i zespołowych znajdują się na stronie z cennikiem IronOCR.

Co stanie się z moimi istniejącymi testami po migracji z Google Cloud Vision API do IronOCR?

Testy sprawdzające wyodrębnioną treść tekstową powinny nadal przechodzić pomyślnie po migracji. Testy weryfikujące wzorce wywołań API lub cykl życia obiektów COM będą wymagały aktualizacji, aby odzwierciedlały prostszy model inicjalizacji i wyników IronOCR.

Kannaopat Udonpant
Inżynier oprogramowania
Zanim stał się inżynierem oprogramowania, Kannapat ukończył doktorat z zasobów środowiskowych na Uniwersytecie Hokkaido w Japonii. W czasie studiowania, Kannapat również został członkiem Laboratorium Robotyki Pojazdów, które jest częścią Wydziału Inżynierii Bioprodukcji. W 2022 roku wykorzystał swoje umiejętności w ...
Czytaj więcej

Zespół wsparcia Iron

Jesteśmy online 24 godziny, 5 dni w tygodniu.
Czat
E-mail
Zadzwoń do mnie